');
this.editingArea.appendChild(tabStop);
this.iframe.onfocus = function(){ _this.editNode.setActive(); }
}
}
try{
this.setDisabled(false);
}catch(e){
// Firefox throws an exception if the editor is initially hidden
// so, if this fails, try again onClick by adding "once" advice
var handle = dojo.connect(this, "onClick", this, function(){
this.setDisabled(false);
dojo.disconnect(handle);
});
}
this._preDomFilterContent(this.editNode);
var events=this.events.concat(this.captureEvents),i=0,et;
while((et=events[i++])){
this.connect(this.document, et.toLowerCase(), et);
}
if(!dojo.isIE){
try{ // sanity check for Mozilla
//AP: what's the point of this?
// this.document.execCommand("useCSS", false, true); // old moz call
this.document.execCommand("styleWithCSS", false, false); // new moz call
//this.document.execCommand("insertBrOnReturn", false, false); // new moz call
}catch(e2){ }
// FIXME: when scrollbars appear/disappear this needs to be fired
}else{ // IE contentEditable
// give the node Layout on IE
this.connect(this.document, "onmousedown", "_onMouseDown"); // #4996 fix focus
this.editNode.style.zoom = 1.0;
}
if(this.focusOnLoad){
setTimeout(dojo.hitch(this, "focus"), 0); // have to wait for IE to set unselectable=off
}
this.onDisplayChanged(e);
if(this.onLoadDeferred){
this.onLoadDeferred.callback(true);
}
},
onKeyDown: function(/* Event */ e){
// summary: Fired on keydown
// we need this event at the moment to get the events from control keys
// such as the backspace. It might be possible to add this to Dojo, so that
// keyPress events can be emulated by the keyDown and keyUp detection.
if(dojo.isIE){
if(e.keyCode == dojo.keys.TAB && e.shiftKey && !e.ctrlKey && !e.altKey){
// focus the BODY so the browser will tab away from it instead
this.iframe.focus();
}else if(e.keyCode == dojo.keys.TAB && !e.shiftKey && !e.ctrlKey && !e.altKey){
// focus the BODY so the browser will tab away from it instead
this.tabStop.focus();
}else if(e.keyCode === dojo.keys.BACKSPACE && this.document.selection.type === "Control"){
// IE has a bug where if a non-text object is selected in the editor,
// hitting backspace would act as if the browser's back button was
// clicked instead of deleting the object. see #1069
dojo.stopEvent(e);
this.execCommand("delete");
}else if((65 <= e.keyCode&&e.keyCode <= 90) ||
(e.keyCode>=37&&e.keyCode<=40) // FIXME: get this from connect() instead!
){ //arrow keys
e.charCode = e.keyCode;
this.onKeyPress(e);
}
}else if(dojo.isMoz){
if(e.keyCode == dojo.keys.TAB && !e.shiftKey && !e.ctrlKey && !e.altKey && this.iframe){
// update iframe document title for screen reader
this.iframe.contentDocument.title = this._localizedIframeTitles.iframeFocusTitle;
// Place focus on the iframe. A subsequent tab or shift tab will put focus
// on the correct control.
this.iframe.focus(); // this.focus(); won't work
dojo.stopEvent(e);
}else if(e.keyCode == dojo.keys.TAB && e.shiftKey){
// if there is a toolbar, set focus to it, otherwise ignore
if(this.toolbar){
this.toolbar.focus();
}
dojo.stopEvent(e);
}
}
},
onKeyUp: function(e){
// summary: Fired on keyup
return;
},
KEY_CTRL: 1,
KEY_SHIFT: 2,
onKeyPress: function(e){
// summary: Fired on keypress
// handle the various key events
var modifiers = (e.ctrlKey && !e.altKey) ? this.KEY_CTRL : 0 | e.shiftKey ? this.KEY_SHIFT : 0;
var key = e.keyChar || e.keyCode;
if(this._keyHandlers[key]){
// console.debug("char:", e.key);
var handlers = this._keyHandlers[key], i = 0, h;
while((h = handlers[i++])){
if(modifiers == h.modifiers){
if(!h.handler.apply(this,arguments)){
e.preventDefault();
}
break;
}
}
}
// function call after the character has been inserted
setTimeout(dojo.hitch(this, function(){
this.onKeyPressed(e);
}), 1);
},
addKeyHandler: function(/*String*/key, /*Int*/modifiers, /*Function*/handler){
// summary: add a handler for a keyboard shortcut
if(!dojo.isArray(this._keyHandlers[key])){ this._keyHandlers[key] = []; }
this._keyHandlers[key].push({
modifiers: modifiers || 0,
handler: handler
});
},
onKeyPressed: function(/*Event*/e){
this.onDisplayChanged(/*e*/); // can't pass in e
},
onClick: function(/*Event*/e){
// console.info('onClick',this._tryDesignModeOn);
this.onDisplayChanged(e);
},
_onMouseDown: function(/*Event*/e){ // IE only to prevent 2 clicks to focus
if(!this._focused && !this.disabled){
this.focus();
}
},
_onBlur: function(e){
this.inherited(arguments);
var _c=this.getValue(true);
if(_c!=this.savedContent){
this.onChange(_c);
this.savedContent=_c;
}
if(dojo.isMoz && this.iframe){
this.iframe.contentDocument.title = this._localizedIframeTitles.iframeEditTitle;
}
},
_initialFocus: true,
_onFocus: function(/*Event*/e){
// summary: Fired on focus
this.inherited(arguments);
if(dojo.isMoz && this._initialFocus){
this._initialFocus = false;
if(this.editNode.innerHTML.replace(/^\s+|\s+$/g, "") == " "){
this.placeCursorAtStart();
// this.execCommand("selectall");
// this.window.getSelection().collapseToStart();
}
}
},
// TODO: why is this needed - should we deprecate this ?
blur: function(){
// summary: remove focus from this instance
if(!dojo.isIE && this.window.document.documentElement && this.window.document.documentElement.focus){
this.window.document.documentElement.focus();
}else if(dojo.doc.body.focus){
dojo.doc.body.focus();
}
},
focus: function(){
// summary: move focus to this instance
if(!dojo.isIE){
dijit.focus(this.iframe);
}else if(this.editNode && this.editNode.focus){
// editNode may be hidden in display:none div, lets just punt in this case
//this.editNode.focus(); -> causes IE to scroll always (strict and quirks mode) to the top the Iframe
// if we fire the event manually and let the browser handle the focusing, the latest
// cursor position is focused like in FF
this.iframe.fireEvent('onfocus', document.createEventObject()); // createEventObject only in IE
// }else{
// TODO: should we throw here?
// console.debug("Have no idea how to focus into the editor!");
}
},
// _lastUpdate: 0,
updateInterval: 200,
_updateTimer: null,
onDisplayChanged: function(/*Event*/e){
// summary:
// This event will be fired everytime the display context
// changes and the result needs to be reflected in the UI.
// description:
// If you don't want to have update too often,
// onNormalizedDisplayChanged should be used instead
// var _t=new Date();
if(!this._updateTimer){
// this._lastUpdate=_t;
if(this._updateTimer){
clearTimeout(this._updateTimer);
}
this._updateTimer=setTimeout(dojo.hitch(this,this.onNormalizedDisplayChanged),this.updateInterval);
}
},
onNormalizedDisplayChanged: function(){
// summary:
// This event is fired every updateInterval ms or more
// description:
// If something needs to happen immidiately after a
// user change, please use onDisplayChanged instead
this._updateTimer=null;
},
onChange: function(newContent){
// summary:
// this is fired if and only if the editor loses focus and
// the content is changed
// console.log('onChange',newContent);
},
_normalizeCommand: function(/*String*/cmd){
// summary:
// Used as the advice function by dojo.connect to map our
// normalized set of commands to those supported by the target
// browser
var command = cmd.toLowerCase();
if(command == "hilitecolor" && !dojo.isMoz){
command = "backcolor";
}
return command;
},
queryCommandAvailable: function(/*String*/command){
// summary:
// Tests whether a command is supported by the host. Clients SHOULD check
// whether a command is supported before attempting to use it, behaviour
// for unsupported commands is undefined.
// command: The command to test for
var ie = 1;
var mozilla = 1 << 1;
var safari = 1 << 2;
var opera = 1 << 3;
var safari420 = 1 << 4;
var gt420 = dojo.isSafari;
function isSupportedBy(browsers){
return {
ie: Boolean(browsers & ie),
mozilla: Boolean(browsers & mozilla),
safari: Boolean(browsers & safari),
safari420: Boolean(browsers & safari420),
opera: Boolean(browsers & opera)
}
}
var supportedBy = null;
switch(command.toLowerCase()){
case "bold": case "italic": case "underline":
case "subscript": case "superscript":
case "fontname": case "fontsize":
case "forecolor": case "hilitecolor":
case "justifycenter": case "justifyfull": case "justifyleft":
case "justifyright": case "delete": case "selectall": case "toggledir":
supportedBy = isSupportedBy(mozilla | ie | safari | opera);
break;
case "createlink": case "unlink": case "removeformat":
case "inserthorizontalrule": case "insertimage":
case "insertorderedlist": case "insertunorderedlist":
case "indent": case "outdent": case "formatblock":
case "inserthtml": case "undo": case "redo": case "strikethrough":
supportedBy = isSupportedBy(mozilla | ie | opera | safari420);
break;
case "blockdirltr": case "blockdirrtl":
case "dirltr": case "dirrtl":
case "inlinedirltr": case "inlinedirrtl":
supportedBy = isSupportedBy(ie);
break;
case "cut": case "copy": case "paste":
supportedBy = isSupportedBy( ie | mozilla | safari420);
break;
case "inserttable":
supportedBy = isSupportedBy(mozilla | ie);
break;
case "insertcell": case "insertcol": case "insertrow":
case "deletecells": case "deletecols": case "deleterows":
case "mergecells": case "splitcell":
supportedBy = isSupportedBy(ie | mozilla);
break;
default: return false;
}
return (dojo.isIE && supportedBy.ie) ||
(dojo.isMoz && supportedBy.mozilla) ||
(dojo.isSafari && supportedBy.safari) ||
(gt420 && supportedBy.safari420) ||
(dojo.isOpera && supportedBy.opera); // Boolean return true if the command is supported, false otherwise
},
execCommand: function(/*String*/command, argument){
// summary: Executes a command in the Rich Text area
// command: The command to execute
// argument: An optional argument to the command
var returnValue;
//focus() is required for IE to work
//In addition, focus() makes sure after the execution of
//the command, the editor receives the focus as expected
this.focus();
command = this._normalizeCommand(command);
if(argument != undefined){
if(command == "heading"){
throw new Error("unimplemented");
}else if((command == "formatblock") && dojo.isIE){
argument = '<'+argument+'>';
}
}
if(command == "inserthtml"){
argument=this._preFilterContent(argument);
if(dojo.isIE){
var insertRange = this.document.selection.createRange();
if(this.document.selection.type.toUpperCase()=='CONTROL'){
var n=insertRange.item(0);
while(insertRange.length){
insertRange.remove(insertRange.item(0));
}
n.outerHTML=argument;
}else{
insertRange.pasteHTML(argument);
}
insertRange.select();
//insertRange.collapse(true);
returnValue=true;
}else if(dojo.isMoz && !argument.length){
//mozilla can not inserthtml an empty html to delete current selection
//so we delete the selection instead in this case
dojo.withGlobal(this.window,'remove',dijit._editor.selection);
returnValue=true;
}else{
returnValue=this.document.execCommand(command, false, argument);
}
}else if(
(command == "unlink")&&
(this.queryCommandEnabled("unlink"))&&
(dojo.isMoz || dojo.isSafari)
){
// fix up unlink in Mozilla to unlink the link and not just the selection
// grab selection
// Mozilla gets upset if we just store the range so we have to
// get the basic properties and recreate to save the selection
var selection = this.window.getSelection();
// var selectionRange = selection.getRangeAt(0);
// var selectionStartContainer = selectionRange.startContainer;
// var selectionStartOffset = selectionRange.startOffset;
// var selectionEndContainer = selectionRange.endContainer;
// var selectionEndOffset = selectionRange.endOffset;
// select our link and unlink
var a = dojo.withGlobal(this.window, "getAncestorElement",dijit._editor.selection, ['a']);
dojo.withGlobal(this.window, "selectElement", dijit._editor.selection, [a]);
returnValue=this.document.execCommand("unlink", false, null);
}else if((command == "hilitecolor")&&(dojo.isMoz)){
// // mozilla doesn't support hilitecolor properly when useCSS is
// // set to false (bugzilla #279330)
this.document.execCommand("styleWithCSS", false, true);
returnValue = this.document.execCommand(command, false, argument);
this.document.execCommand("styleWithCSS", false, false);
}else if((dojo.isIE)&&( (command == "backcolor")||(command == "forecolor") )){
// Tested under IE 6 XP2, no problem here, comment out
// IE weirdly collapses ranges when we exec these commands, so prevent it
// var tr = this.document.selection.createRange();
argument = arguments.length > 1 ? argument : null;
returnValue = this.document.execCommand(command, false, argument);
// timeout is workaround for weird IE behavior were the text
// selection gets correctly re-created, but subsequent input
// apparently isn't bound to it
// setTimeout(function(){tr.select();}, 1);
}else{
argument = arguments.length > 1 ? argument : null;
// if(dojo.isMoz){
// this.document = this.iframe.contentWindow.document
// }
if(argument || command!="createlink"){
returnValue = this.document.execCommand(command, false, argument);
}
}
this.onDisplayChanged();
return returnValue;
},
queryCommandEnabled: function(/*String*/command){
// summary: check whether a command is enabled or not
if(this.disabled){ return false; }
command = this._normalizeCommand(command);
if(dojo.isMoz || dojo.isSafari){
if(command == "unlink"){ // mozilla returns true always
// console.debug(dojo.withGlobal(this.window, "hasAncestorElement",dijit._editor.selection, ['a']));
return dojo.withGlobal(this.window, "hasAncestorElement",dijit._editor.selection, ['a']);
}else if(command == "inserttable"){
return true;
}
}
//see #4109
if(dojo.isSafari){
if(command == "copy"){
command = "cut";
}else if(command == "paste"){
return true;
}
}
// return this.document.queryCommandEnabled(command);
var elem = dojo.isIE ? this.document.selection.createRange() : this.document;
return elem.queryCommandEnabled(command);
},
queryCommandState: function(command){
// summary: check the state of a given command
if(this.disabled){ return false; }
command = this._normalizeCommand(command);
return this.document.queryCommandState(command);
},
queryCommandValue: function(command){
// summary: check the value of a given command
if(this.disabled){ return false; }
command = this._normalizeCommand(command);
if(dojo.isIE && command == "formatblock"){
return this._local2NativeFormatNames[this.document.queryCommandValue(command)];
}
return this.document.queryCommandValue(command);
},
// Misc.
placeCursorAtStart: function(){
// summary:
// place the cursor at the start of the editing area
this.focus();
//see comments in placeCursorAtEnd
var isvalid=false;
if(dojo.isMoz){
var first=this.editNode.firstChild;
while(first){
if(first.nodeType == 3){
if(first.nodeValue.replace(/^\s+|\s+$/g, "").length>0){
isvalid=true;
dojo.withGlobal(this.window, "selectElement", dijit._editor.selection, [first]);
break;
}
}else if(first.nodeType == 1){
isvalid=true;
dojo.withGlobal(this.window, "selectElementChildren",dijit._editor.selection, [first]);
break;
}
first = first.nextSibling;
}
}else{
isvalid=true;
dojo.withGlobal(this.window, "selectElementChildren",dijit._editor.selection, [this.editNode]);
}
if(isvalid){
dojo.withGlobal(this.window, "collapse", dijit._editor.selection, [true]);
}
},
placeCursorAtEnd: function(){
// summary:
// place the cursor at the end of the editing area
this.focus();
//In mozilla, if last child is not a text node, we have to use selectElementChildren on this.editNode.lastChild
//otherwise the cursor would be placed at the end of the closing tag of this.editNode.lastChild
var isvalid=false;
if(dojo.isMoz){
var last=this.editNode.lastChild;
while(last){
if(last.nodeType == 3){
if(last.nodeValue.replace(/^\s+|\s+$/g, "").length>0){
isvalid=true;
dojo.withGlobal(this.window, "selectElement",dijit._editor.selection, [last]);
break;
}
}else if(last.nodeType == 1){
isvalid=true;
if(last.lastChild){
dojo.withGlobal(this.window, "selectElement",dijit._editor.selection, [last.lastChild]);
}else{
dojo.withGlobal(this.window, "selectElement",dijit._editor.selection, [last]);
}
break;
}
last = last.previousSibling;
}
}else{
isvalid=true;
dojo.withGlobal(this.window, "selectElementChildren",dijit._editor.selection, [this.editNode]);
}
if(isvalid){
dojo.withGlobal(this.window, "collapse", dijit._editor.selection, [false]);
}
},
getValue: function(/*Boolean?*/nonDestructive){
// summary:
// return the current content of the editing area (post filters are applied)
if(this.textarea){
if(this.isClosed || !this.isLoaded){
return this.textarea.value;
}
}
return this._postFilterContent(null, nonDestructive);
},
setValue: function(/*String*/html){
// summary:
// this function set the content. No undo history is preserved
if(!this.isLoaded){
// try again after the editor is finished loading
this.onLoadDeferred.addCallback(dojo.hitch(this, function(){
this.setValue(html);
}));
return;
}
if(this.textarea && (this.isClosed || !this.isLoaded)){
this.textarea.value=html;
}else{
html = this._preFilterContent(html);
var node = this.isClosed ? this.domNode : this.editNode;
node.innerHTML = html;
this._preDomFilterContent(node);
}
this.onDisplayChanged();
},
replaceValue: function(/*String*/html){
// summary:
// this function set the content while trying to maintain the undo stack
// (now only works fine with Moz, this is identical to setValue in all
// other browsers)
if(this.isClosed){
this.setValue(html);
}else if(this.window && this.window.getSelection && !dojo.isMoz){ // Safari
// look ma! it's a totally f'd browser!
this.setValue(html);
}else if(this.window && this.window.getSelection){ // Moz
html = this._preFilterContent(html);
this.execCommand("selectall");
if(dojo.isMoz && !html){ html = " " }
this.execCommand("inserthtml", html);
this._preDomFilterContent(this.editNode);
}else if(this.document && this.document.selection){//IE
//In IE, when the first element is not a text node, say
//an
tag, when replacing the content of the editing
//area, the tag will be around all the content
//so for now, use setValue for IE too
this.setValue(html);
}
},
_preFilterContent: function(/*String*/html){
// summary:
// filter the input before setting the content of the editing area
var ec = html;
dojo.forEach(this.contentPreFilters, function(ef){ if(ef){ ec = ef(ec); } });
return ec;
},
_preDomFilterContent: function(/*DomNode*/dom){
// summary:
// filter the input
dom = dom || this.editNode;
dojo.forEach(this.contentDomPreFilters, function(ef){
if(ef && dojo.isFunction(ef)){
ef(dom);
}
}, this);
},
_postFilterContent: function(/*DomNode|DomNode[]|String?*/dom,/*Boolean?*/nonDestructive){
// summary:
// filter the output after getting the content of the editing area
var ec;
if(!dojo.isString(dom)){
dom = dom || this.editNode;
if(this.contentDomPostFilters.length){
if(nonDestructive && dom['cloneNode']){
dom = dom.cloneNode(true);
}
dojo.forEach(this.contentDomPostFilters, function(ef){
dom = ef(dom);
});
}
ec = dijit._editor.getChildrenHtml(dom);
}else{
ec = dom;
}
if(!ec.replace(/^(?:\s|\xA0)+/g, "").replace(/(?:\s|\xA0)+$/g,"").length){ ec = ""; }
// if(dojo.isIE){
// //removing appended
for IE
// ec = ec.replace(/(?:
[\n\r]*)+$/i,"");
// }
dojo.forEach(this.contentPostFilters, function(ef){
ec = ef(ec);
});
return ec;
},
_saveContent: function(/*Event*/e){
// summary:
// Saves the content in an onunload event if the editor has not been closed
var saveTextarea = dojo.byId(dijit._scopeName + "._editor.RichText.savedContent");
saveTextarea.value += this._SEPARATOR + this.name + ":" + this.getValue();
},
escapeXml: function(/*String*/str, /*Boolean*/noSingleQuotes){
dojo.deprecated('dijit.Editor::escapeXml is deprecated','use dijit._editor.escapeXml instead', 2);
return dijit._editor.escapeXml(str,noSingleQuotes);
},
getNodeHtml: function(/* DomNode */node){
dojo.deprecated('dijit.Editor::getNodeHtml is deprecated','use dijit._editor.getNodeHtml instead', 2);
return dijit._editor.getNodeHtml(node);
},
getNodeChildrenHtml: function(/* DomNode */dom){
dojo.deprecated('dijit.Editor::getNodeChildrenHtml is deprecated','use dijit._editor.getChildrenHtml instead', 2);
return dijit._editor.getChildrenHtml(dom);
},
close: function(/*Boolean*/save, /*Boolean*/force){
// summary:
// Kills the editor and optionally writes back the modified contents to the
// element from which it originated.
// save:
// Whether or not to save the changes. If false, the changes are discarded.
// force:
if(this.isClosed){return false; }
if(!arguments.length){ save = true; }
this._content = this.getValue();
var changed = (this.savedContent != this._content);
// line height is squashed for iframes
// FIXME: why was this here? if(this.iframe){ this.domNode.style.lineHeight = null; }
if(this.interval){ clearInterval(this.interval); }
if(this.textarea){
with(this.textarea.style){
position = "";
left = top = "";
if(dojo.isIE){
overflow = this.__overflow;
this.__overflow = null;
}
}
this.textarea.value = save ? this._content : this.savedContent;
dojo._destroyElement(this.domNode);
this.domNode = this.textarea;
}else{
// if(save){
//why we treat moz differently? comment out to fix #1061
// if(dojo.isMoz){
// var nc = dojo.doc.createElement("span");
// this.domNode.appendChild(nc);
// nc.innerHTML = this.editNode.innerHTML;
// }else{
// this.domNode.innerHTML = this._content;
// }
// }
this.domNode.innerHTML = save ? this._content : this.savedContent;
}
dojo.removeClass(this.domNode, "RichTextEditable");
this.isClosed = true;
this.isLoaded = false;
// FIXME: is this always the right thing to do?
delete this.editNode;
if(this.window && this.window._frameElement){
this.window._frameElement = null;
}
this.window = null;
this.document = null;
this.editingArea = null;
this.editorObject = null;
return changed; // Boolean: whether the content has been modified
},
destroyRendering: function(){
// summary: stub
},
destroy: function(){
this.destroyRendering();
if(!this.isClosed){ this.close(false); }
this.inherited("destroy",arguments);
//dijit._editor.RichText.superclass.destroy.call(this);
},
_removeMozBogus: function(/* String */ html){
return html.replace(/\stype="_moz"/gi, '').replace(/\s_moz_dirty=""/gi, ''); // String
},
_removeSafariBogus: function(/* String */ html){
return html.replace(/\sclass="webkit-block-placeholder"/gi, ''); // String
},
_fixContentForMoz: function(/* String */ html){
// summary:
// Moz can not handle strong/em tags correctly, convert them to b/i
return html.replace(/<(\/)?strong([ \>])/gi, '<$1b$2')
.replace(/<(\/)?em([ \>])/gi, '<$1i$2' ); // String
},
_srcInImgRegex : /(?:(]+))/gi ,
_hrefInARegex : /(?:(]+))/gi ,
_preFixUrlAttributes: function(/* String */ html){
return html.replace(this._hrefInARegex, '$1$4$2$3$5$2 _djrealurl=$2$3$5$2')
.replace(this._srcInImgRegex, '$1$4$2$3$5$2 _djrealurl=$2$3$5$2'); // String
}
});
}