感谢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%'
}