基于Dojo(1.2版本)的简单在线编辑器实现(IE)版本

来源:互联网 发布:社会网络理论代表人物 编辑:程序博客网 时间:2024/06/05 17:58

1 前言

项目需要,需要编写一个简单的Python脚本编辑器。Python脚本内容简单,模式固定,形式如下:

 

脚本中只有两三个固定的对象,对象的属性和方法已知。要求支持关键字高亮显示。并且能够具备类似IDE一样的对象属性联想功能。

拿到这个需求,首先想到的如果要支持高亮显示,只能使用DIV作为文本编辑区域.Dojo的Combox的autoComplet功能可以为我所用。就这么开始吧.......

2 重点代码介绍

2.1 自动完成功能

dojo的Combox是一个INPUT,而且其AutoComplete是会覆盖整个文本框内的文本,针对这种情况,必须自己定一个全新的Widget.我的策略如下,定义全新的Widget:

这个Widget的属性和代码基本完全照抄了dojo的dijit.form.ComboBoxMixin(定义在dojo的ComboBox.js)。完全拷贝下来不是我需要的,我做的第一步就是修改这个Widget的templateString

 

<textarea cols="50" rows="15" name="code" class="javascript">templateString: '&lt;div style="height:100px;overflow:auto;font-size=small;border: 2px solid" border="thick double yellow" contentEditable="true" autocomplete="off" dojoAttachEvent="onkeypress:_onKeyPress, onfocus:compositionend"/ dojoAttachPoint="textbox,focusNode" waiRole="textbox" waiState="haspopup-true,autocomplete-list"&gt;&lt;/div&gt;',</textarea>

由于模版字符串的变化造成了很多原先的方法,属性都不在需要,我做了删除,这里不再赘述。接着需要修改的就是autoComplete提示框的位置.要能跟随光标的变化,IE下是可以获得光标位置的,因此增加如下代码

另外就是Combobox原先只是单个单词,而在线编辑器.需要根据当前鼠标的位置,获得左边的文本以及右边的文本,并获得光标前第一个单词的文本(用于联想对象属性),为此增加如下函数

其中_getCaretPos获得光标位置,getWordBeginIndex获得所联想的单词的起始位置,这样当自动完成的时候,我只需要把光标左边的文本,联想出的文本,光标右边的文本相加即为完成后的文本.如此一来,继续修改原dojo代码中的autocomplete部分代码即可,这一部分比较简单,读者可以参阅最后原代码中的_autoCompleteText函数即可.这里值得提出的就是当DIV中内容发生改变的时候,不能在修改完innerHTML后立即执行对新内容的操作,所以代码中通过setTimeout方式,延时处理修改后的内容。

接下来还需要考虑不同情况下, 下拉框结果集的变化,dojo的combobox是根据store属性中的值来构造下拉列表的,因此只需要修改store即可,为此我定义了如下两个成员entityStore,subStoreMap,其中entityStore就是一级对象成员,存储在Store中;二级属性成员存储在 subStoreMap中。举例说明:比如一级对象有个叫bus,r则subStoreMap['bus']存放bus对象的属性.在_onkeyPress函数中增加对'.'的处理 ,修改store属性即可实现对象属性的联想。当然这个键盘响应函数中还要增加其他的控制,来决定store为空还是一级对象集合.

2.2 关键字高亮功能

这个相对比较简单,大体思路是,找出文本中的关键字在文本中的位置,利用textRange的execCommand进行着色和加粗.值得注意的是,对于String的substring一个换行符是两个字符,而在textRange对象中换行是一个字符,所以代码如下

3 Widget代码

