Added: ofbiz/trunk/framework/images/webapp/images/dojo/src/widget/RichText.js
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/images/webapp/images/dojo/src/widget/RichText.js?view=auto&rev=509273 ============================================================================== --- ofbiz/trunk/framework/images/webapp/images/dojo/src/widget/RichText.js (added) +++ ofbiz/trunk/framework/images/webapp/images/dojo/src/widget/RichText.js Mon Feb 19 09:56:06 2007 @@ -0,0 +1,1766 @@ +/* + Copyright (c) 2004-2006, The Dojo Foundation + All Rights Reserved. + + Licensed under the Academic Free License version 2.1 or above OR the + modified BSD license. For more information on Dojo licensing, see: + + http://dojotoolkit.org/community/licensing.shtml +*/ + + /* -*- tab-width: 4 -*- */ +dojo.provide("dojo.widget.RichText"); + +dojo.require("dojo.widget.*"); +dojo.require("dojo.html.*"); +dojo.require("dojo.html.layout"); +dojo.require("dojo.html.selection"); +dojo.require("dojo.event.*"); +dojo.require("dojo.string.extras"); +dojo.require("dojo.uri.Uri"); +dojo.require("dojo.Deferred"); + +// used to save content +if(dojo.hostenv.post_load_){ + (function(){ + var savetextarea = dojo.doc().createElement('textarea'); + savetextarea.id = "dojo.widget.RichText.savedContent"; + savetextarea.style = "display:none;position:absolute;top:-100px;left:-100px;height:3px;width:3px;overflow:hidden;"; + dojo.body().appendChild(savetextarea); + })(); +}else{ + //dojo.body() is not available before onLoad is fired + try { + dojo.doc().write('<textarea id="dojo.widget.RichText.savedContent" ' + + 'style="display:none;position:absolute;top:-100px;left:-100px;height:3px;width:3px;overflow:hidden;"></textarea>'); + }catch(e){ } +} + +dojo.widget.defineWidget( + "dojo.widget.RichText", + dojo.widget.HtmlWidget, + function(){ + // summary: + // dojo.widget.RichText is the core of the WYSIWYG editor in dojo, which + // provides the basic editing features. It also encapsulates the differences + // of different js engines for various browsers + + // contentPreFilters: Array + // pre content filter function register array + this.contentPreFilters = []; + + // contentPostFilters: Array + // post content filter function register array + this.contentPostFilters = []; + + // contentDomPreFilters: Array + // pre content dom filter function register array + this.contentDomPreFilters = []; + + // contentDomPostFilters: Array + // post content dom filter function register array + this.contentDomPostFilters = []; + + // editingAreaStyleSheets: Array + // array to store all the stylesheets applied to the editing area + this.editingAreaStyleSheets=[]; + + if(dojo.render.html.moz){ + this.contentPreFilters.push(this._fixContentForMoz); + } + + this._keyHandlers = {}; + + if(dojo.Deferred){ + this.onLoadDeferred = new dojo.Deferred(); + } + }, + { + // inheritWidth: Boolean + // whether to inherit the parent's width or simply use 100% + inheritWidth: false, + + // focusOnLoad: Boolean + // whether focusing into this instance of richtext when page onload + focusOnLoad: false, + + // saveName: String + // If a save name is specified the content is saved and restored when the user + // leave this page can come back, or if the editor is not properly closed after + // editing has started. + saveName: "", + + // styleSheets: String + // semicolon (";") separated list of css files for the editing area + styleSheets: "", + + // _content: String + // temporary content storage + _content: "", + + // height: String + // set height to fix the editor at a specific height, with scrolling + height: "", + + // minHeight: String + // The minimum height that the editor should have + minHeight: "1em", + + // isClosed: Boolean + isClosed: true, + + // isLoaded: Boolean + isLoaded: false, + + // useActiveX: Boolean + // whether to use the active-x object in IE + useActiveX: false, + + // relativeImageUrls: Boolean + // whether to use relative URLs for images - if this is enabled + // images will be given absolute URLs when inside the editor but + // will be changed to use relative URLs (to the current page) on save + relativeImageUrls: false, + + // _SEPARATOR: String + // used to concat contents from multiple textareas into a single string + _SEPARATOR: "@@**%%__RICHTEXTBOUNDRY__%%**@@", + + // onLoadDeferred: dojo.Deferred + // deferred that can be used to connect to the onLoad function. This + // will only be set if dojo.Deferred is required + onLoadDeferred: null, + + /* Init + *******/ + + fillInTemplate: function(){ + // summary: see dojo.widget.DomWidget + dojo.event.topic.publish("dojo.widget.RichText::init", this); + this.open(); + + + // backwards compatibility, needs to be removed + dojo.event.connect(this, "onKeyPressed", this, "afterKeyPress"); + dojo.event.connect(this, "onKeyPress", this, "keyPress"); + dojo.event.connect(this, "onKeyDown", this, "keyDown"); + dojo.event.connect(this, "onKeyUp", this, "keyUp"); + + this.setupDefaultShortcuts(); + }, + + setupDefaultShortcuts: function(){ + // summary: add some default key handlers + // description: + // Overwrite this to setup your own handlers. The default + // implementation does not use Editor2 commands, but directly + // executes the builtin commands within the underlying browser + // support. + var ctrl = this.KEY_CTRL; + var exec = function (cmd, arg) { + return arguments.length == 1 ? function () { this.execCommand(cmd); } : + function () { this.execCommand(cmd, arg); } + } + this.addKeyHandler("b", ctrl, exec("bold")); + this.addKeyHandler("i", ctrl, exec("italic")); + this.addKeyHandler("u", ctrl, exec("underline")); + this.addKeyHandler("a", ctrl, exec("selectall")); + this.addKeyHandler("s", ctrl, function () { this.save(true); }); + + this.addKeyHandler("1", ctrl, exec("formatblock", "h1")); + this.addKeyHandler("2", ctrl, exec("formatblock", "h2")); + this.addKeyHandler("3", ctrl, exec("formatblock", "h3")); + this.addKeyHandler("4", ctrl, exec("formatblock", "h4")); + + this.addKeyHandler("\\", ctrl, exec("insertunorderedlist")); + if(!dojo.render.html.ie){ + this.addKeyHandler("Z", ctrl, exec("redo")); + } + }, + + // events: Array + // events which should be connected to the underlying editing area + events: ["onBlur", "onFocus", "onKeyPress", "onKeyDown", "onKeyUp", "onClick"], + + /** + * Transforms the node referenced in this.domNode into a rich text editing + * node. This can result in the creation and replacement with an <iframe> if + * designMode is used, an <object> and active-x component if inside of IE or + * a reguler element if contentEditable is available. + */ + open: function (/*DomNode, optional*/element) { + // summary: + // Transforms the node referenced in this.domNode into a rich text editing + // node. This can result in the creation and replacement with an <iframe> if + // designMode is used, an <object> and active-x component if inside of IE or + // a reguler element if contentEditable is available. + + if(this.onLoadDeferred.fired >= 0){ + this.onLoadDeferred = new dojo.Deferred(); + } + + var h = dojo.render.html; + if (!this.isClosed) { this.close(); } + dojo.event.topic.publish("dojo.widget.RichText::open", this); + + this._content = ""; + if((arguments.length == 1)&&(element["nodeName"])){ this.domNode = element; } // else unchanged + + if( (this.domNode["nodeName"])&& + (this.domNode.nodeName.toLowerCase() == "textarea")){ + this.textarea = this.domNode; + var html = dojo.string.trim(this.textarea.value); + this.domNode = dojo.doc().createElement("div"); + dojo.html.copyStyle(this.domNode, this.textarea); + var tmpFunc = dojo.lang.hitch(this, function(){ + //some browsers refuse to submit display=none textarea, so + //move the textarea out of screen instead + with(this.textarea.style){ + display = "block"; + position = "absolute"; + left = top = "-1000px"; + + if(h.ie){ //nasty IE bug: abnormal formatting if overflow is not hidden + this.__overflow = overflow; + overflow = "hidden"; + } + } + }); + if(h.ie){ + setTimeout(tmpFunc, 10); + }else{ + tmpFunc(); + } + if(!h.safari){ + // FIXME: VERY STRANGE safari 2.0.4 behavior here caused by + // moving the textarea. Often crashed the browser!!! Seems + // fixed on webkit nightlies. + dojo.html.insertBefore(this.domNode, this.textarea); + } + // this.domNode.innerHTML = html; + + if(this.textarea.form){ + dojo.event.connect('before', this.textarea.form, "onsubmit", + // FIXME: should we be calling close() here instead? + dojo.lang.hitch(this, function(){ + this.textarea.value = this.getEditorContent(); + }) + ); + } + + // dojo plucks our original domNode from the document so we need + // to go back and put ourselves back in + var editor = this; + dojo.event.connect(this, "postCreate", function (){ + dojo.html.insertAfter(editor.textarea, editor.domNode); + }); + }else{ + var html = this._preFilterContent(dojo.string.trim(this.domNode.innerHTML)); + } + if(html == ""){ html = " "; } + var content = dojo.html.getContentBox(this.domNode); + this._oldHeight = content.height; + this._oldWidth = content.width; + + this._firstChildContributingMargin = this._getContributingMargin(this.domNode, "top"); + this._lastChildContributingMargin = this._getContributingMargin(this.domNode, "bottom"); + + this.savedContent = this.domNode.innerHTML; + this.domNode.innerHTML = ''; + + this.editingArea = dojo.doc().createElement("div"); + this.domNode.appendChild(this.editingArea); + + // If we're a list item we have to put in a blank line to force the + // bullet to nicely align at the top of text + if( (this.domNode["nodeName"])&& + (this.domNode.nodeName == "LI")){ + this.domNode.innerHTML = " <br>"; + } + + if(this.saveName != ""){ + var saveTextarea = dojo.doc().getElementById("dojo.widget.RichText.savedContent"); + if (saveTextarea.value != "") { + var datas = saveTextarea.value.split(this._SEPARATOR); + for (var i = 0; i < datas.length; i++) { + var data = datas[i].split(":"); + if (data[0] == this.saveName) { + html = data[1]; + datas.splice(i, 1); + break; + } + } + } + dojo.event.connect("before", window, "onunload", this, "_saveContent"); + // dojo.event.connect(window, "onunload", this, "_saveContent"); + } + + if(h.ie70 && this.useActiveX){ + dojo.debug("activeX in ie70 is not currently supported, useActiveX is ignored for now."); + this.useActiveX = false; + } + // Safari's selections go all out of whack if we do it inline, + // so for now IE is our only hero + //if (typeof document.body.contentEditable != "undefined") { + if(this.useActiveX && h.ie){ // active-x + var self = this; + //if call _drawObject directly here, textarea replacement + //won't work: no content is shown. However, add a delay + //can workaround this. No clue why. + setTimeout(function(){self._drawObject(html);}, 0); + }else if(h.ie || this._safariIsLeopard() || h.opera){ // contentEditable, easy + this.iframe = dojo.doc().createElement( 'iframe' ) ; + this.iframe.src = 'javascript:void(0)'; + this.editorObject = this.iframe; + with(this.iframe.style){ + border = '0'; + width = "100%"; + } + this.iframe.frameBorder = 0; + this.editingArea.appendChild(this.iframe) + this.window = this.iframe.contentWindow; + this.document = this.window.document; + this.document.open(); + this.document.write("<html><head><style>body{margin:0;padding:0;border:0;overflow:hidden;}</style></head><body><div></div></body></html>"); + this.document.close(); + this.editNode = this.document.body.firstChild;//document.createElement("div"); + this.editNode.contentEditable = true; + with (this.iframe.style) { + if(h.ie70){ + if(this.height){ + height = this.height; + } + if(this.minHeight){ + minHeight = this.minHeight; + } + }else{ + height = this.height ? this.height : this.minHeight; + } + } + + // FIXME: setting contentEditable on switches this element to + // IE's hasLayout mode, triggering weird margin collapsing + // behavior. It's particularly bad if the element you're editing + // contains childnodes that don't have margin: defined in local + // css rules. It would be nice if it was possible to hack around + // this. Sadly _firstChildContributingMargin and + // _lastChildContributingMargin don't work on IE unless all + // elements have margins set in CSS :-( + + //if the normal way fails, we try the hard way to get the list + //do not use _cacheLocalBlockFormatNames here, as it will trigger security warning in IE7 + //in the array below, ul can not come directly after ol, otherwise the queryCommandValue returns Normal for it + var formats = ['p', 'pre', 'address', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'div', 'ul']; + var localhtml = ""; + for(var i in formats){ + if(formats[i].charAt(1) != 'l'){ + localhtml += "<"+formats[i]+"><span>content</span></"+formats[i]+">"; + }else{ + localhtml += "<"+formats[i]+"><li>content</li></"+formats[i]+">"; + } + } + //queryCommandValue returns empty if we hide editNode, so move it out of screen temporary + with(this.editNode.style){ + position = "absolute"; + left = "-2000px"; + top = "-2000px"; + } + this.editNode.innerHTML = localhtml; + var node = this.editNode.firstChild; + while(node){ + dojo.withGlobal(this.window, "selectElement", dojo.html.selection, [node.firstChild]); + var nativename = node.tagName.toLowerCase(); + this._local2NativeFormatNames[nativename] = this.queryCommandValue("formatblock"); +// dojo.debug([nativename,this._local2NativeFormatNames[nativename]]); + this._native2LocalFormatNames[this._local2NativeFormatNames[nativename]] = nativename; + node = node.nextSibling; + } + with(this.editNode.style){ + position = ""; + left = ""; + top = ""; + } + + this.editNode.innerHTML = html; + if(this.height){ this.document.body.style.overflowY="scroll"; } + + dojo.lang.forEach(this.events, function(e){ + dojo.event.connect(this.editNode, e.toLowerCase(), this, e); + }, this); + + this.onLoad(); + } else { // designMode in iframe + this._drawIframe(html); + this.editorObject = this.iframe; + } + + // TODO: this is a guess at the default line-height, kinda works + if (this.domNode.nodeName == "LI") { this.domNode.lastChild.style.marginTop = "-1.2em"; } + dojo.html.addClass(this.domNode, "RichTextEditable"); + + this.isClosed = false; + }, + + _hasCollapseableMargin: function(/*DomNode*/element, /*String*/side) { + // summary: + // check if an element has padding or borders on the given side + // which would prevent it from collapsing margins + if (dojo.html.getPixelValue(element, + 'border-'+side+'-width', + false)) { + return false; + } else if (dojo.html.getPixelValue(element, + 'padding-'+side, + false)) { + return false; + } else { + return true; + } + }, + + _getContributingMargin: function(/*DomNode*/element, /*String*/topOrBottom) { + // summary: + // calculate how much margin this element and its first or last + // child are contributing to the total margin between this element + // and the adjacent node. CSS border collapsing makes this + // necessary. + + if (topOrBottom == "top") { + var siblingAttr = "previousSibling"; + var childSiblingAttr = "nextSibling"; + var childAttr = "firstChild"; + var marginProp = "margin-top"; + var siblingMarginProp = "margin-bottom"; + } else { + var siblingAttr = "nextSibling"; + var childSiblingAttr = "previousSibling"; + var childAttr = "lastChild"; + var marginProp = "margin-bottom"; + var siblingMarginProp = "margin-top"; + } + + var elementMargin = dojo.html.getPixelValue(element, marginProp, false); + + function isSignificantNode(element) { + // see if an node is significant in the current context + // for calulating margins + return !(element.nodeType==3 && dojo.string.isBlank(element.data)) + && dojo.html.getStyle(element, "display") != "none" + && !dojo.html.isPositionAbsolute(element); + } + + // walk throuh first/last children to find total collapsed margin size + var childMargin = 0; + var child = element[childAttr]; + while (child) { + // skip over insignificant elements (whitespace, etc) + while ((!isSignificantNode(child)) && child[childSiblingAttr]) { + child = child[childSiblingAttr]; + } + + childMargin = Math.max(childMargin, dojo.html.getPixelValue(child, marginProp, false)); + // stop if we hit a bordered/padded element + if (!this._hasCollapseableMargin(child, topOrBottom)) break; + child = child[childAttr]; + } + + // if this element has a border, return full child margin immediately + // as there won't be any margin collapsing + if (!this._hasCollapseableMargin(element, topOrBottom)){ return parseInt(childMargin); } + + // find margin supplied by nearest sibling + var contextMargin = 0; + var sibling = element[siblingAttr]; + while (sibling) { + if (isSignificantNode(sibling)) { + contextMargin = dojo.html.getPixelValue(sibling, + siblingMarginProp, + false); + break; + } + sibling = sibling[siblingAttr]; + } + if (!sibling) { // no sibling, look at parent's margin instead + contextMargin = dojo.html.getPixelValue(element.parentNode, + marginProp, false); + } + + if (childMargin > elementMargin) { + return parseInt(Math.max((childMargin-elementMargin)-contextMargin, 0)); + } else { + return 0; + } + + }, + + _drawIframe: function (/*String*/html){ + // summary: + // Draws an iFrame using the existing one if one exists. + // Used by Mozilla, Safari, and Opera + + // detect firefox < 1.5, which has some iframe loading issues + var oldMoz = Boolean(dojo.render.html.moz && ( + typeof window.XML == 'undefined')) + + if(!this.iframe){ + var currentDomain = (new dojo.uri.Uri(dojo.doc().location)).host; + this.iframe = dojo.doc().createElement("iframe"); + // dojo.body().appendChild(this.iframe); + with(this.iframe){ + style.border = "none"; + style.lineHeight = "0"; // squash line height + style.verticalAlign = "bottom"; + scrolling = this.height ? "auto" : "no"; + } + } + // opera likes this to be outside the with block + this.iframe.src = dojo.uri.dojoUri("src/widget/templates/richtextframe.html") + ((dojo.doc().domain != currentDomain) ? ("#"+dojo.doc().domain) : ""); + this.iframe.width = this.inheritWidth ? this._oldWidth : "100%"; + if(this.height){ + this.iframe.style.height = this.height; + }else{ + var height = this._oldHeight; + if(this._hasCollapseableMargin(this.domNode, 'top')){ + height += this._firstChildContributingMargin; + } + if(this._hasCollapseableMargin(this.domNode, 'bottom')){ + height += this._lastChildContributingMargin; + } + this.iframe.height = height; + } + + var tmpContent = dojo.doc().createElement('div'); + tmpContent.innerHTML = html; + //append tmpContent to under the current domNode so that the margin + //calculation below is correct + this.editingArea.appendChild(tmpContent); + + // make relative image urls absolute + if(this.relativeImageUrls){ + var imgs = tmpContent.getElementsByTagName('img'); + for(var i=0; i<imgs.length; i++){ + imgs[i].src = (new dojo.uri.Uri(dojo.global().location, imgs[i].src)).toString(); + } + html = tmpContent.innerHTML; + } + + // fix margins on tmpContent + var firstChild = dojo.html.firstElement(tmpContent); + var lastChild = dojo.html.lastElement(tmpContent); + if(firstChild){ + firstChild.style.marginTop = this._firstChildContributingMargin+"px"; + } + if(lastChild){ + lastChild.style.marginBottom = this._lastChildContributingMargin+"px"; + } + //do we want to show the content before the editing area finish loading here? + //if external style sheets are used for the editing area, the appearance now + //and after loading of the editing area won't be the same (and padding/margin + //calculation above may not be accurate) +// tmpContent.style.display = "none"; + this.editingArea.appendChild(this.iframe); + if(dojo.render.html.safari){ + this.iframe.src = this.iframe.src; + } + + var _iframeInitialized = false; + + // now we wait for onload. Janky hack! + var ifrFunc = dojo.lang.hitch(this, function(){ + if(!_iframeInitialized){ + _iframeInitialized = true; + }else{ return; } + if(!this.editNode){ + if(this.iframe.contentWindow){ + this.window = this.iframe.contentWindow; + this.document = this.iframe.contentWindow.document + }else if(this.iframe.contentDocument){ + // for opera + this.window = this.iframe.contentDocument.window; + this.document = this.iframe.contentDocument; + } + + // curry the getStyle function + var getStyle = (function (domNode) { return function (style) { + return dojo.html.getStyle(domNode, style); + }; })(this.domNode); + + var font = + getStyle('font-weight') + " " + + getStyle('font-size') + " " + + getStyle('font-family'); + + // line height is tricky - applying a units value will mess things up. + // if we can't get a non-units value, bail out. + var lineHeight = "1.0"; + var lineHeightStyle = dojo.html.getUnitValue(this.domNode, 'line-height'); + if (lineHeightStyle.value && lineHeightStyle.units=="") { + lineHeight = lineHeightStyle.value; + } + + dojo.html.insertCssText( + 'body,html{background:transparent;padding:0;margin:0;}' + + // TODO: left positioning will case contents to disappear out of view + // if it gets too wide for the visible area + 'body{top:0;left:0;right:0;' + + (((this.height)||(dojo.render.html.opera)) ? '' : 'position:fixed;') + + 'font:' + font + ';' + + 'min-height:' + this.minHeight + ';' + + 'line-height:' + lineHeight + '}' + + 'p{margin: 1em 0 !important;}' + + 'body > *:first-child{padding-top:0 !important;margin-top:' + this._firstChildContributingMargin + 'px !important;}' + // FIXME: test firstChild nodeType + 'body > *:last-child{padding-bottom:0 !important;margin-bottom:' + this._lastChildContributingMargin + 'px !important;}' + + 'li > ul:-moz-first-node, li > ol:-moz-first-node{padding-top:1.2em;}\n' + + 'li{min-height:1.2em;}' + + //' p,ul,li { padding-top: 0; padding-bottom: 0; margin-top:0; margin-bottom: 0; }\n' + + '', this.document); + + dojo.html.removeNode(tmpContent); + this.document.body.innerHTML = html; + if(oldMoz||dojo.render.html.safari){ + this.document.designMode = "on"; + } + this.onLoad(); + }else{ + dojo.html.removeNode(tmpContent); + this.editNode.innerHTML = html; + this.onDisplayChanged(); + } + }); + + if(this.editNode){ + ifrFunc(); // iframe already exists, just set content + }else if(dojo.render.html.moz){ + // FIXME: if we put this on a delay, we get a height of 20px. + // Otherwise we get the correctly specified minHeight value. + this.iframe.onload = function(){ + setTimeout(ifrFunc, 250); + } + }else{ // new mozillas, opera, safari + this.iframe.onload = ifrFunc; + } + }, + + _applyEditingAreaStyleSheets: function(){ + // summary: + // apply the specified css files in styleSheets + var files = []; + if(this.styleSheets){ + files = this.styleSheets.split(';'); + this.styleSheets = ''; + } + + //empty this.editingAreaStyleSheets here, as it will be filled in addStyleSheet + files = files.concat(this.editingAreaStyleSheets); + this.editingAreaStyleSheets = []; + + if(files.length>0){ + for(var i=0;i<files.length;i++){ + var url = files[i]; + if(url){ + this.addStyleSheet(dojo.uri.dojoUri(url)); + } + } + } + }, + + addStyleSheet: function(/*dojo.uri.Uri*/uri) { + // summary: + // add an external stylesheet for the editing area + // uri: a dojo.uri.Uri pointing to the url of the external css file + var url=uri.toString(); + if(dojo.lang.find(this.editingAreaStyleSheets, url) > -1){ + dojo.debug("dojo.widget.RichText.addStyleSheet: Style sheet "+url+" is already applied to the editing area!"); + return; + } + + //if uri is relative, then convert it to absolute so that it can be resolved correctly in iframe + if(url.charAt(0) == '.' || (url.charAt(0) != '/' && !uri.host)){ + url = (new dojo.uri.Uri(dojo.global().location, url)).toString(); + } + + this.editingAreaStyleSheets.push(url); + if(this.document.createStyleSheet){ //IE + this.document.createStyleSheet(url); + }else{ //other browser + var head = this.document.getElementsByTagName("head")[0]; + var stylesheet = this.document.createElement("link"); + with(stylesheet){ + rel="stylesheet"; + type="text/css"; + href=url; + } + head.appendChild(stylesheet); + } + }, + + removeStyleSheet: function (/*dojo.uri.Uri*/uri) { + // summary: + // remove an external stylesheet for the editing area + var url=uri.toString(); + //if uri is relative, then convert it to absolute so that it can be resolved correctly in iframe + if(url.charAt(0) == '.' || (url.charAt(0) != '/' && !uri.host)){ + url = (new dojo.uri.Uri(dojo.global().location, url)).toString(); + } + var index = dojo.lang.find(this.editingAreaStyleSheets, url); + if(index == -1){ + dojo.debug("dojo.widget.RichText.removeStyleSheet: Style sheet "+url+" is not applied to the editing area so it can not be removed!"); + return; + } + delete this.editingAreaStyleSheets[index]; + + var links = this.document.getElementsByTagName("link"); + for(var i=0;i<links.length;i++){ + if(links[i].href == url){ + if(dojo.render.html.ie){//we need to empty the href first, to get IE to remove the rendered styles + links[i].href=""; + } + dojo.html.removeNode(links[i]); + break; + } + } + }, + + _drawObject: function (/*String*/html) { + // summary: + // Draws an active x object, used by IE + this.object = dojo.html.createExternalElement(dojo.doc(), "object"); + + with (this.object) { + classid = "clsid:2D360201-FFF5-11D1-8D03-00A0C959BC0A"; + width = this.inheritWidth ? this._oldWidth : "100%"; + style.height = this.height ? this.height : (this._oldHeight+"px"); + Scrollbars = this.height ? true : false; + Appearance = this._activeX.appearance.flat; + } + this.editorObject = this.object; + this.editingArea.appendChild(this.object); + + this.object.attachEvent("DocumentComplete", dojo.lang.hitch(this, "onLoad")); + //DisplayChanged is fired too often even no change is made, so we ignore it + //and call onDisplayChanged manually in execCommand instead +// this.object.attachEvent("DisplayChanged", dojo.lang.hitch(this, "onDisplayChanged")); + + dojo.lang.forEach(this.events, function(e){ + this.object.attachEvent(e.toLowerCase(), dojo.lang.hitch(this, e)); + }, this); + + this.object.DocumentHTML = '<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">' + + '<html><title></title>' + + '<style type="text/css">' + + ' body,html { padding: 0; margin: 0; }' + //font: ' + font + '; }' + + (this.height ? '' : ' body, { overflow: hidden; }') + + '</style>' + + //'<base href="' + dojo.global().location + '">' + + '<body><div>' + html + '<div></body></html>'; + + this._cacheLocalBlockFormatNames(); + }, + + //static cache variables shared among all instance of this class + _local2NativeFormatNames: {}, + _native2LocalFormatNames: {}, + //in IE, names for blockformat is locale dependent, so we cache the values here + //we use activeX to obtain the list, if success or the names are already cached, + //return true + _cacheLocalBlockFormatNames: function(){ + // summary: + // in IE, names for blockformat is locale dependent, so we cache the values here + // we use activeX to obtain the list, if success or the names are already cached, + // return true + if(!this._native2LocalFormatNames['p']){ + var obj = this.object; + var error = false; + if(!obj){ + //create obj temporarily + try{ + obj = dojo.html.createExternalElement(dojo.doc(), "object"); + obj.classid = "clsid:2D360201-FFF5-11D1-8D03-00A0C959BC0A"; + dojo.body().appendChild(obj); + obj.DocumentHTML = "<html><head></head><body></body></html>"; + }catch(e){ error = true; } + } + try{ + var oNamesParm = new ActiveXObject("DEGetBlockFmtNamesParam.DEGetBlockFmtNamesParam"); + obj.ExecCommand(this._activeX.command['getblockformatnames'], 0, oNamesParm); + var vbNamesArray = new VBArray(oNamesParm.Names); + var localFormats = vbNamesArray.toArray(); + var nativeFormats = ['p', 'pre', 'address', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'ul', '', '', '','','div']; + for(var i=0;i<nativeFormats.length;++i){ + if(nativeFormats[i].length>0){ + this._local2NativeFormatNames[localFormats[i]] = nativeFormats[i]; + this._native2LocalFormatNames[nativeFormats[i]] = localFormats[i]; + } + } + }catch(e){ error = true; } + if(obj && !this.object){ + //delete the temporary obj + dojo.body().removeChild(obj); + } + } + return !error; + }, + /* Event handlers + *****************/ + + _isResized: function(){ return false; }, + + onLoad: function(e){ + // summary: handler after the content of the document finishes loading + this.isLoaded = true; + if (this.object){ + this.document = this.object.DOM; + this.window = this.document.parentWindow; + this.editNode = this.document.body.firstChild; + this.editingArea.style.height = this.height ? this.height : this.minHeight; + if(!this.height){ + this.connect(this, "onDisplayChanged", "_updateHeight"); + } + //pretend the object as an iframe, so that the context menu for the + //editor can be placed correctly when shown + this.window._frameElement = this.object; + }else if (this.iframe && !dojo.render.html.ie){ + this.editNode = this.document.body; + if(!this.height){ + this.connect(this, "onDisplayChanged", "_updateHeight"); + } + + try { // sanity check for Mozilla + 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){ } + + if (dojo.render.html.safari) { + /* + this.iframe.style.visiblity = "visible"; + this.iframe.style.border = "1px solid black"; + this.editNode.style.visiblity = "visible"; + this.editNode.style.border = "1px solid black"; + */ + // this.onDisplayChanged(); + this.connect(this.editNode, "onblur", "onBlur"); + this.connect(this.editNode, "onfocus", "onFocus"); + this.connect(this.editNode, "onclick", "onFocus"); + + this.interval = setInterval(dojo.lang.hitch(this, "onDisplayChanged"), 750); + // dojo.raise("onload"); + // dojo.debug(this.editNode.parentNode.parentNode.parentNode.nodeName); + } else if (dojo.render.html.mozilla || dojo.render.html.opera) { + var doc = this.document; + var addListener = dojo.event.browser.addListener; + var self = this; + dojo.lang.forEach(this.events, function(e){ + var l = addListener(self.document, e.substr(2).toLowerCase(), dojo.lang.hitch(self, e)); + if(e=="onBlur"){ + // We need to unhook the blur event listener on close as we + // can encounter a garunteed crash in FF if another event is + // also fired + var unBlur = { unBlur: function(e){ + dojo.event.browser.removeListener(doc, "blur", l); + } }; + dojo.event.connect("before", self, "close", unBlur, "unBlur"); + } + }); + } + // FIXME: when scrollbars appear/disappear this needs to be fired + }else if(dojo.render.html.ie){ + // IE contentEditable + if(!this.height){ + this.connect(this, "onDisplayChanged", "_updateHeight"); + } + this.editNode.style.zoom = 1.0; + } + + this._applyEditingAreaStyleSheets(); + + if(this.focusOnLoad){ + this.focus(); + } + this.onDisplayChanged(e); + if(this.onLoadDeferred){ + this.onLoadDeferred.callback(true); + } + }, + + onKeyDown: function(e){ + // summary: Fired on keydown + if((!e)&&(this.object)){ + e = dojo.event.browser.fixEvent(this.window.event); + } + // dojo.debug("onkeydown:", e.keyCode); + // 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.render.html.ie)&&(e.keyCode == e.KEY_TAB)){ + e.preventDefault(); + e.stopPropagation(); + // FIXME: this is a poor-man's indent/outdent. It would be + // better if it added 4 " " chars in an undoable way. + // Unfortuantly pasteHTML does not prove to be undoable + this.execCommand((e.shiftKey ? "outdent" : "indent")); + }else if(dojo.render.html.ie){ + if((65 <= e.keyCode)&&(e.keyCode <= 90)){ + e.charCode = e.keyCode; + this.onKeyPress(e); + } + // dojo.debug(e.ctrlKey); + // dojo.debug(e.keyCode); + // dojo.debug(e.charCode); + // this.onKeyPress(e); + } + }, + + onKeyUp: function(e){ + // summary: Fired on keyup + return; + }, + + KEY_CTRL: 1, + + onKeyPress: function(e){ + // summary: Fired on keypress + if((!e)&&(this.object)){ + e = dojo.event.browser.fixEvent(this.window.event); + } + // handle the various key events + + var modifiers = e.ctrlKey ? this.KEY_CTRL : 0; + + if (this._keyHandlers[e.key]) { + // dojo.debug("char:", e.key); + var handlers = this._keyHandlers[e.key], i = 0, handler; + while (handler = handlers[i++]) { + if (modifiers == handler.modifiers) { + e.preventDefault(); + handler.handler.call(this); + break; + } + } + } + + // function call after the character has been inserted + dojo.lang.setTimeout(this, this.onKeyPressed, 1, e); + }, + + addKeyHandler: function (/*String*/key, /*Int*/modifiers, /*Function*/handler) { + // summary: add a handler for a keyboard shortcut + if (!(this._keyHandlers[key] instanceof Array)) { this._keyHandlers[key] = []; } + this._keyHandlers[key].push({ + modifiers: modifiers || 0, + handler: handler + }); + }, + + onKeyPressed: function (e) { + // summary: + // Fired after a keypress event has occured and it's action taken. This + // is useful if action needs to be taken after text operations have finished + + // Mozilla adds a single <p> with an embedded <br> when you hit enter once: + // <p><br>\n</p> + // when you hit enter again it adds another <br> inside your enter + // <p><br>\n<br>\n</p> + // and if you hit enter again it splits the <br>s over 2 <p>s + // <p><br>\n</p>\n<p><br>\n</p> + // now this assumes that <p>s have double the line-height of <br>s to work + // and so we need to remove the <p>s to ensure the position of the cursor + // changes from the users perspective when they hit enter, as the second two + // html snippets render the same when margins are set to 0. + + // TODO: doesn't really work; is this really needed? + //if (dojo.render.html.moz) { + // for (var i = 0; i < this.document.getElementsByTagName("p").length; i++) { + // var p = this.document.getElementsByTagName("p")[i]; + // if (p.innerHTML.match(/^<br>\s$/m)) { + // while (p.hasChildNodes()) { p.parentNode.insertBefore(p.firstChild, p); } + // p.parentNode.removeChild(p); + // } + // } + //} + this.onDisplayChanged(/*e*/); // can't pass in e + }, + + onClick: function(e){ this.onDisplayChanged(e); }, + onBlur: function(e){ }, + _initialFocus: true, + onFocus: function(e){ + // summary: Fired on focus + if( (dojo.render.html.mozilla)&&(this._initialFocus) ){ + this._initialFocus = false; + if(dojo.string.trim(this.editNode.innerHTML) == " "){ + this.placeCursorAtStart(); +// this.execCommand("selectall"); +// this.window.getSelection().collapseToStart(); + } + } + /* + // FIXME: attempted (but ultimately unworkable) solution for #2066 + if(dojo.render.html.safari){ + var sel = this.window.getSelection(); + dojo.debug(dojo.dom.isDescendantOf(sel.focusNode, this.document.body)); + + var isLastNode = (sel.focusNode == this.document.body.lastChild); + if(!isLastNode){ return; } + var isText = (sel.focusNode.nodeType == 3); + if(!isText){ return; } + var isLastChar = (sel.focusOffset == sel.focusNode.nodeValue.length); + if(!isLastChar){ return; } + dojo.debug(sel.focusOffset, sel.focusNode.nodeValue.length); + if(isLastNode && isText && isLastChar){ + this.document.body.appendChild(this.document.createTextNode(" ")); + this.document.body.appendChild(this.document.createElement("br")); + this.document.body.appendChild(this.document.createElement("p")); + this.document.body.appendChild(this.document.createElement("p")); + sel.collapse(sel.focusNode, sel.focusOffset-1); + sel.collapse(sel.focusNode, sel.focusOffset+1); + } + } + */ + }, + + blur: function () { + // summary: remove focus from this instance + if(this.iframe) { this.window.blur(); } + else if(this.object) { this.document.body.blur(); } + else if(this.editNode) { this.editNode.blur(); } + }, + + focus: function () { + // summary: move focus to this instance + if(this.iframe && !dojo.render.html.ie) { this.window.focus(); } + else if(this.object) { this.document.focus(); } + // editNode may be hidden in display:none div, lets just punt in this case + else if(this.editNode && this.editNode.focus) { this.editNode.focus(); } + else{ + dojo.debug("Have no idea how to focus into the editor!"); + } + }, + + /** this event will be fired everytime the display context changes and the + result needs to be reflected in the UI */ + onDisplayChanged: function (e){ }, + + + /* Formatting commands + **********************/ + + // Object: IE's Active X codes: see http://www.computerbytesman.com/js/activex/dhtmledit.htm + _activeX: { + command: { + bold: 5000, + italic: 5023, + underline: 5048, + + justifycenter: 5024, + justifyleft: 5025, + justifyright: 5026, + + cut: 5003, + copy: 5002, + paste: 5032, + "delete": 5004, + + undo: 5049, + redo: 5033, + + removeformat: 5034, + selectall: 5035, + unlink: 5050, + + indent: 5018, + outdent: 5031, + + insertorderedlist: 5030, + insertunorderedlist: 5051, + + // table commands + inserttable: 5022, + insertcell: 5019, + insertcol: 5020, + insertrow: 5021, + deletecells: 5005, + deletecols: 5006, + deleterows: 5007, + mergecells: 5029, + splitcell: 5047, + + // the command need mapping, they don't translate directly + // to the contentEditable commands + setblockformat: 5043, + getblockformat: 5011, + getblockformatnames: 5012, + setfontname: 5044, + getfontname: 5013, + setfontsize: 5045, + getfontsize: 5014, + setbackcolor: 5042, + getbackcolor: 5010, + setforecolor: 5046, + getforecolor: 5015, + + findtext: 5008, + font: 5009, + hyperlink: 5016, + image: 5017, + + lockelement: 5027, + makeabsolute: 5028, + sendbackward: 5036, + bringforward: 5037, + sendbelowtext: 5038, + bringabovetext: 5039, + sendtoback: 5040, + bringtofront: 5041, + + properties: 5052 + }, + + ui: { + "default": 0, + prompt: 1, + noprompt: 2 + }, + + status: { + notsupported: 0, + disabled: 1, + enabled: 3, + latched: 7, + ninched: 11 + }, + + appearance: { + flat: 0, + inset: 1 + }, + + state: { + unchecked: 0, + checked: 1, + gray: 2 + } + }, + + _normalizeCommand: function (/*String*/cmd){ + // summary: + // Used as the advice function by dojo.event.connect to map our + // normalized set of commands to those supported by the target + // browser + var drh = dojo.render.html; + + var command = cmd.toLowerCase(); + if(command == "formatblock"){ + if(drh.safari){ command = "heading"; } + }else if(this.object){ + switch(command){ + case "createlink": + command = "hyperlink"; + break; + case "insertimage": + command = "image"; + break; + } + }else if(command == "hilitecolor" && !drh.mozilla){ + command = "backcolor"; + } + + return command; + }, + + _safariIsLeopard: function(){ + var gt420 = false; + if(dojo.render.html.safari){ + var tmp = dojo.render.html.UA.split("AppleWebKit/")[1]; + var ver = parseFloat(tmp.split(" ")[0]); + if(ver >= 420){ gt420 = true; } + } + return gt420; + }, + + 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 = this._safariIsLeopard(); + + 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": + 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 | (this.object ? ie : 0)); + break; + + case "insertcell": case "insertcol": case "insertrow": + case "deletecells": case "deletecols": case "deleterows": + case "mergecells": case "splitcell": + supportedBy = isSupportedBy(this.object ? ie : 0); + break; + + default: return false; + } + + return (dojo.render.html.ie && supportedBy.ie) || + (dojo.render.html.mozilla && supportedBy.mozilla) || + (dojo.render.html.safari && supportedBy.safari) || + (gt420 && supportedBy.safari420) || + (dojo.render.html.opera && 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 (none-activeX mode) 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"){ + if(this.object){ //IE activeX mode + argument = this._native2LocalFormatNames[argument]; + } + else if(dojo.render.html.ie){ argument = '<'+argument+'>'; } + } + } + if(this.object){ + switch (command) { + case "hilitecolor": + command = "setbackcolor"; + break; + case "forecolor": + case "backcolor": + case "fontsize": + case "fontname": + command = "set" + command; + break; + case "formatblock": + command = "setblockformat"; + } + + if(command == "strikethrough"){ + command = "inserthtml"; + var range = this.document.selection.createRange(); + if(!range.htmlText){ + return; + } + argument=range.htmlText.strike(); + }else if(command == "inserthorizontalrule"){ + command = "inserthtml"; + argument="<hr>"; + } + + if(command == "inserthtml"){ + var range = this.document.selection.createRange(); + if(this.document.selection.type.toUpperCase() == "CONTROL"){ + //if selection is controlrange, no pasteHTML is available, + //we replace the outerHTML directly + for(var i=0;i<range.length;i++){ + range.item(i).outerHTML = argument; + } + }else{ + // on IE, we can use the pasteHTML method of the textRange object + // to get an undo-able innerHTML modification + range.pasteHTML(argument); + range.select(); + } + returnValue = true; + }else if(arguments.length == 1){ + returnValue = this.object.ExecCommand(this._activeX.command[command], + this._activeX.ui.noprompt); + }else{ + returnValue = this.object.ExecCommand(this._activeX.command[command], + this._activeX.ui.noprompt, argument); + } + }else if(command == "inserthtml"){ + if(dojo.render.html.ie){ + //dojo.debug("inserthtml breaks the undo stack when not using the ActiveX version of the control!"); + var insertRange = this.document.selection.createRange(); + insertRange.pasteHTML(argument); + insertRange.select(); + //insertRange.collapse(true); + return true; + }else{ + return this.document.execCommand(command, false, argument); + } + /* */ + // fix up unlink in Mozilla to unlink the link and not just the selection + }else if((command == "unlink")&& + (this.queryCommandEnabled("unlink"))&& + (dojo.render.html.mozilla)){ + // 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", dojo.html.selection, ['a']); + dojo.withGlobal(this.window, "selectElement", dojo.html.selection, [a]); + + returnValue = this.document.execCommand("unlink", false, null); + + // restore original selection + var selectionRange = this.document.createRange(); + selectionRange.setStart(selectionStartContainer, selectionStartOffset); + selectionRange.setEnd(selectionEndContainer, selectionEndOffset); + selection.removeAllRanges(); + selection.addRange(selectionRange); + + return returnValue; + }else if((command == "hilitecolor")&&(dojo.render.html.mozilla)){ + // mozilla doesn't support hilitecolor properly when useCSS is + // set to false (bugzilla #279330) + + this.document.execCommand("useCSS", false, false); + returnValue = this.document.execCommand(command, false, argument); + this.document.execCommand("useCSS", false, true); + + }else if((dojo.render.html.ie)&&( (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{ + // dojo.debug("command:", command, "arg:", argument); + + argument = arguments.length > 1 ? argument : null; +// if(dojo.render.html.moz){ +// 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 + command = this._normalizeCommand(command); + if(this.object){ + switch (command) { + case "hilitecolor": + command = "setbackcolor"; + break; + case "forecolor": + case "backcolor": + case "fontsize": + case "fontname": + command = "set" + command; + break; + case "formatblock": + command = "setblockformat"; + break; + //below are not natively supported commands, we fake them + case "strikethrough": + command = "bold"; //whenever bold is enabled, strikethrough should be so as well + break; + case "inserthorizontalrule": + return true; + } + + if(typeof this._activeX.command[command] == "undefined"){ return false; } + var status = this.object.QueryStatus(this._activeX.command[command]); + return ((status != this._activeX.status.notsupported)&& + (status != this._activeX.status.disabled)); + }else{ + if(dojo.render.html.mozilla){ + if(command == "unlink"){ // mozilla returns true always + return dojo.withGlobal(this.window, "hasAncestorElement", dojo.html.selection, ['a']); + } else if (command == "inserttable") { + return true; + } + } + + // return this.document.queryCommandEnabled(command); + var elem = (dojo.render.html.ie) ? this.document.selection.createRange() : this.document; + return elem.queryCommandEnabled(command); + } + }, + + queryCommandState: function(command){ + // summary: check the state of a given command + command = this._normalizeCommand(command); + if(this.object){ + if(command == "forecolor"){ + command = "setforecolor"; + }else if(command == "backcolor"){ + command = "setbackcolor"; + }else if(command == "strikethrough"){ + //check whether we are under a <strike> + return dojo.withGlobal(this.window, "hasAncestorElement", dojo.html.selection, ['strike']); + }else if(command == "inserthorizontalrule"){ + return false; + } + + if(typeof this._activeX.command[command] == "undefined"){ return null; } + var status = this.object.QueryStatus(this._activeX.command[command]); + return ((status == this._activeX.status.latched)|| + (status == this._activeX.status.ninched)); + }else{ + return this.document.queryCommandState(command); + } + }, + + queryCommandValue: function (command) { + // summary: check the value of a given command + command = this._normalizeCommand(command); + if (this.object) { + switch (command) { + case "forecolor": + case "backcolor": + case "fontsize": + case "fontname": + command = "get" + command; + return this.object.execCommand( + this._activeX.command[command], + this._activeX.ui.noprompt); + case "formatblock": + var retvalue = this.object.execCommand( + this._activeX.command["getblockformat"], + this._activeX.ui.noprompt); + if(retvalue){ + return this._local2NativeFormatNames[retvalue]; + } + } + } else { + if(dojo.render.html.ie && command == "formatblock"){ + return this._local2NativeFormatNames[this.document.queryCommandValue(command)] || 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 + if(dojo.render.html.moz && this.editNode.firstChild && + this.editNode.firstChild.nodeType != dojo.dom.TEXT_NODE){ + dojo.withGlobal(this.window, "selectElementChildren", dojo.html.selection, [this.editNode.firstChild]); + }else{ + dojo.withGlobal(this.window, "selectElementChildren", dojo.html.selection, [this.editNode]); + } + dojo.withGlobal(this.window, "collapse", dojo.html.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 + if(dojo.render.html.moz && this.editNode.lastChild && + this.editNode.lastChild.nodeType != dojo.dom.TEXT_NODE){ + dojo.withGlobal(this.window, "selectElementChildren", dojo.html.selection, [this.editNode.lastChild]); + }else{ + dojo.withGlobal(this.window, "selectElementChildren", dojo.html.selection, [this.editNode]); + } + dojo.withGlobal(this.window, "collapse", dojo.html.selection, [false]); + }, + + replaceEditorContent: function(/*String*/html){ + // summary: + // this function set the content while trying to maintain the undo stack + html = this._preFilterContent(html); + if(this.isClosed){ + this.domNode.innerHTML = html; + }else if(this.window && this.window.getSelection && !dojo.render.html.moz){ // Safari + // look ma! it's a totally f'd browser! + this.editNode.innerHTML = html; + }else if((this.window && this.window.getSelection) || (this.document && this.document.selection)){ // Moz/IE + this.execCommand("selectall"); + if(dojo.render.html.moz && !html){ html = " " } + this.execCommand("inserthtml", html); + } + }, + + _preFilterContent: function(/*String*/html){ + // summary: + // filter the input before setting the content of the editing area + var ec = html; + dojo.lang.forEach(this.contentPreFilters, function(ef){ + ec = ef(ec); + }); + if(this.contentDomPreFilters.length>0){ + var dom = dojo.doc().createElement('div'); + dom.style.display = "none"; + dojo.body().appendChild(dom); + dom.innerHTML = ec; + dojo.lang.forEach(this.contentDomPreFilters, function(ef){ + dom = ef(dom); + }); + ec = dom.innerHTML; + dojo.body().removeChild(dom); + } + return ec; + }, + _postFilterContent: function(/*String*/html){ + // summary: + // filter the output after getting the content of the editing area + var ec = html; + if(this.contentDomPostFilters.length>0){ + var dom = this.document.createElement('div'); + dom.innerHTML = ec; + dojo.lang.forEach(this.contentDomPostFilters, function(ef){ + dom = ef(dom); + }); + ec = dom.innerHTML; + } + dojo.lang.forEach(this.contentPostFilters, function(ef){ + ec = ef(ec); + }); + return ec; + }, + + //Int: stored last time height + _lastHeight: 0, + + _updateHeight: function(){ + // summary: + // Updates the height of the editor area to fit the contents. + if(!this.isLoaded){ return; } + if(this.height){ return; } + + var height = dojo.html.getBorderBox(this.editNode).height; + //height maybe zero in some cases even though the content is not empty, + //we try the height of body instead + if(!height){ + height = dojo.html.getBorderBox(this.document.body).height; + } + if(height == 0){ + dojo.debug("Can not figure out the height of the editing area!"); + return; //prevent setting height to 0 + } + this._lastHeight = height; + this.editorObject.style.height = this._lastHeight + "px"; + this.window.scrollTo(0, 0); + }, + + _saveContent: function(e){ + // summary: + // Saves the content in an onunload event if the editor has not been closed + var saveTextarea = dojo.doc().getElementById("dojo.widget.RichText.savedContent"); + saveTextarea.value += this._SEPARATOR + this.saveName + ":" + this.getEditorContent(); + }, + + getEditorContent: function(){ + // summary: + // return the current content of the editing area (post filters are applied) + var ec = ""; + try{ + ec = (this._content.length > 0) ? this._content : this.editNode.innerHTML; + if(dojo.string.trim(ec) == " "){ ec = ""; } + }catch(e){ /* squelch */ } + + if(dojo.render.html.ie && !this.object){ + //removing appended <P> </P> for IE in none-activeX mode + var re = new RegExp("(?:<p> </p>[\n\r]*)+$", "i"); + ec = ec.replace(re,""); + } + + ec = this._postFilterContent(ec); + + if (this.relativeImageUrls) { + // why use a regexp instead of dom? because IE is stupid + // and won't let us set img.src to a relative URL + // this comes after contentPostFilters because once content + // gets innerHTML'd img urls will be fully qualified + var siteBase = dojo.global().location.protocol + "//" + dojo.global().location.host; + var pathBase = dojo.global().location.pathname; + if (pathBase.match(/\/$/)) { + // ends with slash, match full path + } else { + // match parent path to find siblings + var pathParts = pathBase.split("/"); + if (pathParts.length) { + pathParts.pop(); + } + pathBase = pathParts.join("/") + "/"; + + } + + var sameSite = new RegExp("(<img[^>]*\ src=[\"'])("+siteBase+"("+pathBase+")?)", "ig"); + ec = ec.replace(sameSite, "$1"); + } + return ec; + }, + + 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 == 0) { save = true; } + this._content = this._postFilterContent(this.editNode.innerHTML); + 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(dojo.render.html.ie && !this.object){ + dojo.event.browser.clean(this.editNode); + } + + if (this.iframe) { + // FIXME: should keep iframe around for later re-use + delete this.iframe; + } + + if(this.textarea){ + with(this.textarea.style){ + position = ""; + left = top = ""; + if(dojo.render.html.ie){ + overflow = this.__overflow; + this.__overflow = null; + } + } + if(save){ + this.textarea.value = this._content; + }else{ + this.textarea.value = this.savedContent; + } + dojo.html.removeNode(this.domNode); + this.domNode = this.textarea; + }else{ + if(save){ + if(dojo.render.html.moz){ + var nc = dojo.doc().createElement("span"); + this.domNode.appendChild(nc); + nc.innerHTML = this.editNode.innerHTML; + }else{ + this.domNode.innerHTML = this._content; + } + }else{ + this.domNode.innerHTML = this.savedContent; + } + } + + dojo.html.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._frameElement){ + this.window._frameElement = null; + } + + this.window = null; + this.document = null; + this.object = null; + this.editingArea = null; + this.editorObject = null; + + return changed; // Boolean: whether the content has been modified + }, + + destroyRendering: function(){}, // stub! + + destroy: function (){ + this.destroyRendering(); + if(!this.isClosed){ this.close(false); } + + dojo.widget.RichText.superclass.destroy.call(this); + }, + + connect: function (targetObj, targetFunc, thisFunc) { + // summary: convenient method for dojo.event.connect + dojo.event.connect(targetObj, targetFunc, this, thisFunc); + }, + + disconnect: function (targetObj, targetFunc, thisFunc) { + // summary: convenient method for dojo.event.disconnect + dojo.event.disconnect(targetObj, targetFunc, this, thisFunc); + }, + + disconnectAllWithRoot: function (targetObj) { + dojo.depre |
Free forum by Nabble | Edit this page |