blob: 7160ad319eb9e738d1c9a459a719b6856a0a9a87 [file] [log] [blame]
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.user.client.ui.impl;
import com.google.gwt.core.client.JavaScriptException;
import com.google.gwt.dom.client.Element;
import com.google.gwt.safehtml.shared.annotations.IsSafeHtml;
import com.google.gwt.safehtml.shared.annotations.SuppressIsSafeHtmlCastCheck;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.RichTextArea;
import com.google.gwt.user.client.ui.RichTextArea.FontSize;
import com.google.gwt.user.client.ui.RichTextArea.Justification;
/**
* Basic rich text platform implementation.
*
* <p>Deprecated, may be flattened into RichTextAreaImpl as it has no other
* direct subclasses.</p>
*/
@Deprecated
public abstract class RichTextAreaImplStandard extends RichTextAreaImpl implements
RichTextArea.Formatter {
/**
* The message displayed when the formatter is used before the RichTextArea
* is initialized.
*/
private static final String INACTIVE_MESSAGE = "RichTextArea formatters " +
"cannot be used until the RichTextArea is attached and focused.";
/**
* Holds a cached copy of any user setHTML/setText/setEnabled actions until
* the real text area is fully initialized. Becomes <code>null</code> after
* init.
*/
private Element beforeInitPlaceholder = DOM.createDiv();
/**
* Set to true when the {@link RichTextArea} is attached to the page and
* {@link #initElement()} is called. If the {@link RichTextArea} is detached
* before {@link #onElementInitialized()} is called, this will be set to
* false. See issue 1897 for details.
*/
protected boolean initializing;
/**
* Indicates that the text area should be focused as soon as it is loaded.
*/
private boolean isPendingFocus;
/**
* True when the element has been attached.
*/
private boolean isReady;
@Override
public native Element createElement() /*-{
return $doc.createElement('iframe');
}-*/;
public void createLink(String url) {
execCommand("CreateLink", url);
}
public String getBackColor() {
return queryCommandValue("BackColor");
}
public String getForeColor() {
return queryCommandValue("ForeColor");
}
@Override
public final String getHTML() {
return beforeInitPlaceholder == null ? getHTMLImpl() : beforeInitPlaceholder.getInnerHTML();
}
@Override
public final String getText() {
return beforeInitPlaceholder == null ? getTextImpl() : beforeInitPlaceholder.getInnerText();
}
@Override
public native void initElement() /*-{
// Most browsers don't like setting designMode until slightly _after_
// the iframe becomes attached to the DOM. Any non-zero timeout will do
// just fine.
var _this = this;
_this.@com.google.gwt.user.client.ui.impl.RichTextAreaImplStandard::onElementInitializing()();
setTimeout($entry(function() {
// We need to check to see if the content window still is there. It might not be if the RTA
// first was attached to the DOM and then quickly was removed before the timeout fired.
if (_this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow != null) {
// Turn on design mode.
_this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.designMode = 'On';
// Send notification that the iframe has reached design mode.
_this.@com.google.gwt.user.client.ui.impl.RichTextAreaImplStandard::onElementInitialized()();
}
}), 1);
}-*/;
public void insertHorizontalRule() {
execCommand("InsertHorizontalRule", null);
}
public void insertHTML(@IsSafeHtml String html) {
execCommand("InsertHTML", html);
}
public void insertImage(String url) {
execCommand("InsertImage", url);
}
public void insertOrderedList() {
execCommand("InsertOrderedList", null);
}
public void insertUnorderedList() {
execCommand("InsertUnorderedList", null);
}
public boolean isBold() {
return queryCommandState("Bold");
}
@Override
public boolean isEnabled() {
return beforeInitPlaceholder == null ? isEnabledImpl()
: !beforeInitPlaceholder.getPropertyBoolean("disabled");
}
public boolean isItalic() {
return queryCommandState("Italic");
}
public boolean isStrikethrough() {
return queryCommandState("Strikethrough");
}
public boolean isSubscript() {
return queryCommandState("Subscript");
}
public boolean isSuperscript() {
return queryCommandState("Superscript");
}
public boolean isUnderlined() {
return queryCommandState("Underline");
}
public void leftIndent() {
execCommand("Outdent", null);
}
public void redo() {
execCommand("Redo", "false");
}
public void removeFormat() {
execCommand("RemoveFormat", null);
}
public void removeLink() {
execCommand("Unlink", "false");
}
public void rightIndent() {
execCommand("Indent", null);
}
public void selectAll() {
execCommand("SelectAll", null);
}
public void setBackColor(String color) {
execCommand("BackColor", color);
}
@Override
public void setEnabled(boolean enabled) {
if (beforeInitPlaceholder == null) {
setEnabledImpl(enabled);
} else {
beforeInitPlaceholder.setPropertyBoolean("disabled", !enabled);
}
}
@Override
public void setFocus(boolean focused) {
if (initializing) {
// Issue 3503: if we focus before the iframe is in design mode, the text
// caret will not appear.
isPendingFocus = focused;
} else {
setFocusImpl(focused);
}
}
public void setFontName(String name) {
execCommand("FontName", name);
}
public void setFontSize(FontSize fontSize) {
execCommand("FontSize", Integer.toString(fontSize.getNumber()));
}
public void setForeColor(String color) {
execCommand("ForeColor", color);
}
@Override
public final void setHTML(@IsSafeHtml String html) {
if (beforeInitPlaceholder == null) {
setHTMLImpl(html);
} else {
beforeInitPlaceholder.setInnerHTML(html);
}
}
public void setJustification(Justification justification) {
if (justification == Justification.CENTER) {
execCommand("JustifyCenter", null);
} else if (justification == Justification.FULL) {
execCommand("JustifyFull", null);
} else if (justification == Justification.LEFT) {
execCommand("JustifyLeft", null);
} else if (justification == Justification.RIGHT) {
execCommand("JustifyRight", null);
}
}
@Override
public final void setText(String text) {
if (beforeInitPlaceholder == null) {
setTextImpl(text);
} else {
beforeInitPlaceholder.setInnerText(text);
}
}
public void toggleBold() {
execCommand("Bold", "false");
}
public void toggleItalic() {
execCommand("Italic", "false");
}
public void toggleStrikethrough() {
execCommand("Strikethrough", "false");
}
public void toggleSubscript() {
execCommand("Subscript", "false");
}
public void toggleSuperscript() {
execCommand("Superscript", "false");
}
public void toggleUnderline() {
execCommand("Underline", "False");
}
public void undo() {
execCommand("Undo", "false");
}
@Override
@SuppressIsSafeHtmlCastCheck
public void uninitElement() {
isReady = false;
// Issue 1897: initElement uses a timeout, so its possible to call this
// method after calling initElement, but before the event system is in
// place.
if (initializing) {
initializing = false;
return;
}
// Unhook all custom event handlers when the element is detached.
unhookEvents();
// Recreate the placeholder element and store the iframe's contents and the
// enabled status in it. This is necessary because some browsers will wipe
// the iframe's contents when it is removed from the DOM.
@IsSafeHtml String html = getHTML(); // TODO: mXSS
boolean enabled = isEnabled();
beforeInitPlaceholder = DOM.createDiv();
beforeInitPlaceholder.setInnerHTML(html);
setEnabled(enabled);
}
protected native String getHTMLImpl() /*-{
return this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.body.innerHTML;
}-*/;
protected native String getTextImpl() /*-{
return this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.body.textContent;
}-*/;
@Override
protected native void hookEvents() /*-{
var elem = this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem;
var wnd = elem.contentWindow;
elem.__gwt_handler = $entry(function(evt) {
@com.google.gwt.user.client.DOM::dispatchEvent(Lcom/google/gwt/user/client/Event;Lcom/google/gwt/dom/client/Element;)(evt, elem);
});
elem.__gwt_focusHandler = function(evt) {
if (elem.__gwt_isFocused) {
return;
}
elem.__gwt_isFocused = true;
elem.__gwt_handler(evt);
};
elem.__gwt_blurHandler = function(evt) {
if (!elem.__gwt_isFocused) {
return;
}
elem.__gwt_isFocused = false;
elem.__gwt_handler(evt);
};
wnd.addEventListener('keydown', elem.__gwt_handler, true);
wnd.addEventListener('keyup', elem.__gwt_handler, true);
wnd.addEventListener('keypress', elem.__gwt_handler, true);
wnd.addEventListener('mousedown', elem.__gwt_handler, true);
wnd.addEventListener('mouseup', elem.__gwt_handler, true);
wnd.addEventListener('mousemove', elem.__gwt_handler, true);
wnd.addEventListener('mouseover', elem.__gwt_handler, true);
wnd.addEventListener('mouseout', elem.__gwt_handler, true);
wnd.addEventListener('click', elem.__gwt_handler, true);
wnd.addEventListener('focus', elem.__gwt_focusHandler, true);
wnd.addEventListener('blur', elem.__gwt_blurHandler, true);
}-*/;
protected native boolean isEnabledImpl() /*-{
var elem = this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem;
return elem.contentWindow.document.designMode.toUpperCase() == 'ON';
}-*/;
@Override @SuppressIsSafeHtmlCastCheck
protected void onElementInitialized() {
// Issue 1897: This method is called after a timeout, during which time the
// element might by detached.
if (!initializing) {
return;
}
initializing = false;
isReady = true;
// When the iframe is ready, ensure cached content is set.
if (beforeInitPlaceholder != null) {
setHTMLImpl(beforeInitPlaceholder.getInnerHTML());
setEnabledImpl(isEnabled());
beforeInitPlaceholder = null;
}
super.onElementInitialized();
// Focus on the element now that it is initialized
if (isPendingFocus) {
isPendingFocus = false;
setFocus(true);
}
}
protected void onElementInitializing() {
initializing = true;
isPendingFocus = false;
}
protected native void setEnabledImpl(boolean enabled) /*-{
var elem = this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem;
elem.contentWindow.document.designMode = enabled ? 'On' : 'Off';
}-*/;
protected native void setFocusImpl(boolean focused) /*-{
if (focused) {
this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.focus();
} else {
this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.blur();
}
}-*/;
protected native void setHTMLImpl(@IsSafeHtml String html) /*-{
this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.body.innerHTML = html;
}-*/;
protected native void setTextImpl(String text) /*-{
this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.body.textContent = text;
}-*/;
protected native void unhookEvents() /*-{
var elem = this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem;
var wnd = elem.contentWindow;
wnd.removeEventListener('keydown', elem.__gwt_handler, true);
wnd.removeEventListener('keyup', elem.__gwt_handler, true);
wnd.removeEventListener('keypress', elem.__gwt_handler, true);
wnd.removeEventListener('mousedown', elem.__gwt_handler, true);
wnd.removeEventListener('mouseup', elem.__gwt_handler, true);
wnd.removeEventListener('mousemove', elem.__gwt_handler, true);
wnd.removeEventListener('mouseover', elem.__gwt_handler, true);
wnd.removeEventListener('mouseout', elem.__gwt_handler, true);
wnd.removeEventListener('click', elem.__gwt_handler, true);
wnd.removeEventListener('focus', elem.__gwt_focusHandler, true);
wnd.removeEventListener('blur', elem.__gwt_blurHandler, true);
elem.__gwt_handler = null;
elem.__gwt_focusHandler = null;
elem.__gwt_blurHandler = null;
}-*/;
void execCommand(String cmd, String param) {
assert isReady : INACTIVE_MESSAGE;
if (isReady) {
// When executing a command, focus the iframe first, since some commands
// don't take properly when it's not focused.
setFocus(true);
try {
execCommandAssumingFocus(cmd, param);
} catch (JavaScriptException e) {
// In mozilla, editing throws a JS exception if the iframe is
// *hidden, but attached*.
}
}
}
native void execCommandAssumingFocus(String cmd, String param) /*-{
this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.execCommand(cmd, false, param);
}-*/;
boolean queryCommandState(String cmd) {
if (isReady) {
// When executing a command, focus the iframe first, since some commands
// don't take properly when it's not focused.
setFocus(true);
try {
return queryCommandStateAssumingFocus(cmd);
} catch (JavaScriptException e) {
return false;
}
}
return false;
}
native boolean queryCommandStateAssumingFocus(String cmd) /*-{
return !!this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.queryCommandState(cmd);
}-*/;
String queryCommandValue(String cmd) {
if (isReady) {
// When executing a command, focus the iframe first, since some commands
// don't take properly when it's not focused.
setFocus(true);
try {
return queryCommandValueAssumingFocus(cmd);
} catch (JavaScriptException e) {
return "";
}
}
return "";
}
native String queryCommandValueAssumingFocus(String cmd) /*-{
return this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.queryCommandValue(cmd);
}-*/;
}