感谢Ext社区的外国朋友提供一个现成的插件,我在当中加入一个表情panel,希望给大家在用Ext的时候也方便一些.做个例子让大家继续扩展.
Ext.ux.form.plugin.HtmlEditor.js
/** * @class Ext.ux.form.plugin.HtmlEditor * @author Adrian Teodorescu (ateodorescu@gmail.com; http://www.mzsolutions.eu) * @docauthor Adrian Teodorescu (ateodorescu@gmail.com; http://www.mzsolutions.eu) * @version 1.1 * * Provides plugins for the HtmlEditor. Many thanks to [Shea Frederick][1] as I was inspired by his [work][2]. * * [1]: http://www.vinylfox.com * [2]: http://www.vinylfox.com/plugin-set-for-additional-extjs-htmleditor-buttons/ * * The plugin buttons have tooltips defined in the {@link #buttonTips} property, but they are not * enabled by default unless the global {@link Ext.tip.QuickTipManager} singleton is {@link Ext.tip.QuickTipManager#init initialized}. * * * #Example usage:# {@img Ext.ux.form.plugin.HtmlEditor.png Ext.ux.form.plugin.HtmlEditor plugins} var top = Ext.create('Ext.form.Panel', { frame:true, title: 'HtmlEditor plugins', bodyStyle: 'padding:5px 5px 0', width: '80%', fieldDefaults: { labelAlign: 'top', msgTarget: 'side' }, items: [{ xtype: 'htmleditor', fieldLabel: 'Text editor', height: 300, plugins: [ Ext.create('Ext.ux.form.plugin.HtmlEditor',{ enableAll: true }) ], anchor: '100%' }], buttons: [{ text: 'Save' },{ text: 'Cancel' }] }); top.render(document.body); */ Ext.define('Ext.ux.form.plugin.HtmlEditor', { mixins: { observable: 'Ext.util.Observable' }, alternateClassName: 'Ext.form.plugin.HtmlEditor', requires: [ 'Ext.tip.QuickTipManager', 'Ext.form.field.HtmlEditor' ], /** * @cfg {Boolean} enableAll Enable all available plugins */ enableAll: false, /** * @cfg {Boolean} enableUndoRedo Enable "undo" and "redo" plugins */ enableUndoRedo: false, /** * @cfg {Boolean} enableRemoveHtml Enable the plugin "remove html" which is removing all html entities from the entire text */ enableRemoveHtml: false, /** * @cfg {Boolean} enableRemoveFormatting Enable "remove format" plugin */ enableRemoveFormatting: false, /** * @cfg {Boolean} enableIndenting Enable "indent" and "outdent" plugins */ enableIndenting: false, /** * @cfg {Boolean} enableSmallLetters Enable "superscript" and "subscript" plugins */ enableSmallLetters: false, /** * @cfg {Boolean} enableHorizontalRule Enable "horizontal rule" plugin */ enableHorizontalRule: false, /** * @cfg {Boolean} enableSpecialChars Enable "special chars" plugin */ enableSpecialChars: false, /** * @cfg {Boolean} enableWordPaste Enable "word paste" plugin which is cleaning the pasted text that is coming from MS Word */ enableWordPaste: false, /** * @cfg {Boolean} enableFormatBlocks Enable "format blocks" plugin which allows to insert formatting tags. */ enableFormatBlocks: false, /** * @cfg {Boolean} defaultFormatBlock Set the default block format. */ defaultFormatBlock: 'p', /** * 表情 */ enableFaceImages: false, enableInsertTable: false, wordPasteEnabled: false, /** * @cfg {Array} specialChars * An array of additional characters to display for user selection. Uses numeric portion of the ASCII HTML Character Code only. For example, to use the Copyright symbol, which is © we would just specify <tt>169</tt> (ie: <tt>specialChars:[169]</tt>). */ specialChars: [], /** * 表情 */ faceImages: [], /** * @cfg {Array} charRange * Two numbers specifying a range of ASCII HTML Characters to display for user selection. Defaults to <tt>[160, 256]</tt>. */ charRange: [160, 256], /** * @cfg {Array} listFormatBlocks Array of available format blocks. */ listFormatBlocks: ["p", "h1", "h2", "h3", "h4", "h5", "h6", "address", "pre"], constructor: function(config) { Ext.apply(this, config); }, init: function(editor){ var me = this; me.editor = editor; me.mon(editor, 'initialize', me.onInitialize, me); }, onInitialize: function(){ var me = this, undef, items = [], baseCSSPrefix = Ext.baseCSSPrefix, tipsEnabled = Ext.tip.QuickTipManager && Ext.tip.QuickTipManager.isEnabled(); function btn(id, toggle, handler){ return { itemId : id, cls : baseCSSPrefix + 'btn-icon', iconCls: baseCSSPrefix + 'edit-'+id, enableToggle:toggle !== false, scope: me, handler:handler||me.relayBtnCmd, clickEvent:'mousedown', tooltip: tipsEnabled ? me.buttonTips[id] || undef : undef, overflowText: me.buttonTips[id].title || undef, tabIndex:-1 }; } /** if(me.enableFormatBlocks || me.enableAll){ var i, listFormatBlocks = new Array(); for(i=0; i < me.listFormatBlocks.length; i++){ listFormatBlocks.push({ value: me.listFormatBlocks[i].toLowerCase(), caption: me.buttonTips.listFormatBlocks[me.listFormatBlocks[i]] }); } formatBlockSelectItem = Ext.widget('component', { renderTpl: [ '<select class="{cls}">', '<tpl for="formats">', '<option value="<{value}>" <tpl if="values.toLowerCase()==parent.defaultFormatBlock"> selected</tpl>>{caption}</option>', '</tpl>', '</select>' ], renderData: { cls: baseCSSPrefix + 'font-select', formats: listFormatBlocks, defaultFormatBlock: me.defaultFormatBlock }, renderSelectors: { selectEl: 'select' }, onDisable: function() { var selectEl = this.selectEl; if (selectEl) { selectEl.dom.disabled = true; } Ext.Component.superclass.onDisable.apply(this, arguments); }, onEnable: function() { var selectEl = this.selectEl; if (selectEl) { selectEl.dom.disabled = false; } Ext.Component.superclass.onEnable.apply(this, arguments); } }); items.push( '-', formatBlockSelectItem ); } */ if(me.enableRemoveHtml || me.enableAll){ items.push(btn('removehtml', false, me.doRemoveHtml)); } if(me.enableRemoveFormatting || me.enableAll){ items.push(btn('removeformat', false)); } if(me.enableUndoRedo || me.enableAll){ items.push('-'); items.push(btn('undo', false)); items.push(btn('redo', false)); } /*if(me.enableInsertTable || me.enableAll){ items.push('-'); items.push(btn('inserttable', false, me.doInsertTable)); }*/ if(me.enableIndenting || me.enableAll){ items.push('-'); items.push(btn('indent', false)); items.push(btn('outdent', false)); } if(me.enableSmallLetters || me.enableAll){ items.push('-'); items.push(btn('superscript')); items.push(btn('subscript')); } if(me.enableHorizontalRule || me.enableAll){ items.push(btn('inserthorizontalrule', false)); } if(me.enableSpecialChars || me.enableAll){ items.push(btn('chars', false, me.doSpecialChars)); } if(me.enableFaceImages || me.enableAll){ items.push('-'); var faceImagesBtn = btn('faceImages', false, me.doFaceImages); faceImagesBtn.iconCls = 'employee-add'; items.push(faceImagesBtn); } /** if(me.enableWordPaste || me.enableAll){ items.push('-'); items.push(btn('wordpaste', true, me.doWordPaste)); me.wordPasteEnabled = true; }else{ me.wordPasteEnabled = false; } */ /*if(me.enableImages){ items.push(btn('images', false, me.doImages)); }*/ if(items.length > 0){ me.editor.getToolbar().add(items); fn = Ext.Function.bind(me.onEditorEvent, me); Ext.EventManager.on(me.editor.getDoc(), { mousedown: fn, dblclick: fn, click: fn, keyup: fn, buffer:100 }); if(formatBlockSelectItem){ me.formatBlockSelect = formatBlockSelectItem.selectEl; me.mon(me.formatBlockSelect, 'change', function(){ me.relayCmd('formatblock', me.formatBlockSelect.dom.value); me.editor.deferFocus(); }); } } }, onEditorEvent: function(e){ var me = this, diffAt = 0; me.updateToolbar(); me.curLength = me.editor.getValue().length; if (e.ctrlKey) { var c = e.getCharCode(); if (c > 0) { c = String.fromCharCode(c); if(c.toLowerCase() == 'v' && me.wordPasteEnabled){ me.cleanWordPaste(); } } } me.lastLength = me.editor.getValue().length; me.lastValue = me.editor.getValue(); }, updateToolbar: function(){ var me = this, btns, doc; if(me.editor.readOnly){ return; } btns = me.editor.getToolbar().items.map; doc = me.editor.getDoc(); function updateButtons() { Ext.Array.forEach(Ext.Array.toArray(arguments), function(name) { btns[name].toggle(doc.queryCommandState(name)); }); } if(me.enableSmallLetters || me.enableAll){ updateButtons('superscript', 'subscript'); } /** if(me.enableWordPaste || me.enableAll){ btns['wordpaste'].toggle(me.wordPasteEnabled); } if(me.enableFormatBlocks || me.enableAll){ this.checkSelectionFormatBlock(); } */ }, doRemoveHtml: function() { Ext.defer(function() { var me = this; me.editor.focus(); var tmp = document.createElement("DIV"); tmp.innerHTML = me.editor.getValue(); me.editor.setValue(tmp.textContent||tmp.innerText); }, 10, this); }, doInsertTable: function(){ }, doFaceImages: function(){ //表情列表 var faceImages = [ ["001"],["002"],["003"],["004"],["005"] ]; //表情集合 var faceImagesStore = new Ext.data.ArrayStore({ fields: ['name'], data: faceImages }); //表情窗口 this.faceImagesWindow = Ext.create('Ext.Window', { title: this.buttonTips.faceImages.text, width: 500, height : 400, autoShow: true, layout: 'fit', items: [{ itemId: 'idDataView', xtype: 'dataview', store: faceImagesStore, autoHeight: true, multiSelect: true, tpl: new Ext.XTemplate('<tpl for="."><div class="char-item"><img src="images/face/{name}.gif" title="{name}"></div></tpl>'), overItemCls: 'char-over', trackOver: true, itemSelector: 'div.char-item', listeners: { itemdblclick: function(t, record, item, index, e, eOpts ){ var c = '<img src="images/face/'+ record.get('name') + '.gif" title="' + record.get('name') + '">'; if(this.editor.getValue() == null || this.editor.getValue() == ""){ this.editor.setValue(c); this.editor.focus(true,true); }else{ this.editor.focus(); this.editor.insertAtCursor(c); } this.faceImagesWindow.close(); }, scope: this } }] }); this.faceImagesWindow.show(); }, doSpecialChars: function(){ var specialChars = []; if (this.specialChars.length) { Ext.each(this.specialChars, function(c, i){ specialChars[i] = ['&#' + c + ';']; }, this); } for (i = this.charRange[0]; i < this.charRange[1]; i++) { specialChars.push(['&#' + i + ';']); } var charStore = new Ext.data.ArrayStore({ fields: ['char'], data: specialChars }); this.charWindow = Ext.create('Ext.Window', { title: this.buttonTips.chars.text, width: 436, autoHeight: true, layout: 'fit', items: [{ itemId: 'idDataView', xtype: 'dataview', store: charStore, autoHeight: true, multiSelect: true, tpl: new Ext.XTemplate('<tpl for="."><div class="char-item" style="width: 20px; height: 20px; ">{char}</div></tpl><div class="x-clear"></div>'), overItemCls: 'char-over', trackOver: true, itemSelector: 'div.char-item', listeners: { itemdblclick: function(t, i, n, e){ if(this.editor.getValue() == null || this.editor.getValue() == ""){ this.editor.setValue(i.get('char')); }else{ this.editor.focus(); this.editor.insertAtCursor(i.get('char')); } this.charWindow.close(); }, scope: this } }] }); this.charWindow.show(); }, doWordPaste: function(){ this.wordPasteEnabled = !this.wordPasteEnabled; }, cleanWordPaste: function(){ this.editor.suspendEvents(); diffAt = this.findValueDiffAt(this.editor.getValue()); var parts = [ this.editor.getValue().substr(0, diffAt), this.fixWordPaste(this.editor.getValue().substr(diffAt, (this.curLength - this.lastLength))), this.editor.getValue().substr((this.curLength - this.lastLength)+diffAt, this.curLength) ]; this.editor.setValue(parts.join('')); this.editor.resumeEvents(); }, findValueDiffAt: function(val){ for (i=0;i<this.curLength;i++){ if (this.lastValue[i] != val[i]){ return i; } } }, fixWordPaste: function(wordPaste) { var removals = [/ /ig, /[\r\n]/g, /<(xml|style)[^>]*>.*?<\/\1>/ig, /<\/?(meta|object|span)[^>]*>/ig, /<\/?[A-Z0-9]*:[A-Z]*[^>]*>/ig, /(lang|class|type|href|name|title|id|clear)=\"[^\"]*\"/ig, /style=(\'\'|\"\")/ig, /<![\[-].*?-*>/g, /MsoNormal/g, /<\\?\?xml[^>]*>/g, /<\/?o:p[^>]*>/g, /<\/?v:[^>]*>/g, /<\/?o:[^>]*>/g, /<\/?st1:[^>]*>/g, / /g, /<\/?SPAN[^>]*>/g, /<\/?FONT[^>]*>/g, /<\/?STRONG[^>]*>/g, /<\/?H1[^>]*>/g, /<\/?H2[^>]*>/g, /<\/?H3[^>]*>/g, /<\/?H4[^>]*>/g, /<\/?H5[^>]*>/g, /<\/?H6[^>]*>/g, /<\/?P[^>]*><\/P>/g, /<!--(.*)-->/g, /<!--(.*)>/g, /<!(.*)-->/g, /<\\?\?xml[^>]*>/g, /<\/?o:p[^>]*>/g, /<\/?v:[^>]*>/g, /<\/?o:[^>]*>/g, /<\/?st1:[^>]*>/g, /style=\"[^\"]*\"/g, /style=\'[^\"]*\'/g, /lang=\"[^\"]*\"/g, /lang=\'[^\"]*\'/g, /class=\"[^\"]*\"/g, /class=\'[^\"]*\'/g, /type=\"[^\"]*\"/g, /type=\'[^\"]*\'/g, /href=\'#[^\"]*\'/g, /href=\"#[^\"]*\"/g, /name=\"[^\"]*\"/g, /name=\'[^\"]*\'/g, / clear=\"all\"/g, /id=\"[^\"]*\"/g, /title=\"[^\"]*\"/g, /<span[^>]*>/g, /<\/?span[^>]*>/g, /class=/g]; Ext.each(removals, function(s){ wordPaste = wordPaste.replace(s, ""); }); // keep the divs in paragraphs wordPaste = wordPaste.replace(/<div[^>]*>/g, "<p>"); wordPaste = wordPaste.replace(/<\/?div[^>]*>/g, "</p>"); return wordPaste; }, getSelectedNode: function(){ try{ if (this.editor.getWin().getSelection) { var currNode = this.editor.getWin().getSelection().focusNode; } else if (this.editor.getDoc().getSelection) { var currNode = this.editor.getDoc().getSelection().focusNode; } else if (this.editor.getDoc().selection) { var currNode = this.editor.getDoc().selection.createRange().parentElement(); } }catch(err){} if(currNode){ if(currNode.nodeName == "#text") currNode = currNode.parentNode; } return currNode; }, /** checkSelectionFormatBlock: function(){ currNode = this.getSelectedNode(); var index = -1; try{ var currTag = currNode; var prevTagName = currNode.tagName; if (prevTagName) prevTagName = prevTagName.toLowerCase(); while(prevTagName != "html"){ if (prevTagName == "paragraph"){ index = this.listFormatBlocks.indexOf('p') }else{ index = this.listFormatBlocks.indexOf(prevTagName); } if (index >= 0) break; currTag = currTag.parentNode; prevTagName = currTag.tagName; if (prevTagName) prevTagName = prevTagName.toLowerCase(); } }catch(err){} this.formatBlockSelect.dom.selectedIndex = index; return index; }, */ relayBtnCmd: function(btn){ this.relayCmd(btn.getItemId()); }, relayCmd: function(cmd, value) { Ext.defer(function() { var me = this; me.editor.focus(); me.editor.execCmd(cmd, value); }, 10, this); }, buttonTips : { undo : { title: 'Undo', text: 'Undo the last action.', cls: Ext.baseCSSPrefix + 'html-editor-tip' }, redo : { title: 'Redo', text: 'Redo the last action.', cls: Ext.baseCSSPrefix + 'html-editor-tip' }, removehtml : { title: 'Remove html', text: 'Remove html from the entire text.', cls: Ext.baseCSSPrefix + 'html-editor-tip' }, removeformat : { title: 'Remove formatting', text: 'Remove formatting for the selected area.', cls: Ext.baseCSSPrefix + 'html-editor-tip' }, inserttable : { title: 'Insert table', text: 'Insert table at the cursor.', cls: Ext.baseCSSPrefix + 'html-editor-tip' }, indent : { title: 'Indent', text: 'Indent paragraph.', cls: Ext.baseCSSPrefix + 'html-editor-tip' }, outdent : { title: 'Outdent', text: 'Outdent paragraph.', cls: Ext.baseCSSPrefix + 'html-editor-tip' }, superscript : { title: 'Superscript', text: 'Change font size to superscript.', cls: Ext.baseCSSPrefix + 'html-editor-tip' }, subscript : { title: 'Subscript', text: 'Change font size to subscript.', cls: Ext.baseCSSPrefix + 'html-editor-tip' }, inserthorizontalrule : { title: 'Insert horizontal rule', text: 'Insert horizontal rule at the cursor.', cls: Ext.baseCSSPrefix + 'html-editor-tip' }, chars : { title: 'Special chars', text: 'Insert special characters.', cls: Ext.baseCSSPrefix + 'html-editor-tip' }, faceImages : { title: 'Face images', text: 'Insert Face Images.', cls: Ext.baseCSSPrefix + 'html-editor-tip' }, wordpaste : { title: 'Word paste', text: 'Clean the pasted text.', cls: Ext.baseCSSPrefix + 'html-editor-tip' }, images : { title: 'Images', text: 'Insert images.', cls: Ext.baseCSSPrefix + 'html-editor-tip' }, listFormatBlocks: { p: "Paragraph", h1: "Header 1", h2: "Header 2", h3: "Header 3", h4: "Header 4", h5: "Header 5", h6: "Header 6", address: "Address", pre: "Formatted" } } }) /** * This override is required to make the formatBlock plugin to work in IE and WebKit browsers. * The default behaviour was to insert <br> tags when Enter was pressed. We have to let the browser insert a new paragraph * to be able to change the format. */ if(Ext.isIE || Ext.isWebKit){ Ext.override(Ext.form.field.HtmlEditor, { fixKeys: function() { if (Ext.isIE) { return function(e){ var me = this, k = e.getKey(), doc = me.getDoc(), range, target; if (k === e.TAB) { e.stopEvent(); range = doc.selection.createRange(); if(range){ range.collapse(true); range.pasteHTML(' '); me.deferFocus(); } } }; } if (Ext.isOpera) { return function(e){ var me = this; if (e.getKey() === e.TAB) { e.stopEvent(); me.win.focus(); me.execCmd('InsertHTML',' '); me.deferFocus(); } }; } if (Ext.isWebKit) { return function(e){ var me = this, k = e.getKey(); if (k === e.TAB) { e.stopEvent(); me.execCmd('InsertText','\t'); me.deferFocus(); } }; } return null; }() }) }
ux-all.css
/* Ext.ux.form.plugins.HtmlEditor */ .x-html-editor-tb .x-edit-removehtml, .x-menu-item img.x-edit-removehtml { background-image: url("../themes/images/default/editor/tb-plugins.gif"); background-position: 0 0; } .x-html-editor-tb .x-edit-removeformat, .x-menu-item img.x-edit-removeformat { background-image: url("../themes/images/default/editor/tb-plugins.gif"); background-position: -16px 0; } .x-html-editor-tb .x-edit-inserttable, .x-menu-item img.x-edit-inserttable { background-image: url("../themes/images/default/editor/tb-plugins.gif"); background-position: -32px 0; } .x-html-editor-tb .x-edit-indent, .x-menu-item img.x-edit-indent { background-image: url("../themes/images/default/editor/tb-plugins.gif"); background-position: -48px 0; } .x-html-editor-tb .x-edit-outdent, .x-menu-item img.x-edit-outdent { background-image: url("../themes/images/default/editor/tb-plugins.gif"); background-position: -64px 0; } .x-html-editor-tb .x-edit-superscript, .x-menu-item img.x-edit-superscript { background-image: url("../themes/images/default/editor/tb-plugins.gif"); background-position: -80px 0; } .x-html-editor-tb .x-edit-subscript, .x-menu-item img.x-edit-subscript { background-image: url("../themes/images/default/editor/tb-plugins.gif"); background-position: -96px 0; } .x-html-editor-tb .x-edit-inserthorizontalrule, .x-menu-item img.x-edit-inserthorizontalrule { background-image: url("../themes/images/default/editor/tb-plugins.gif"); background-position: -112px 0; } .x-html-editor-tb .x-edit-chars, .x-menu-item img.x-edit-chars { background-image: url("../themes/images/default/editor/tb-plugins.gif"); background-position: -128px 0; } .x-html-editor-tb .x-edit-wordpaste, .x-menu-item img.x-edit-wordpaste { background-image: url("../themes/images/default/editor/tb-plugins.gif"); background-position: -144px 0; } .x-html-editor-tb .x-edit-images, .x-menu-item img.x-edit-images { background-image: url("../themes/images/default/editor/tb-plugins.gif"); background-position: -160px 0; } .x-html-editor-tb .x-edit-undo, .x-menu-item img.x-edit-undo { background-image: url("../themes/images/default/editor/tb-plugins.gif"); background-position: -176px 0; } .x-html-editor-tb .x-edit-redo, .x-menu-item img.x-edit-redo { background-image: url("../themes/images/default/editor/tb-plugins.gif"); background-position: -192px 0; } .char-item { float: left; border: 1px solid #99BBE8; margin: 3px; text-align: center; vertical-align: middle; font-size: 14px; color: #15428B; cursor: pointer; } .char-item.x-item-selected { background-color: #777; } .char-over { border: 1px solid #15428B; background-color:#d0def0; }
使用
<script type="text/javascript" src="scripts/plugins/htmleditor/js/Ext.ux.form.plugin.HtmlEditor.js"></script>
<link rel="stylesheet" href="scripts/plugins/htmleditor/css/ux-all.css" type="text/css"></link>
{ xtype: 'htmleditor', name: 'bio', id: 'bio', plugins: [ Ext.create('Ext.ux.form.plugin.HtmlEditor',{ enableAll: true }) ], fieldLabel: 'Biography', height: 200, anchor: '100%' }
- 1楼caodegao昨天 16:37
- 大家感觉有帮助就回个帖鼓励一下,太需要鼓励了,也让小弟有个动力继续发帖,别带走也不支声(会很伤心的).