dojo.provide("dojox.widget.SpellCheck"); dojo.require("dojox.widget.MenuGenerator"); dojo.requireLocalization("dojox.widget", "SpellCheck"); dojo.declare( "dojox.widget.SpellCheck", [dijit._Widget], { // summary // A spellchecker for all king of textarea or richtexteditor constructor: function(){ this._contextMenu = {}; this._handles = []; this._nls = dojo.i18n.getLocalization(this.i18nModule, this.i18nBundle, this.lang); }, //i18nModule: String //Module for i18n i18nModule: "dojox.widget", //i18nBundle: String //Bundle for i18n i18nBundle: "SpellCheck", //aspell: String //file used (in xhr mode to) for calling aspell aspell: "spellcheck.php", //encoding: String //encoding used in communication, with aspell encoding: "utf-8", //language: String //language to use in aspell language: "fr", //leftClickToOpen: Boolean //false if you want to open contextual menu with right click leftClickToOpen: true, //incorrectWordStyle: String //style applyed on incorrect word //Why style ? because for class we need to declare some CSS file... to bad... incorrectWordStyle: "cursor:pointer;border-bottom:1px dotted red;background-color:yellow", //editor: Object //Specify wich editor instance use editor: null, //innerDocument: Object //document root in iframe editor widget innerDocument: null, //textarea: DomNode //textarea node for rich editor in simple mode textarea: null, //maxSuggest: Integer //Set the max suggestion available in contextual menu. //Set to 0 for unlimited contextual menu maxSuggest: 10, //contextualMenuCoordsCorrection: Object //properties are: //l:some px to add to left menu coords //t:some px to add to top menu coords contextualMenuCoordsCorrection: null, //_activeWord: DomNode //Store node wich is in correction _activeWord: null, //_contextMenu: Object //Contextual menu _contextMenu: null, //_handles: Object //Store all conexion _handles: null, //_useTextarea: Boolean //true when textarea is used instead of rich editor _useTextarea: false, //_divArea: DomNode //Div put behind the textarea _divArea: null, //isInProgress: Boolean //true when spellchecker is active isInProgress: false, //useXYPosition: Boolean //when true, add coords.x and coords.y to top and left of the textarea //needed sometime but why ? (maybe in position:relative elements ?) useXYPosition: false, //_canStop: Boolean //true when spellchecker can be stopped _canStop: false, _getSpellContent: function(){ // summary: return spell content (including special add for correction) if(this._useTextarea){ var reg = new RegExp("
","gi"); return this._divArea.innerHTML.replace(reg,"\n"); }else{ return this.getContent(); } }, _finalizeStop: function(){ // summary: Finalize the stop of spellcheck (if needed) if(this._useTextarea){ this.textarea.value = this._getSpellContent(); this.textarea.style.visibility = "visible"; dojo._destroyElement(this._divArea); this._divArea = null; } }, _initializeStart: function(){ // summary: Initialize the start of spellcheck (if needed) if(this._useTextarea){ this._divArea = dojo.doc.createElement("div"); this._divArea.className = this.textarea.className + " spellChecker"; this._divArea.style.position = "absolute"; var coords = dojo.coords(this.textarea); //FIXME : why sometime we need x and y sum ? if(this.useXYPosition){ coords.l += coords.x; coords.t += coords.y; } dojo.marginBox(this._divArea,coords); this.textarea.style.visibility = "hidden"; dojo.body().appendChild(this._divArea); } }, stop: function(){ // summary: Stop the spell checker if(!this.isInProgress){ return false; } if(!this._canStop){ return false; } this._canStop = false; this._destroySpellItem(); //we use a temporary div because IE does not allow replaceChild in iframe document var tmp = dojo.doc.createElement("div"); tmp.innerHTML = this._getSpellContent(); //remove all spell node var rm = this._query("spellCheck",tmp); rm.forEach(function(obj){ var fragment = dojo.doc.createTextNode(obj.innerHTML); obj.parentNode.replaceChild(fragment, obj); fragment = null; },this); rm = null; this._setContent(tmp.innerHTML); tmp = null; this._finalizeStop(); this.isInProgress = false; return true; }, start: function(/*Boolean*/useTextarea){ // summary: Start the spell checker if(this.isInProgress){ return false; } this.isInProgress = true; this._useTextarea = useTextarea || false; this._initializeStart(); var content = this._getContent(); dojo.xhrPost({ url : this.aspell, encoding : this.encoding, handleAs : "json", content : { words : content, lang : this.language }, handle : dojo.hitch(this,this._addSpelling) }); return true; }, _getContent: function(){ // summary: Internal method, choose between getContent in richtext and getContent in textarea var value = ""; if(this._useTextarea){ value = this.textarea.value.replace(/\n|\r/g,"
"); }else{ value = this.getContent().replace(/\n|\r/g,""); } return value; }, _setContent: function(/*String*/content){ // summary: Internal method, choose between setContent in richtext and setContent in textarea if(this._useTextarea){ this._divArea.innerHTML = content; }else{ this.setContent(content); } }, _addSpelling: function(data){ // summary: add all element used to show the correction var content = this._getContent(); var decalage = 0; var span = '${mot}'; var nbMenu = 0; if(!this._useTextarea){ var editorCoords = dojo.coords(this.editor.domNode); if(this.contextualMenuCoordsCorrection != null && typeof this.contextualMenuCoordsCorrection.l != "undefined"){ editorCoords.x += this.contextualMenuCoordsCorrection.l; } if(this.contextualMenuCoordsCorrection != null && typeof this.contextualMenuCoordsCorrection.t != "undefined"){ editorCoords.y += this.contextualMenuCoordsCorrection.t; } } for(var i in data.response){ var off = decalage + data.response[i]['offset'] + 1; var mot = data.response[i]['mot']; var len = data.response[i]['length']; var sp = dojo.string.substitute(span, { mot: mot }); var tmp = [content.substr(0,off), sp, content.substr(off + len)].join(""); content = tmp; decalage += sp.length - len; if(typeof this._contextMenu[mot] == "undefined"){ var menu = []; var countSuggest = 0; for(var j in data.response[i]['suggestion']){ menu.push({ id : this.id + "menu" + nbMenu, onClick : dojo.hitch(this,this._onItemClick), label : data.response[i]['suggestion'][j] }); nbMenu++; countSuggest++; if(this.maxSuggest > 0 && this.maxSuggest <= countSuggest){ break; } } menu.push({ id : this.id + "separator" + nbMenu, label : "-" }); menu.push({ id : this.id + "ignore" + nbMenu, label : this._nls.ignore, onClick : dojo.hitch(this,this.ignore) }); menu.push({ id : this.id + "ignoreall" + nbMenu, label : this._nls.ignoreAll, onClick : dojo.hitch(this,this.ignoreAll) }); this._contextMenu[mot] = new dojox.widget.MenuGenerator({ menuClass : "dojox.widget.SpellCheckContextMenu", id : this.id + "popmenu" + mot, menuEntries : menu, leftClickToOpen : this.leftClickToOpen }); this._contextMenu[mot].startup(); if(!this._useTextarea){ this._contextMenu[mot].getMenu().coordsPadding = { x : editorCoords.x, y : editorCoords.y }; } } } this._setContent(content); for(var i in this._contextMenu){ var nl = this._query(i); nl.forEach(function(obj){ this._contextMenu[i].bindDomNode(obj); this._handles.push(dojo.connect(obj,"onmousedown",this,"_memorizeNode")); },this); nl = null; } this._canStop = true; }, _query: function(/*string*/className,/*Optional, DomNode*/root,/*String,optional*/tag){ // summary: We cannot use dojo.query in firefox 3 (security restriction with iframe) // so we do our query method tag = tag || "span"; var nodes = new dojo.NodeList(); if(typeof root == "undefined" || root == null){ if(this._useTextarea){ root = this._divArea; }else{ root = this.innerDocument; } } var nl = root.getElementsByTagName(tag); if(nl.length > 0){ for(var j in nl){ if(typeof nl[j] == "undefined" || typeof nl[j].nodeType == "undefined" || 1 != nl[j].nodeType){ continue; } var cls = nl[j].className.split(" "); if(dojo.indexOf(cls,className) != -1){ nodes.push(nl[j]); } } } nl = null; try{ return nodes; }finally{ nodes = null; } }, _memorizeNode: function(e){ // summary: Store current word e = dojo.fixEvent(e); this._activeWord = e.target; }, _onItemClick: function(e){ // summary: Change mispelled word by choosen e = dojo.fixEvent(e); //find the td with content, not td clicked var father = e.target.parentNode; var target = this._query("dijitMenuItemLabel",father,"td")[0]; this._activeWord.innerHTML = target.innerHTML; this._unBind(this._activeWord); father = null; target = null; }, ignore: function(e){ // summary: ignore a word e = dojo.fixEvent(e); this._unBind(this._activeWord); }, ignoreAll: function(e){ // summary: ignore all word e = dojo.fixEvent(e); var rm = this._query(this._activeWord.getAttribute("word")); this._unBindAll(rm); }, _unBindAll: function(nodes){ // summary: remove events on all incorrect word nodes.forEach(function(obj){ this._unBind(obj); },this); }, _unBind: function(node){ // summary: remove events on incorrect word if(dojo.isIE){ node.style.cssText = ""; }else{ node.setAttribute("style",""); } this._contextMenu[node.getAttribute("word")].unBindDomNode(node); }, _destroySpellItem: function(){ // summary: destroy all item added by spellchecker for(var i in this._handles){ dojo.disconnect(this._handles[i]); } for(var i in this._contextMenu){ if(typeof i != "string" || i == ""){ continue; } var rm = this._query(i); this._unBindAll(rm); this._contextMenu[i].destroy(); } this._contextMenu = {}; }, destroy: function(){ // summary: destroy spellchecker this._destroySpellItem(); this._activeWord = null; this._divArea = null; this._contextMenu = null; this._handles = null; this.inherited("destroy",arguments); }, /************************************************************************************** * Overwrited method **************************************************************************************/ setContent: function(value){ // summary: Overwrited method, refer to the internal method of editor to set content }, getContent: function(){ // summary: Overwrited method, refer to the internal method of editor to get content } }); dojo.declare( "dojox.widget.SpellCheckContextMenu", [dijit.Menu], { // summary // Contextual menu for spellchecker //coordsPadding: Object //Used to positionning context menu coordsPadding: null, _openMyself: function(/*Event*/ e){ // summary: // Internal function for opening myself when the user // does a right-click or something similar e = dojo.fixEvent(e); this.opener = e.target; if(this.leftClickToOpen && e.button > 0){ return; } dojo.stopEvent(e); // Get coordinates. // if we are opening the menu with the mouse or on safari open // the menu at the mouse cursor // (Safari does not have a keyboard command to open the context menu // and we don't currently have a reliable way to determine // _contextMenuWithMouse on Safari) var x,y; if(dojo.isSafari || this._contextMenuWithMouse){ x = e.pageX; y = e.pageY; }else{ // otherwise open near e.target var coords = dojo.coords(e.target, true); x = coords.x + 10; y = coords.y + 10; } if(this.coordsPadding != null && typeof this.coordsPadding.x != "undefined"){ x += this.coordsPadding.x; } if(this.coordsPadding != null && typeof this.coordsPadding.y != "undefined"){ y += this.coordsPadding.y; } var self = this; var savedFocus = dijit.getFocus(this); function closeAndRestoreFocus(){ // user has clicked on a menu or popup dijit.focus(savedFocus); dijit.popup.close(self); } dijit.popup.open({ popup : this, x : x, y : y, onExecute : closeAndRestoreFocus, onCancel : closeAndRestoreFocus, orient : this.isLeftToRight() ? 'L' : 'R' }); this.focus(); this._onBlur = function(){ this.inherited('_onBlur', arguments); // Usually the parent closes the child widget but if this is a context // menu then there is no parent dijit.popup.close(this); // don't try to restore focus; user has clicked another part of the screen // and set focus there }; if(dojo.isSafari){ this ._blurHandler = dojo.connect(dojo.doc, "onclick", this, "_onBlur"); } } });