/* 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 */ /* TODO: * - font selector * - test, bug fix, more features :) */ dojo.provide("dojo.widget.Editor2"); dojo.provide("dojo.widget.html.Editor2"); dojo.require("dojo.io.*"); dojo.require("dojo.widget.*"); dojo.require("dojo.widget.RichText"); dojo.require("dojo.widget.Editor2Toolbar"); // dojo.require("dojo.widget.ColorPalette"); // dojo.require("dojo.string.extras"); dojo.widget.defineWidget( "dojo.widget.html.Editor2", dojo.widget.html.RichText, { saveUrl: "", saveMethod: "post", saveArgName: "editorContent", closeOnSave: false, shareToolbar: false, toolbarAlwaysVisible: false, htmlEditing: false, _inHtmlMode: false, _htmlEditNode: null, commandList: dojo.widget.html.Editor2Toolbar.prototype.commandList, toolbarWidget: null, scrollInterval: null, editorOnLoad: function(){ var toolbars = dojo.widget.byType("Editor2Toolbar"); if((!toolbars.length)||(!this.shareToolbar)){ var tbOpts = {}; tbOpts.templatePath = dojo.uri.dojoUri("src/widget/templates/HtmlEditorToolbarOneline.html"); this.toolbarWidget = dojo.widget.createWidget("Editor2Toolbar", tbOpts, this.domNode, "before"); dojo.event.connect(this, "destroy", this.toolbarWidget, "destroy"); this.toolbarWidget.hideUnusableButtons(this); if(this.object){ this.tbBgIframe = new dojo.html.BackgroundIframe(this.toolbarWidget.domNode); this.tbBgIframe.iframe.style.height = "30px"; } // need to set position fixed to wherever this thing has landed if(this.toolbarAlwaysVisible){ var src = document["documentElement"]||window; this.scrollInterval = setInterval(dojo.lang.hitch(this, "globalOnScrollHandler"), 100); // dojo.event.connect(src, "onscroll", this, "globalOnScrollHandler"); dojo.event.connect("before", this, "destroyRendering", this, "unhookScroller"); } }else{ // FIXME: should we try harder to explicitly manage focus in // order to prevent too many editors from all querying // for button status concurrently? // FIXME: selecting in one shared toolbar doesn't clobber // selection in the others. This is problematic. this.toolbarWidget = toolbars[0]; } dojo.event.topic.registerPublisher("Editor2.clobberFocus", this.editNode, "onfocus"); // dojo.event.topic.registerPublisher("Editor2.clobberFocus", this.editNode, "onclick"); dojo.event.topic.subscribe("Editor2.clobberFocus", this, "setBlur"); dojo.event.connect(this.editNode, "onfocus", this, "setFocus"); dojo.event.connect(this.toolbarWidget.linkButton, "onclick", dojo.lang.hitch(this, function(){ var range; if(this.document.selection){ range = this.document.selection.createRange().text; }else if(dojo.render.html.mozilla){ range = this.window.getSelection().toString(); } if(range.length){ this.toolbarWidget.exec("createlink", prompt("Please enter the URL of the link:", "http://")); }else{ alert("Please select text to link"); } }) ); var focusFunc = dojo.lang.hitch(this, function(){ if(dojo.render.html.ie){ this.editNode.focus(); }else{ this.window.focus(); } }); dojo.event.connect(this.toolbarWidget, "formatSelectClick", focusFunc); dojo.event.connect(this, "execCommand", focusFunc); if(this.htmlEditing){ var tb = this.toolbarWidget.htmltoggleButton; if(tb){ tb.style.display = ""; dojo.event.connect(this.toolbarWidget, "htmltoggleClick", this, "toggleHtmlEditing"); } } }, toggleHtmlEditing: function(){ if(!this._inHtmlMode){ this._inHtmlMode = true; this.toolbarWidget.highlightButton("htmltoggle"); if(!this._htmlEditNode){ this._htmlEditNode = document.createElement("textarea"); dojo.html.insertBefore(this._htmlEditNode, this.domNode); } this._htmlEditNode.style.display = ""; this._htmlEditNode.style.width = "100%"; this._htmlEditNode.style.height = dojo.style.getInnerHeight(this.editNode)+"px"; this._htmlEditNode.value = this.editNode.innerHTML; this.domNode.style.display = "none"; }else{ this._inHtmlMode = false; this.domNode.style.display = ""; this.toolbarWidget.unhighlightButton("htmltoggle"); dojo.lang.setTimeout(this, "replaceEditorContent", 1, this._htmlEditNode.value); this._htmlEditNode.style.display = "none"; this.editNode.focus(); } }, setFocus: function(){ // dojo.debug("setFocus:", this); dojo.event.connect(this.toolbarWidget, "exec", this, "execCommand"); }, setBlur: function(){ // dojo.debug("setBlur:", this); dojo.event.disconnect(this.toolbarWidget, "exec", this, "execCommand"); }, _scrollSetUp: false, _fixEnabled: false, _scrollThreshold: false, _handleScroll: true, globalOnScrollHandler: function(){ var isIE = dojo.render.html.ie; if(!this._handleScroll){ return; } var ds = dojo.style; var tdn = this.toolbarWidget.domNode; var db = document["body"]; var totalHeight = ds.getOuterHeight(tdn); if(!this._scrollSetUp){ this._scrollSetUp = true; var editorWidth = ds.getOuterWidth(this.domNode); this._scrollThreshold = ds.abs(tdn, false).y; // dojo.debug("threshold:", this._scrollThreshold); if((isIE)&&(db)&&(ds.getStyle(db, "background-image")=="none")){ with(db.style){ backgroundImage = "url(" + dojo.uri.dojoUri("src/widget/templates/images/blank.gif") + ")"; backgroundAttachment = "fixed"; } } } var scrollPos = (window["pageYOffset"]) ? window["pageYOffset"] : (document["documentElement"]||document["body"]).scrollTop; // FIXME: need to have top and bottom thresholds so toolbar doesn't keep scrolling past the bottom if(scrollPos > this._scrollThreshold){ // dojo.debug(scrollPos); if(!this._fixEnabled){ this.domNode.style.marginTop = totalHeight+"px"; if(isIE){ // FIXME: should we just use setBehvior() here instead? var cl = dojo.style.abs(tdn).x; document.body.appendChild(tdn); tdn.style.left = cl+dojo.style.getPixelValue(document.body, "margin-left")+"px"; dojo.html.addClass(tdn, "IEFixedToolbar"); if(this.object){ dojo.html.addClass(this.tbBgIframe, "IEFixedToolbar"); } }else{ with(tdn.style){ position = "fixed"; top = "0px"; } } tdn.style.zIndex = 1000; this._fixEnabled = true; } // if we're showing the floating toolbar, make sure that if // we've scrolled past the bottom of the editor that we hide // the toolbar for this instance of the editor. // TODO: when we get multiple editor toolbar support working // correctly, ensure that we check this against the scroll // position of the bottom-most editor instance. if(!dojo.render.html.safari){ // safari reports a bunch of things incorrectly here var eHeight = (this.height) ? parseInt(this.height) : ((this.object) ? dojo.style.getInnerHeight(this.editNode) : this._lastHeight); if(scrollPos > (this._scrollThreshold+eHeight)){ tdn.style.display = "none"; }else{ tdn.style.display = ""; } } }else if(this._fixEnabled){ this.domNode.style.marginTop = null; with(tdn.style){ position = ""; top = ""; zIndex = ""; if(isIE){ marginTop = ""; } } if(isIE){ dojo.html.removeClass(tdn, "IEFixedToolbar"); dojo.html.insertBefore(tdn, this._htmlEditNode||this.domNode); } this._fixEnabled = false; } }, unhookScroller: function(){ this._handleScroll = false; clearInterval(this.scrollInterval); // var src = document["documentElement"]||window; // dojo.event.disconnect(src, "onscroll", this, "globalOnScrollHandler"); if(dojo.render.html.ie){ dojo.html.removeClass(this.toolbarWidget.domNode, "IEFixedToolbar"); } }, _updateToolbarLastRan: null, _updateToolbarTimer: null, _updateToolbarFrequency: 500, updateToolbar: function(force){ if((!this.isLoaded)||(!this.toolbarWidget)){ return; } // keeps the toolbar from updating too frequently // TODO: generalize this functionality? var diff = new Date() - this._updateToolbarLastRan; if( (!force)&&(this._updateToolbarLastRan)&& ((diff < this._updateToolbarFrequency)) ){ clearTimeout(this._updateToolbarTimer); var _this = this; this._updateToolbarTimer = setTimeout(function() { _this.updateToolbar(); }, this._updateToolbarFrequency/2); return; }else{ this._updateToolbarLastRan = new Date(); } // end frequency checker dojo.lang.forEach(this.commandList, function(cmd){ if(cmd == "inserthtml"){ return; } try{ if(this.queryCommandEnabled(cmd)){ if(this.queryCommandState(cmd)){ this.toolbarWidget.highlightButton(cmd); }else{ this.toolbarWidget.unhighlightButton(cmd); } } }catch(e){ // alert(cmd+":"+e); } }, this); var h = dojo.render.html; // safari f's us for selection primitives if(h.safari){ return; } var selectedNode = (h.ie) ? this.document.selection.createRange().parentElement() : this.window.getSelection().anchorNode; // make sure we actuall have an element while((selectedNode)&&(selectedNode.nodeType != 1)){ selectedNode = selectedNode.parentNode; } if(!selectedNode){ return; } var formats = ["p", "pre", "h1", "h2", "h3", "h4"]; // gotta run some specialized updates for the various // formatting options var type = formats[dojo.lang.find(formats, selectedNode.nodeName.toLowerCase())]; while((selectedNode)&&(selectedNode!=this.editNode)&&(!type)){ selectedNode = selectedNode.parentNode; type = formats[dojo.lang.find(formats, selectedNode.nodeName.toLowerCase())]; } if(!type){ type = ""; }else{ if(type.charAt(0)=="h"){ this.toolbarWidget.unhighlightButton("bold"); } } this.toolbarWidget.selectFormat(type); }, updateItem: function(item) { try { var cmd = item._name; var enabled = this._richText.queryCommandEnabled(cmd); item.setEnabled(enabled, false, true); var active = this._richText.queryCommandState(cmd); if(active && cmd == "underline") { // don't activate underlining if we are on a link active = !this._richText.queryCommandEnabled("unlink"); } item.setSelected(active, false, true); return true; } catch(err) { return false; } }, _save: function(e){ // FIXME: how should this behave when there's a larger form in play? if(!this.isClosed){ if(this.saveUrl.length){ var content = {}; content[this.saveArgName] = this.getHtml(); dojo.io.bind({ method: this.saveMethod, url: this.saveUrl, content: content }); }else{ dojo.debug("please set a saveUrl for the editor"); } if(this.closeOnSave){ this.close(e.getName().toLowerCase() == "save"); } } }, wireUpOnLoad: function(){ if(!dojo.render.html.ie){ /* dojo.event.kwConnect({ srcObj: this.document, srcFunc: "click", targetObj: this.toolbarWidget, targetFunc: "hideAllDropDowns", once: true }); */ } } }, "html", function(){ var cp = dojo.widget.html.Editor2.prototype; if(!cp._wrappersSet){ cp._wrappersSet = true; cp.fillInTemplate = (function(fit){ return function(){ fit.call(this); this.editorOnLoad(); }; })(cp.fillInTemplate); cp.onDisplayChanged = (function(odc){ return function(){ try{ odc.call(this); this.updateToolbar(); }catch(e){} }; })(cp.onDisplayChanged); cp.onLoad = (function(ol){ return function(){ ol.call(this); this.wireUpOnLoad(); }; })(cp.onLoad); } } );