<textarea cols="50" rows="15" name="code" class="javascript">dojo.provide("dijit.form.AutoCompleteEditor");dojo.require("dijit.form.ComboBox");dojo.declare( "dijit.form.AutoCompleteEditor", [dijit._Widget, dijit._Templated], { // summary: // Implements the base functionality for ComboBox/FilteringSelect // description: // All widgets that mix in dijit.form.ComboBoxMixin must extend dijit.form._FormValueWidget // item: Object // This is the item returned by the dojo.data.store implementation that // provides the data for this cobobox, it's the currently selected item. item: null, // pageSize: Integer // Argument to data provider. // Specifies number of search results per page (before hitting "next" button) pageSize: Infinity, // store: Object // Reference to data provider object used by this ComboBox store: null, // fetchProperties: Object // Mixin to the dojo.data store's fetch. // For example, to set the sort order of the ComboBox menu, pass: // {sort:{attribute:"name",descending:true}} fetchProperties:{}, // query: Object // A query that can be passed to 'store' to initially filter the items, // before doing further filtering based on `searchAttr` and the key. // Any reference to the `searchAttr` is ignored. query: {}, // autoComplete: Boolean // If you type in a partial string, and then tab out of the `&lt;input&gt;` box, // automatically copy the first entry displayed in the drop down list to // the `&lt;input&gt;` field autoComplete: false, // highlightMatch: String // One of: "first", "all" or "none". // If the ComboBox opens with the serach results and the searched // string can be found it will be highlighted. // This value is not considered when labelType!="text" to not // screw up any mark up the label might contain. highlightMatch: "first", // searchDelay: Integer // Delay in milliseconds between when user types something and we start // searching based on that value searchDelay: 100, // searchAttr: String // Searches pattern match against this field searchAttr: "name", // labelAttr: String // Optional. The text that actually appears in the drop down. // If not specified, the searchAttr text is used instead. labelAttr: "", // labelType: String // "html" or "text" labelType: "text", // queryExpr: String // dojo.data query expression pattern. // `${0}` will be substituted for the user text. // `*` is used for wildcards. // `${0}*` means "starts with", `*${0}*` means "contains", `${0}` means "is" queryExpr: "${0}*", // ignoreCase: Boolean // Set true if the ComboBox should ignore case when matching possible items ignoreCase: true, // hasDownArrow: Boolean // Set this textbox to have a down arrow button. // Defaults to true. hasDownArrow:false, templateString: '&lt;div style="height:100px;overflow:auto;font-size=small;border: 2px solid" border="thick double yellow" contentEditable="true" autocomplete="off" dojoAttachEvent="onkeypress:_onKeyPress, onfocus:compositionend"/ dojoAttachPoint="textbox,focusNode" waiRole="textbox" waiState="haspopup-true,autocomplete-list"&gt;&lt;/div&gt;', baseClass:"dijitComboBox", keyWords:['and', 'or', 'if'], noliterWords:['/n', ' ', '.','(',')', '=', '&gt;', '&lt;'], leftTextValue: '', rigthtTextValue: '', entityStore: null, subStoreMap: null, highlightColor: 'blue', _getCaretPos: function(/*DomNode*/ element){ var CaretPos = 0; // IE Support if (document.selection) { element.focus (); var Sel = document.selection.createRange(); Sel.moveStart ('character', -element.innerText.length); var text = Sel.text; for (var i = 0; i &lt; element.innerText.length; i++) { if (element.innerText.substring(0, i + 1) == text.substring(text.length - i - 1, text.length)) { CaretPos = i + 1; } } } // Firefox support else if (element.selectionStart || element.selectionStart == '0') CaretPos = element.selectionStart; return (CaretPos); }, _setCaretPos: function(/*DomNode*/ element, /*Number*/ location){ if(element.setSelectionRange) { element.focus(); element.setSelectionRange(location,location); } else if (dojo.doc.body.createTextRange) { var range = dojo.doc.body.createTextRange(); range.moveToElementText(element); range.collapse(true); range.move('character', location); range.select(); } }, _onKeyPress: function(/*Event*/ evt){ // summary: handles keyboard events var key = evt.charOrCode; //except for cutting/pasting case - ctrl + x/v if(evt.altKey || (evt.ctrlKey &amp;&amp; (key != 'x' &amp;&amp; key != 'v')) || evt.key == dojo.keys.SHIFT){ return; // throw out weird key combinations and spurious events } var doSearch = false; var pw = this._popupWidget; var dk = dojo.keys; if(this._isShowingNow){ pw.handleKey(key); } switch(key){ case dk.PAGE_DOWN: case dk.DOWN_ARROW: if(!this._isShowingNow||this._prev_key_esc){ return }else{ dojo.stopEvent(evt); this._prev_key_backspace = false; this._prev_key_esc = false; } break; case dk.PAGE_UP: case dk.UP_ARROW: if (this._isShowingNow) { dojo.stopEvent(evt); this._prev_key_backspace = false; this._prev_key_esc = false; } else{ return; } break; case dojo.keys.ENTER: case dojo.keys.TAB: if (this._isShowingNow) { this._announceOption(this._popupWidget.getHighlightedOption()); text = this.focusNode[dojo.isIE?'innerText':'textContent']; //pos = this.leftTextValue.length; //pos += this.focusNode[dojo.isIE?'innerText':'textContent'].length; this.focusNode[dojo.isIE?'innerText':'textContent'] = text; this.store = this.entityStore; //this._setCaretPos(this.focusNode, pos); this.clearTextValue(); evt.preventDefault(); this._prev_key_backspace = false; this._prev_key_esc = false; this._lastQuery = null; // in case results come back later this._hideResultList(); }else{ return; } break; case ' ': if (this._isShowingNow &amp;&amp; this._popupWidget.getHighlightedOption()) { this._announceOption(this._popupWidget.getHighlightedOption()); text = this.focusNode[dojo.isIE?'innerText':'textContent']; this.focusNode[dojo.isIE?'innerText':'textContent'] = text; this.store = this.entityStore; this.clearTextValue(); } else { return; } break; case dk.ESCAPE: this._prev_key_backspace = false; this._prev_key_esc = true; if(this._isShowingNow){ dojo.stopEvent(evt); this._hideResultList(); }else{ this.inherited(arguments); } break; case dk.DELETE: case dk.BACKSPACE: if (this._isShowingNow) { this._prev_key_esc = false; this._prev_key_backspace = true; doSearch = true; } else { return; } break; case dk.RIGHT_ARROW: // fall through case dk.LEFT_ARROW: this._prev_key_backspace = false; this._prev_key_esc = false; if (!this._isShowingNow) { return; } break; case '.': var key =this.getSearchKey(); if (this.subStoreMap[key]) { this.store = this.subStoreMap[key]; doSearch = true; } else { doSearch = false; } break; default: // non char keys (F1-F12 etc..) shouldn't open list this._prev_key_backspace = false; this._prev_key_esc = false; doSearch = typeof key == 'string'; } if(this.searchTimer){ clearTimeout(this.searchTimer); } if(doSearch){ // need to wait a tad before start search so that the event // bubbles through DOM and we have value visible setTimeout(dojo.hitch(this, "_startSearchFromInput"),1); } }, _autoCompleteText: function(/*String*/ text){ // summary: // Fill in the textbox with the first item from the drop down // list, and highlight the characters that were // auto-completed. For example, if user typed "CA" and the // drop down list appeared, the textbox would be changed to // "California" and "ifornia" would be highlighted. var fn = this.focusNode; // IE7: clear selection so next highlight works all the time dijit.selectInputText(fn, fn[dojo.isIE?'innerText':'textContent'].length); // does text autoComplete the value in the textbox? var caseFilter = this.ignoreCase? 'toLowerCase' : 'substr'; if(text[caseFilter](0).indexOf(this.focusNode[dojo.isIE?'innerText':'textContent'][caseFilter](0)) == 0){ var cpos = this._getCaretPos(fn); // only try to extend if we added the last character at the end of the input if((cpos+1) &gt; fn[dojo.isIE?'innerText':'textContent'].length){ // only add to input node as we would overwrite Capitalisation of chars // actually, that is ok fn[dojo.isIE?'innerText':'textContent'] = text;//.substr(cpos); // visually highlight the autocompleted characters dijit.selectInputText(fn, cpos); } }else{ // text does not autoComplete; replace the whole value and highlight fn[dojo.isIE?'innerText':'textContent'] = text; dijit.selectInputText(fn); } var pos = (this.leftTextValue + this.focusNode[dojo.isIE?'innerText':'textContent']).length; this.focusNode.innerHTML = (this.leftTextValue + this.focusNode[dojo.isIE?'innerText':'textContent'] + this.rigthtTextValue).split('/n').join('&lt;BR&gt;').split(' ').join(' '); setTimeout(dojo.hitch(this, "_setCaretPos", this.focusNode, pos),10); setTimeout(dojo.hitch(this, "highlightKeyWord"), 20); }, _openResultList: function(/*Object*/ results, /*Object*/ dataObject){ setTimeout(dojo.hitch(this, "highlightKeyWord"),10); if( this.disabled || this.readOnly || (dataObject.query[this.searchAttr] != this._lastQuery) ){ return; } this._popupWidget.clearResultList(); if(!results.length){ this._hideResultList(); return; } // Fill in the textbox with the first item from the drop down list, // and highlight the characters that were auto-completed. For // example, if user typed "CA" and the drop down list appeared, the // textbox would be changed to "California" and "ifornia" would be // highlighted. var zerothvalue = new String(this.store.getValue(results[0], this.searchAttr)); if(zerothvalue &amp;&amp; this.autoComplete &amp;&amp; !this._prev_key_backspace &amp;&amp; (dataObject.query[this.searchAttr] != "*")){ // when the user clicks the arrow button to show the full list, // startSearch looks for "*". // it does not make sense to autocomplete // if they are just previewing the options available. this._autoCompleteText(zerothvalue); } dataObject._maxOptions = this._maxOptions; this._popupWidget.createOptions( results, dataObject, dojo.hitch(this, "_getMenuLabelFromItem") ); // show our list (only if we have content, else nothing) this._showResultList(); // #4091: // tell the screen reader that the paging callback finished by // shouting the next choice if(dataObject.direction){ if(1 == dataObject.direction){ this._popupWidget.highlightFirstOption(); }else if(-1 == dataObject.direction){ this._popupWidget.highlightLastOption(); } //this._announceOption(this._popupWidget.getHighlightedOption()); } }, _showResultList: function(){ this._hideResultList(); var items = this._popupWidget.getItems(), visibleCount = Math.min(items.length,this.maxListLength); // hide the tooltip //this.displayMessage(""); // Position the list and if it's too big to fit on the screen then // size it to the maximum possible height // Our dear friend IE doesnt take max-height so we need to // calculate that on our own every time // TODO: want to redo this, see // http://trac.dojotoolkit.org/ticket/3272 // and // http://trac.dojotoolkit.org/ticket/4108 // natural size of the list has changed, so erase old // width/height settings, which were hardcoded in a previous // call to this function (via dojo.marginBox() call) dojo.style(this._popupWidget.domNode, {width: "", height: ""}); var best = this.open(); // #3212: // only set auto scroll bars if necessary prevents issues with // scroll bars appearing when they shouldn't when node is made // wider (fractional pixels cause this) var popupbox = dojo.marginBox(this._popupWidget.domNode); this._popupWidget.domNode.style.overflow = ((best.h==popupbox.h)&amp;&amp;(best.w==popupbox.w)) ? "hidden" : "auto"; // #4134: // borrow TextArea scrollbar test so content isn't covered by // scrollbar and horizontal scrollbar doesn't appear var newwidth = best.w; if(best.h &lt; this._popupWidget.domNode.scrollHeight){ newwidth += 16; } dojo.marginBox(this._popupWidget.domNode, { h: best.h, w: Math.max(newwidth, this.domNode.offsetWidth) }); //dijit.setWaiState(this.comboNode, "expanded", "true"); if (dojo.isIE) { dijit.removeWaiState(this.focusNode, "owns"); var range = document.selection.createRange(); this._popupWidget.domNode.parentNode.style.left = range.offsetLeft + 'px'; this._popupWidget.domNode.parentNode.style.top = (range.offsetTop + 13) + 'px'; this._popupWidget.domNode.parentNode.style.width = 200; this._popupWidget.domNode.style.width = 200; } }, _hideResultList: function(){ if(this._isShowingNow){ dijit.popup.close(this._popupWidget); this._isShowingNow=false; //dijit.setWaiState(this.comboNode, "expanded", "false"); dijit.removeWaiState(this.focusNode,"activedescendant"); } }, _announceOption: function(/*Node*/ node){ // summary: // a11y code that puts the highlighted option in the textbox // This way screen readers will know what is happening in the // menu this.setTextValue(); if(node == null){ return; } // pull the text value from the item attached to the DOM node var newValue; if( node == this._popupWidget.nextButton || node == this._popupWidget.previousButton){ newValue = node.innerHTML; }else{ newValue = this.store.getValue(node.item, this.searchAttr); } // get the text that the user manually entered (cut off autocompleted text) this.focusNode[dojo.isIE?'innerText':'textContent'] = this.focusNode[dojo.isIE?'innerText':'textContent'].substring(0, this._getCaretPos(this.focusNode)); //set up ARIA activedescendant dijit.setWaiState(this.focusNode, "activedescendant", dojo.attr(node, "id")); // autocomplete the rest of the option to announce change this._autoCompleteText(newValue); }, _selectOption: function(/*Event*/ evt){ var tgt = null; if(!evt){ evt ={ target: this._popupWidget.getHighlightedOption()}; } // what if nothing is highlighted yet? if(!evt.target){ // handle autocompletion where the the user has hit ENTER or TAB this.attr('displayedValue', this.attr('displayedValue')); return; // otherwise the user has accepted the autocompleted value }else{ tgt = evt.target; } if(!evt.noHide){ this._hideResultList(); this._setCaretPos(this.focusNode, this.store.getValue(tgt.item, this.searchAttr).length); } this._doSelect(tgt); }, _doSelect: function(tgt){ this.item = tgt.item; this.attr('value', this.store.getValue(tgt.item, this.searchAttr)); }, _startSearchFromInput: function(){ this._startSearch(this.focusNode[dojo.isIE?'innerText':'textContent']); }, _getQueryString: function(/*String*/ text){ return dojo.string.substitute(this.queryExpr, [text]); }, _startSearch: function(/*String*/ key){ var index = this.getWordBeginIndex(); key = key.substring(0, this._getCaretPos(this.focusNode)); key = dojo.trim(key.substring(index + 1, key.length)); if(!this._popupWidget){ var popupId = this.id + "_popup"; this._popupWidget = new dijit.form._ComboBoxMenu({ onChange: dojo.hitch(this, this._selectOption), id:popupId }); this.connect(this._popupWidget, '_onMouseUp', function(event){ this.focusNode.innerHTML = (this.leftTextValue + this.value + this.rigthtTextValue).split('/n').join('&lt;BR&gt;'); this.clearTextValue(); this.store = this.entityStore; }); this.connect(this._popupWidget, '_onMouseDown', function(event){ this.setTextValue(); }); dijit.removeWaiState(this.focusNode,"activedescendant"); dijit.setWaiState(this.textbox,"owns",popupId); // associate popup with textbox } // create a new query to prevent accidentally querying for a hidden // value from FilteringSelect's keyField this.item = null; // #4872 var query = dojo.clone(this.query); // #5970 this._lastInput = key; // Store exactly what was entered by the user. this._lastQuery = query[this.searchAttr] = this._getQueryString(key); // #5970: set _lastQuery, *then* start the timeout // otherwise, if the user types and the last query returns before the timeout, // _lastQuery won't be set and their input gets rewritten this.searchTimer=setTimeout(dojo.hitch(this, function(query, _this){ var fetch = { queryOptions: { ignoreCase: this.ignoreCase, deep: true }, query: query, onBegin: dojo.hitch(this, "_setMaxOptions"), onComplete: dojo.hitch(this, "_openResultList"), onError: function(errText){ console.error('dijit.form.ComboBox: ' + errText); dojo.hitch(_this, "_hideResultList")(); }, start:0, count:this.pageSize }; dojo.mixin(fetch, _this.fetchProperties); var dataObject = _this.store.fetch(fetch); var nextSearch = function(dataObject, direction){ dataObject.start += dataObject.count*direction; // #4091: // tell callback the direction of the paging so the screen // reader knows which menu option to shout dataObject.direction = direction; this.store.fetch(dataObject); }; this._nextSearch = this._popupWidget.onPage = dojo.hitch(this, nextSearch, dataObject); }, query, this), this.searchDelay); }, _setMaxOptions: function(size, request){ this._maxOptions = size; }, _getValueField:function(){ return this.searchAttr; }, // FIXME: // this is public so we can't remove until 2.0, but the name // SHOULD be "compositionEnd" compositionend: function(/*Event*/ evt){ // summary: // When inputting characters using an input method, such as // Asian languages, it will generate this event instead of // onKeyDown event Note: this event is only triggered in FF // (not in IE) this._onKeyPress({charCode:-1}); }, //////////// INITIALIZATION METHODS /////////////////////////////////////// constructor: function(){ this.query={}; this.fetchProperties={}; }, postMixInProperties: function(){ this.store = this.entityStore; if(!this.hasDownArrow){ this.baseClass = "dijitTextBox"; } if(!this.store){ var srcNodeRef = this.srcNodeRef; // if user didn't specify store, then assume there are option tags this.store = new dijit.form._ComboBoxDataStore(srcNodeRef); // if there is no value set and there is an option list, set // the value to the first value to be consistent with native // Select // Firefox and Safari set value // IE6 and Opera set selectedIndex, which is automatically set // by the selected attribute of an option tag // IE6 does not set value, Opera sets value = selectedIndex if( !this.value || ( (typeof srcNodeRef.selectedIndex == "number") &amp;&amp; srcNodeRef.selectedIndex.toString() === this.value) ){ var item = this.store.fetchSelectedItem(); if(item){ this.value = this.store.getValue(item, this._getValueField()); } } } }, uninitialize:function(){ if(this._popupWidget){ this._hideResultList(); this._popupWidget.destroy(); } }, _getMenuLabelFromItem:function(/*Item*/ item){ var label = this.store.getValue(item, this.labelAttr || this.searchAttr); var labelType = this.labelType; // If labelType is not "text" we don't want to screw any markup ot whatever. if (this.highlightMatch!="none" &amp;&amp; this.labelType=="text" &amp;&amp; this._lastInput){ label = this.doHighlight(label, this._escapeHtml(this._lastInput)); labelType = "html"; } return {html: labelType=="html", label: label}; }, doHighlight:function(/*String*/label, /*String*/find){ // summary: // Highlights the string entered by the user in the menu, by default this // highlights the first occurence found. Override this method // to implement your custom highlighing. // Add greedy when this.highlightMatch=="all" var modifiers = "i"+(this.highlightMatch=="all"?"g":""); var escapedLabel = this._escapeHtml(label); var ret = escapedLabel.replace(new RegExp("^("+ find +")", modifiers), '&lt;span class="dijitComboBoxHighlightMatch"&gt;$1&lt;/span&gt;'); if (escapedLabel==ret){ // Nothing replaced, try to replace at word boundaries. ret = escapedLabel.replace(new RegExp(" ("+ find +")", modifiers), ' &lt;span class="dijitComboBoxHighlightMatch"&gt;$1&lt;/span&gt;'); } return ret;// returns String, (almost) valid HTML (entities encoded) }, _escapeHtml:function(/*string*/str){ // TODO Should become dojo.html.entities(), when exists use instead // summary: // Adds escape sequences for special characters in XML: &amp;&lt;&gt;"' str = String(str).replace(/&amp;/gm, "&amp;").replace(/&lt;/gm, "&lt;") .replace(/&gt;/gm, "&gt;").replace(/"/gm, """); return str; // string }, open:function(){ this._isShowingNow=true; return dijit.popup.open({ popup: this._popupWidget, around: this.domNode, parent: this }); }, reset:function(){ // summary: // Additionally reset the .item (to clean up). this.item = null; this.inherited(arguments); }, getSearchKey: function() { var pos = this._getCaretPos(this.focusNode); var key = this.focusNode[dojo.isIE?'innerText':'textContent'].substring(0, pos); var index = this.getWordBeginIndex(key); key = dojo.trim(key.substring(index + 1, key.length)); return key; }, setTextValue: function() { var pos = this._getCaretPos(this.focusNode); this.leftTextValue = this.focusNode[dojo.isIE?'innerText':'textContent'].substring(0, pos); this.leftTextValue = this.leftTextValue.substr(0,this.getWordBeginIndex(this.leftTextValue) + 1); this.rigthtTextValue = this.focusNode[dojo.isIE?'innerText':'textContent'].substring(pos, this.focusNode[dojo.isIE?'innerText':'textContent'].length); }, clearTextValue: function() { this.leftTextValue = ''; this.rigthtTextValue = ''; }, getWordBeginIndex: function(text) { var lastIndexArray = []; if (!text) text = this.focusNode[dojo.isIE?'innerText':'textContent'].substring(0, this._getCaretPos(this.focusNode)); dojo.forEach(this.keyWords.concat(this.noliterWords), function(x) { lastIndexArray.push(text.toLowerCase().lastIndexOf(x)); }); lastIndexArray.sort(function(a,b) { return b - a; }); return lastIndexArray[0]; }, highlightKeyWord: function() { //Only for IE var textContent = this.focusNode[dojo.isIE?'innerText':'textContent']; var begin = 0, oneword; for (var i = 0; i &lt; textContent.length; i++) { if (dojo.indexOf(this.noliterWords, textContent.substring(i,i + 1)) &gt; -1) { oneword = textContent.substring(begin, i + 1); if (dojo.indexOf(this.keyWords, dojo.trim(oneword)) &gt; -1) { this._boldWord(this.focusNode, begin, i + 1); } begin = i; } } oneword = textContent.substring(begin,i + 1); if (dojo.indexOf(this.keyWords, dojo.trim(oneword)) &gt; -1) { this._boldWord(this.focusNode, begin, i + 1); } }, _boldWord: function(element, begin, end) { if (dojo.doc.body.createTextRange) { var range = dojo.doc.body.createTextRange(); range.moveToElementText(element); var line1 = element.innerText.substring(0, begin).split('/n').length - 1; var line2 = element.innerText.substring(end, element.innerText.length).split('/n').length - 1; range.moveEnd('character', end - element.innerText.length + line2); range.moveStart('character', begin - line1); if (!range.queryCommandState('bold')) { range.execCommand('bold', false, null); range.execCommand('forecolor', false, this.highlightColor); } } }, getValue: function() { return this.focusNode[dojo.isIE?'innerText':'textContent']; } });</textarea>

这个JS放置于dojo/dijit/form目录下

测试HTML为,读者使用的时候注意修改相对路径

 

其中一级对象为Customer,Event和GetString,Customer包含BirthDay和Gender两个属性 Event包含Voice和GPRS两个属性

关键字为[if, else,and,or]

效果图如下

效果图