dangreenfield
29 Nov 2007, 2:56 AM
I have created new extensions to the Ext.form.HtmlEditor and Ext.Toolbar components that I believe are better suited for handling plugins. As Jack et al have commented that the HtmlEditor was designed to be 'lightweight', it follows that the tool should at least be fit for the easy integration of plugins. I found it wasn't, so I have extended it to cater for them.
The code attached is only a suggestion of the way the editor should function in future. I'm open to anyone suggesting it should work another way. Use it if you feel it is of value, or not, if you don't.
I'll start with the toolbar. I called it Ext.ux.HTMLEditorToolbar, as I felt that the regular toolbar worked sufficiently for non-editor needs, so I wrote this one solely with the editor in mind.
The main problem with the existing toolbar was that the editor added the tools directly into it, both sequentially and at the time of render. I wanted the tools to be added prior to the toolbar being rendered. I also wanted the tools to be inserted, if needed, rather than just added to the end.
// Ext.ux.HTMLEditorToolbar
// extension of Ext.Toolbar to cater for extensibility
Ext.ux.HTMLEditorToolbar = Ext.extend(Ext.Toolbar, {
// overrides Ext.Toolbar.initComponent
// first function to be called upon creation of toolbar
initComponent: function() {
// call Ext.Toolbar.initComponent
Ext.ux.HTMLEditorToolbar.superclass.initComponent.call(this);
// unable to use existing items collection for pre-render
// configuration as it's updated by Ext.Toolbar during render
this.tools = new Ext.util.MixedCollection(false, function(tool) {
return tool.itemId || tool.id || Ext.id();
});
},
// add tools (pre-render)
addTools: function(tools) {
tools = (tools instanceof Array) ? tools : [tools];
for (var i = 0, len = tools.length; i < len; i++) {
this.tools.add(tools[i]);
}
},
// insert tools (pre-render)
insertTools: function(index, tools) {
tools = (tools instanceof Array) ? tools : [tools];
for (var i = 0, len = tools.length; i < len; i++) {
this.tools.insert(index + i, tools[i]);
}
},
// insert tools before another tool (pre-render)
insertToolsBefore: function(itemId, tools) {
var index = this.tools.indexOfKey(itemId);
this.insertTools(index, tools);
},
// insert tools after another tool (pre-render)
insertToolsAfter: function(itemId, tools) {
var index = this.tools.indexOfKey(itemId) + 1;
this.insertTools(index, tools);
},
// render tools (performed after tools/plugins have been configured/reordered)
renderTool: function(tool) {
// cater for new tbcombo component
// created to split configuration from render
if (typeof tool == "object" && tool.xtype && tool.xtype == "tbcombo") {
// not catered for in Ext.Toolbar.add function
// as it defaults to addField instead of addItem
this.addItem(Ext.ComponentMgr.create(tool));
}
else {
// else use existing Ext.Toolbar.add function
// to render tools
this.add(tool);
}
},
// overrides Ext.Toolbar.onRender
onRender: function(ct, position) {
// call Ext.Toolbar.onRender
Ext.ux.HTMLEditorToolbar.superclass.onRender.call(this, ct, position);
// loop through pre-configured/reordered tools and render each accordingly
this.tools.each(this.renderTool, this);
}
});
I also added a new component to the toolbar to handle comboboxes. The existing fontnames combobox could only be created at the time of render, so adding a new component meant that I could configure it in memory prior to it being rendered to the toolbar.
// Ext.ux.HTMLEditorToolbar.ComboBox
// created to handle the pre-configuration of a combobox (pre-render)
Ext.ux.HTMLEditorToolbar.ComboBox = function(config) {
Ext.apply(this, config);
// create combobox in memory before render
var selEl = document.createElement("select");
selEl.className = this.cls;
for (var i = 0, len = this.opts.length; i < len; i++) {
var opt = this.opts[i];
var optEl = document.createElement('option');
optEl.text = opt.text;
optEl.value = opt.value;
if (opt.selected) {
optEl.selected = true;
this.defaultValue = opt.value;
}
selEl.options.add(optEl);
}
if (! this.defaultValue) {
this.defaultValue = this.opts[0].value;
}
// call Ext.Toolbar.Item constructor passing combobox
Ext.ux.HTMLEditorToolbar.ComboBox.superclass.constructor.call(this, selEl);
}
// Ext.ux.HTMLEditorToolbar.ComboBox
// extension of Ext.Toolbar.Item
Ext.extend(Ext.ux.HTMLEditorToolbar.ComboBox, Ext.Toolbar.Item, {
// overrides Ext.Toolbar.Item.render
render: function(td) {
// call Ext.Toolbar.Item.render
Ext.ux.HTMLEditorToolbar.ComboBox.superclass.render.call(this, td);
// add handler for combobox change event
Ext.EventManager.on(this.el, 'change', this.handler, this.scope);
}
});
// register Ext.ux.HTMLEditorToolbar.ComboBox as a new component
Ext.ComponentMgr.registerType('tbcombo', Ext.ux.HTMLEditorToolbar.ComboBox);
Now, to the HtmlEditor. I have called the extended version Ext.ux.HTMLEditor. It has changed significantly.
I no longer use the enable... flags to include the tools, but use an array, called toolbarItems, that can be overwritten in the config file (I also included a config array called toolbarItemExcludes, as an alternative, to exclude tools contained in the standard toolbarItems array). Using this array means that you can display the tools in any order.
Once the editor is initialized, the toolbar will have all the tools configured, meaning that any plugins can then manipulate the tools list by adding or inserting new tools as needed. This can all happen prior to the toolbar being rendered.
// Ext.ux.HTMLEditor
// extends Ext.form.HtmlEditor to provide extensibility
Ext.ux.HTMLEditor = Ext.extend(Ext.form.HtmlEditor, {
// using the enable... flags to define content meant that items
// were always added in the same order.
// using the toolbarItems list instead allows the user to override
// the order of items, and even exclude items not wanted.
// the enable... flags are now no longer used
toolbarItems: [
'fonts',
'allformats',
'allfontsizes',
'allcolors',
'allalignments',
'alllinks',
'alllists',
'sourceedit'
],
// as an alternative, the toolbarItemExcludes list can be used to
// exclude items from the toolbarItem list
toolbarItemExcludes: [],
// overrides Ext.form.HtmlEditor.initComponent
// first function to be called upon creation of the editor
initComponent: function() {
// call Ext.form.HtmlEditor.initComponent
Ext.ux.HTMLEditor.superclass.initComponent.call(this);
// add important event missing from Ext.form.HtmlEditor
this.addEvents({
editorevent: true
});
// remove any toolbarItemExcludes from the toolbarItems array
for (var i = 0, iMax = this.toolbarItemExcludes.length; i < iMax; i++) {
var item = this.toolbarItemExcludes[i].toLowerCase();
for (var j = 0, jMax = this.toolbarItems.length; j < jMax; j++) {
if (this.toolbarItems[j] == item) {
this.toolbarItems.splice(j, 1);
break;
}
}
}
// create the editor toolbar
this.tb = new Ext.ux.HTMLEditorToolbar();
// create the toolbar items
this.createTools(this.toolbarItems);
},
// overrides Ext.form.HtmlEditor.createFontOptions
createFontOptions: function() {
var opts = [], ffs = this.fontFamilies, ff;
for (var i = 0, len = ffs.length; i < len; i++) {
ff = ffs[i];
fflc = ff.toLowerCase();
var opt = {text: ff, value: fflc};
if (fflc == this.defaultFont) opt.selected = true;
opts.push(opt);
}
return opts;
},
// create default button config
btn: function(id, toggle, queryState, handler) {
return {
itemId: id,
cls: 'x-btn-icon x-edit-' + id,
enableToggle: toggle !== false,
queryState: queryState !== false,
handler: handler || this.relayBtnCmd,
scope: this,
clickEvent: 'mousedown',
tooltip: this.buttonTips[id] || undefined,
tabIndex: -1
};
},
// create known tools based on the passed item list (initially
// from the toolbarItems list) and add it to the tools collection.
// this function allows random tool allocation as opposed
// to the old version that added tools sequentially
createTools: function(toolbarItems) {
// convert single items to a list
toolbarItems = (toolbarItems instanceof Array) ? toolbarItems : [toolbarItems];
// loop through the item list
for (var i = 0, len = toolbarItems.length; i < len; i++) {
//add the item to the toolbar
var item = toolbarItems[i];
switch (item) {
// add the fonts combobox
case 'fonts':
if (! Ext.isSafari) {
this.tb.addTools({
itemId: 'fontname',
xtype: 'tbcombo',
cls: 'x-font-select',
opts: this.createFontOptions(),
queryValue: true,
handler: function(event, el) {
this.relayCmd('fontname', el.value);
this.deferFocus();
},
scope: this
});
}
break;
// add the bold button
case 'bold':
this.tb.addTools(this.btn('bold'));
break;
// add the italic button
case 'italic':
this.tb.addTools(this.btn('italic'));
break;
// add the underline button
case 'underline':
this.tb.addTools(this.btn('underline'));
break;
// add all format buttons (with a leading separator)
case 'allformats':
this.createTools(['-', 'bold', 'italic', 'underline']);
break;
// add the increasefontsize button
case 'increasefontsize':
this.tb.addTools(this.btn('increasefontsize', false, false, this.adjustFont));
break;
// add the decreasefontsize button
case 'decreasefontsize':
this.tb.addTools(this.btn('decreasefontsize', false, false, this.adjustFont));
break;
// add both fontsize buttons (with a leading separator)
case 'allfontsizes':
this.createTools(['-', 'increasefontsize', 'decreasefontsize']);
break;
// add the forecolor button and associated menu
case 'forecolor':
this.tb.addTools({
itemId: 'forecolor',
cls: 'x-btn-icon x-edit-forecolor',
clickEvent: 'mousedown',
tooltip: this.buttonTips['forecolor'],
tabIndex: -1,
menu: new Ext.menu.ColorMenu({
allowReselect: true,
focus: Ext.emptyFn,
value: '000000',
plain: true,
selectHandler: function(cp, color) {
this.execCmd('forecolor', Ext.isSafari || Ext.isIE ? '#' + color : color);
this.deferFocus();
},
scope: this,
clickEvent:'mousedown'
})
});
break;
// add the backcolor button and associated menu
case 'backcolor':
this.tb.addTools({
itemId: 'backcolor',
cls: 'x-btn-icon x-edit-backcolor',
clickEvent: 'mousedown',
tooltip: this.buttonTips['backcolor'],
tabIndex: -1,
menu: new Ext.menu.ColorMenu({
focus: Ext.emptyFn,
value: 'FFFFFF',
plain: true,
allowReselect: true,
selectHandler: function(cp, color) {
if (Ext.isGecko) {
this.execCmd('useCSS', false);
this.execCmd('hilitecolor', color);
this.execCmd('useCSS', true);
this.deferFocus();
}
else {
this.execCmd(Ext.isOpera ? 'hilitecolor' : 'backcolor',
Ext.isSafari || Ext.isIE ? '#' + color : color);
this.deferFocus();
}
},
scope: this,
clickEvent: 'mousedown'
})
});
break;
// add both color buttons (with a leading separator)
case 'allcolors':
this.createTools(['-', 'forecolor', 'backcolor']);
break;
// add the justifyleft button
case 'justifyleft':
this.tb.addTools(this.btn('justifyleft'));
break;
// add the justifycenter button
case 'justifycenter':
this.tb.addTools(this.btn('justifycenter'));
break;
// add the justifyright button
case 'justifyright':
this.tb.addTools(this.btn('justifyright'));
break;
// add all alignment buttons (with a leading separator)
case 'allalignments':
this.createTools(['-', 'justifyleft', 'justifycenter', 'justifyright']);
break;
// add the link button
case 'link':
if (! Ext.isSafari) {
this.tb.addTools(this.btn('createlink', false, false, this.createLink));
}
break;
// add the link button (with a leading separator)
case 'alllinks':
if (! Ext.isSafari) {
this.createTools(['-', 'link']);
}
break;
// add the orderedlist button
case 'orderedlist':
if (! Ext.isSafari) {
this.tb.addTools(this.btn('insertorderedlist'));
}
break;
// add the unorderedlist button
case 'unorderedlist':
if (! Ext.isSafari) {
this.tb.addTools(this.btn('insertunorderedlist'));
}
break;
// add both list buttons (with a leading separator)
case 'alllists':
if (! Ext.isSafari) {
this.createTools(['-', 'orderedlist', 'unorderedlist']);
}
break;
// add the sourceedit button
case 'sourceedit':
if (! Ext.isSafari) {
this.tb.addTools(this.btn('sourceedit', true, false, function(btn) {
this.toggleSourceEdit(btn.pressed);
}));
}
break;
// allows for '-', 'separator', ' ', '->', labels, or other item types
default:
this.tb.addTools(item);
}
}
},
// overrides Ext.form.HtmlEditor.createToolbar
// most functionality has been removed as this is called
// upon render
createToolbar: function() {
// render toolbar
this.tb.render(this.wrap.dom.firstChild);
// inherited
this.tb.el.on('click', function(e) {
e.preventDefault();
});
},
// overrides Ext.form.HtmlEditor.getDocMarkup
// provides ability to include stylesheets in the editor document
// created by bpjohnson (see http://extjs.com/forum/showthread.php?t=9588)
getDocMarkup: function() {
var markup = '<html><head><style type="text/css">body{border:0;margin:0;padding:3px;height:98%;cursor:text;}</style>';
if (this.styles) {
for (var i = 0; i < this.styles.length; i++) {
markup = markup + '<link rel="stylesheet" type="text/css" href="' + this.styles[i] + '" />';
}
}
markup = markup + '</head><body></body></html>';
return markup;
},
// overrides Ext.form.HtmlEditor.onEditorEvent
onEditorEvent: function(e) {
// call Ext.form.HtmlEditor.onEditorEvent
Ext.ux.HTMLEditor.superclass.onEditorEvent.call(this, e);
// fire new editorevent to tell plugins that an event occurred
// in the editor.
// this saves plugins from having to monitor multiple events
// i.e. 'click', 'keyup', etc.
this.fireEvent('editorevent', this, e);
},
// overrides Ext.form.HtmlEditor.updateToolbar
// does not call superclass function as much of it was no
// longer needed, but duplicates some code
updateToolbar: function() {
// inherited
if (! this.activated) {
this.onFirstFocus();
return;
}
// loop through toolbar items and update status based on
// query values return from the browser (if configured)
this.tb.items.each(function(item) {
if (item.queryState) {
item.toggle(this.doc.queryCommandState(item.itemId));
}
else if (item.queryEnabled) {
item.setDisabled(! this.doc.queryCommandEnabled(item.itemId));
}
else if (item.xtype = "tbcombo" && item.queryValue) {
var value = (this.doc.queryCommandValue(item.itemId) || item.defaultValue).toLowerCase();
if (value != item.el.value) {
item.el.value = value;
}
}
}, this);
// inherited
Ext.menu.MenuMgr.hideAll();
// inherited
this.syncValue();
}
});
I have attached a demo of the functionality, including an updated example of my HTMLEditorStyles plugin, which now utilises the new functionality.
For those who requested a live demo, click here (http://coder.4realhost.com/htmleditor/).
The code attached is only a suggestion of the way the editor should function in future. I'm open to anyone suggesting it should work another way. Use it if you feel it is of value, or not, if you don't.
I'll start with the toolbar. I called it Ext.ux.HTMLEditorToolbar, as I felt that the regular toolbar worked sufficiently for non-editor needs, so I wrote this one solely with the editor in mind.
The main problem with the existing toolbar was that the editor added the tools directly into it, both sequentially and at the time of render. I wanted the tools to be added prior to the toolbar being rendered. I also wanted the tools to be inserted, if needed, rather than just added to the end.
// Ext.ux.HTMLEditorToolbar
// extension of Ext.Toolbar to cater for extensibility
Ext.ux.HTMLEditorToolbar = Ext.extend(Ext.Toolbar, {
// overrides Ext.Toolbar.initComponent
// first function to be called upon creation of toolbar
initComponent: function() {
// call Ext.Toolbar.initComponent
Ext.ux.HTMLEditorToolbar.superclass.initComponent.call(this);
// unable to use existing items collection for pre-render
// configuration as it's updated by Ext.Toolbar during render
this.tools = new Ext.util.MixedCollection(false, function(tool) {
return tool.itemId || tool.id || Ext.id();
});
},
// add tools (pre-render)
addTools: function(tools) {
tools = (tools instanceof Array) ? tools : [tools];
for (var i = 0, len = tools.length; i < len; i++) {
this.tools.add(tools[i]);
}
},
// insert tools (pre-render)
insertTools: function(index, tools) {
tools = (tools instanceof Array) ? tools : [tools];
for (var i = 0, len = tools.length; i < len; i++) {
this.tools.insert(index + i, tools[i]);
}
},
// insert tools before another tool (pre-render)
insertToolsBefore: function(itemId, tools) {
var index = this.tools.indexOfKey(itemId);
this.insertTools(index, tools);
},
// insert tools after another tool (pre-render)
insertToolsAfter: function(itemId, tools) {
var index = this.tools.indexOfKey(itemId) + 1;
this.insertTools(index, tools);
},
// render tools (performed after tools/plugins have been configured/reordered)
renderTool: function(tool) {
// cater for new tbcombo component
// created to split configuration from render
if (typeof tool == "object" && tool.xtype && tool.xtype == "tbcombo") {
// not catered for in Ext.Toolbar.add function
// as it defaults to addField instead of addItem
this.addItem(Ext.ComponentMgr.create(tool));
}
else {
// else use existing Ext.Toolbar.add function
// to render tools
this.add(tool);
}
},
// overrides Ext.Toolbar.onRender
onRender: function(ct, position) {
// call Ext.Toolbar.onRender
Ext.ux.HTMLEditorToolbar.superclass.onRender.call(this, ct, position);
// loop through pre-configured/reordered tools and render each accordingly
this.tools.each(this.renderTool, this);
}
});
I also added a new component to the toolbar to handle comboboxes. The existing fontnames combobox could only be created at the time of render, so adding a new component meant that I could configure it in memory prior to it being rendered to the toolbar.
// Ext.ux.HTMLEditorToolbar.ComboBox
// created to handle the pre-configuration of a combobox (pre-render)
Ext.ux.HTMLEditorToolbar.ComboBox = function(config) {
Ext.apply(this, config);
// create combobox in memory before render
var selEl = document.createElement("select");
selEl.className = this.cls;
for (var i = 0, len = this.opts.length; i < len; i++) {
var opt = this.opts[i];
var optEl = document.createElement('option');
optEl.text = opt.text;
optEl.value = opt.value;
if (opt.selected) {
optEl.selected = true;
this.defaultValue = opt.value;
}
selEl.options.add(optEl);
}
if (! this.defaultValue) {
this.defaultValue = this.opts[0].value;
}
// call Ext.Toolbar.Item constructor passing combobox
Ext.ux.HTMLEditorToolbar.ComboBox.superclass.constructor.call(this, selEl);
}
// Ext.ux.HTMLEditorToolbar.ComboBox
// extension of Ext.Toolbar.Item
Ext.extend(Ext.ux.HTMLEditorToolbar.ComboBox, Ext.Toolbar.Item, {
// overrides Ext.Toolbar.Item.render
render: function(td) {
// call Ext.Toolbar.Item.render
Ext.ux.HTMLEditorToolbar.ComboBox.superclass.render.call(this, td);
// add handler for combobox change event
Ext.EventManager.on(this.el, 'change', this.handler, this.scope);
}
});
// register Ext.ux.HTMLEditorToolbar.ComboBox as a new component
Ext.ComponentMgr.registerType('tbcombo', Ext.ux.HTMLEditorToolbar.ComboBox);
Now, to the HtmlEditor. I have called the extended version Ext.ux.HTMLEditor. It has changed significantly.
I no longer use the enable... flags to include the tools, but use an array, called toolbarItems, that can be overwritten in the config file (I also included a config array called toolbarItemExcludes, as an alternative, to exclude tools contained in the standard toolbarItems array). Using this array means that you can display the tools in any order.
Once the editor is initialized, the toolbar will have all the tools configured, meaning that any plugins can then manipulate the tools list by adding or inserting new tools as needed. This can all happen prior to the toolbar being rendered.
// Ext.ux.HTMLEditor
// extends Ext.form.HtmlEditor to provide extensibility
Ext.ux.HTMLEditor = Ext.extend(Ext.form.HtmlEditor, {
// using the enable... flags to define content meant that items
// were always added in the same order.
// using the toolbarItems list instead allows the user to override
// the order of items, and even exclude items not wanted.
// the enable... flags are now no longer used
toolbarItems: [
'fonts',
'allformats',
'allfontsizes',
'allcolors',
'allalignments',
'alllinks',
'alllists',
'sourceedit'
],
// as an alternative, the toolbarItemExcludes list can be used to
// exclude items from the toolbarItem list
toolbarItemExcludes: [],
// overrides Ext.form.HtmlEditor.initComponent
// first function to be called upon creation of the editor
initComponent: function() {
// call Ext.form.HtmlEditor.initComponent
Ext.ux.HTMLEditor.superclass.initComponent.call(this);
// add important event missing from Ext.form.HtmlEditor
this.addEvents({
editorevent: true
});
// remove any toolbarItemExcludes from the toolbarItems array
for (var i = 0, iMax = this.toolbarItemExcludes.length; i < iMax; i++) {
var item = this.toolbarItemExcludes[i].toLowerCase();
for (var j = 0, jMax = this.toolbarItems.length; j < jMax; j++) {
if (this.toolbarItems[j] == item) {
this.toolbarItems.splice(j, 1);
break;
}
}
}
// create the editor toolbar
this.tb = new Ext.ux.HTMLEditorToolbar();
// create the toolbar items
this.createTools(this.toolbarItems);
},
// overrides Ext.form.HtmlEditor.createFontOptions
createFontOptions: function() {
var opts = [], ffs = this.fontFamilies, ff;
for (var i = 0, len = ffs.length; i < len; i++) {
ff = ffs[i];
fflc = ff.toLowerCase();
var opt = {text: ff, value: fflc};
if (fflc == this.defaultFont) opt.selected = true;
opts.push(opt);
}
return opts;
},
// create default button config
btn: function(id, toggle, queryState, handler) {
return {
itemId: id,
cls: 'x-btn-icon x-edit-' + id,
enableToggle: toggle !== false,
queryState: queryState !== false,
handler: handler || this.relayBtnCmd,
scope: this,
clickEvent: 'mousedown',
tooltip: this.buttonTips[id] || undefined,
tabIndex: -1
};
},
// create known tools based on the passed item list (initially
// from the toolbarItems list) and add it to the tools collection.
// this function allows random tool allocation as opposed
// to the old version that added tools sequentially
createTools: function(toolbarItems) {
// convert single items to a list
toolbarItems = (toolbarItems instanceof Array) ? toolbarItems : [toolbarItems];
// loop through the item list
for (var i = 0, len = toolbarItems.length; i < len; i++) {
//add the item to the toolbar
var item = toolbarItems[i];
switch (item) {
// add the fonts combobox
case 'fonts':
if (! Ext.isSafari) {
this.tb.addTools({
itemId: 'fontname',
xtype: 'tbcombo',
cls: 'x-font-select',
opts: this.createFontOptions(),
queryValue: true,
handler: function(event, el) {
this.relayCmd('fontname', el.value);
this.deferFocus();
},
scope: this
});
}
break;
// add the bold button
case 'bold':
this.tb.addTools(this.btn('bold'));
break;
// add the italic button
case 'italic':
this.tb.addTools(this.btn('italic'));
break;
// add the underline button
case 'underline':
this.tb.addTools(this.btn('underline'));
break;
// add all format buttons (with a leading separator)
case 'allformats':
this.createTools(['-', 'bold', 'italic', 'underline']);
break;
// add the increasefontsize button
case 'increasefontsize':
this.tb.addTools(this.btn('increasefontsize', false, false, this.adjustFont));
break;
// add the decreasefontsize button
case 'decreasefontsize':
this.tb.addTools(this.btn('decreasefontsize', false, false, this.adjustFont));
break;
// add both fontsize buttons (with a leading separator)
case 'allfontsizes':
this.createTools(['-', 'increasefontsize', 'decreasefontsize']);
break;
// add the forecolor button and associated menu
case 'forecolor':
this.tb.addTools({
itemId: 'forecolor',
cls: 'x-btn-icon x-edit-forecolor',
clickEvent: 'mousedown',
tooltip: this.buttonTips['forecolor'],
tabIndex: -1,
menu: new Ext.menu.ColorMenu({
allowReselect: true,
focus: Ext.emptyFn,
value: '000000',
plain: true,
selectHandler: function(cp, color) {
this.execCmd('forecolor', Ext.isSafari || Ext.isIE ? '#' + color : color);
this.deferFocus();
},
scope: this,
clickEvent:'mousedown'
})
});
break;
// add the backcolor button and associated menu
case 'backcolor':
this.tb.addTools({
itemId: 'backcolor',
cls: 'x-btn-icon x-edit-backcolor',
clickEvent: 'mousedown',
tooltip: this.buttonTips['backcolor'],
tabIndex: -1,
menu: new Ext.menu.ColorMenu({
focus: Ext.emptyFn,
value: 'FFFFFF',
plain: true,
allowReselect: true,
selectHandler: function(cp, color) {
if (Ext.isGecko) {
this.execCmd('useCSS', false);
this.execCmd('hilitecolor', color);
this.execCmd('useCSS', true);
this.deferFocus();
}
else {
this.execCmd(Ext.isOpera ? 'hilitecolor' : 'backcolor',
Ext.isSafari || Ext.isIE ? '#' + color : color);
this.deferFocus();
}
},
scope: this,
clickEvent: 'mousedown'
})
});
break;
// add both color buttons (with a leading separator)
case 'allcolors':
this.createTools(['-', 'forecolor', 'backcolor']);
break;
// add the justifyleft button
case 'justifyleft':
this.tb.addTools(this.btn('justifyleft'));
break;
// add the justifycenter button
case 'justifycenter':
this.tb.addTools(this.btn('justifycenter'));
break;
// add the justifyright button
case 'justifyright':
this.tb.addTools(this.btn('justifyright'));
break;
// add all alignment buttons (with a leading separator)
case 'allalignments':
this.createTools(['-', 'justifyleft', 'justifycenter', 'justifyright']);
break;
// add the link button
case 'link':
if (! Ext.isSafari) {
this.tb.addTools(this.btn('createlink', false, false, this.createLink));
}
break;
// add the link button (with a leading separator)
case 'alllinks':
if (! Ext.isSafari) {
this.createTools(['-', 'link']);
}
break;
// add the orderedlist button
case 'orderedlist':
if (! Ext.isSafari) {
this.tb.addTools(this.btn('insertorderedlist'));
}
break;
// add the unorderedlist button
case 'unorderedlist':
if (! Ext.isSafari) {
this.tb.addTools(this.btn('insertunorderedlist'));
}
break;
// add both list buttons (with a leading separator)
case 'alllists':
if (! Ext.isSafari) {
this.createTools(['-', 'orderedlist', 'unorderedlist']);
}
break;
// add the sourceedit button
case 'sourceedit':
if (! Ext.isSafari) {
this.tb.addTools(this.btn('sourceedit', true, false, function(btn) {
this.toggleSourceEdit(btn.pressed);
}));
}
break;
// allows for '-', 'separator', ' ', '->', labels, or other item types
default:
this.tb.addTools(item);
}
}
},
// overrides Ext.form.HtmlEditor.createToolbar
// most functionality has been removed as this is called
// upon render
createToolbar: function() {
// render toolbar
this.tb.render(this.wrap.dom.firstChild);
// inherited
this.tb.el.on('click', function(e) {
e.preventDefault();
});
},
// overrides Ext.form.HtmlEditor.getDocMarkup
// provides ability to include stylesheets in the editor document
// created by bpjohnson (see http://extjs.com/forum/showthread.php?t=9588)
getDocMarkup: function() {
var markup = '<html><head><style type="text/css">body{border:0;margin:0;padding:3px;height:98%;cursor:text;}</style>';
if (this.styles) {
for (var i = 0; i < this.styles.length; i++) {
markup = markup + '<link rel="stylesheet" type="text/css" href="' + this.styles[i] + '" />';
}
}
markup = markup + '</head><body></body></html>';
return markup;
},
// overrides Ext.form.HtmlEditor.onEditorEvent
onEditorEvent: function(e) {
// call Ext.form.HtmlEditor.onEditorEvent
Ext.ux.HTMLEditor.superclass.onEditorEvent.call(this, e);
// fire new editorevent to tell plugins that an event occurred
// in the editor.
// this saves plugins from having to monitor multiple events
// i.e. 'click', 'keyup', etc.
this.fireEvent('editorevent', this, e);
},
// overrides Ext.form.HtmlEditor.updateToolbar
// does not call superclass function as much of it was no
// longer needed, but duplicates some code
updateToolbar: function() {
// inherited
if (! this.activated) {
this.onFirstFocus();
return;
}
// loop through toolbar items and update status based on
// query values return from the browser (if configured)
this.tb.items.each(function(item) {
if (item.queryState) {
item.toggle(this.doc.queryCommandState(item.itemId));
}
else if (item.queryEnabled) {
item.setDisabled(! this.doc.queryCommandEnabled(item.itemId));
}
else if (item.xtype = "tbcombo" && item.queryValue) {
var value = (this.doc.queryCommandValue(item.itemId) || item.defaultValue).toLowerCase();
if (value != item.el.value) {
item.el.value = value;
}
}
}, this);
// inherited
Ext.menu.MenuMgr.hideAll();
// inherited
this.syncValue();
}
});
I have attached a demo of the functionality, including an updated example of my HTMLEditorStyles plugin, which now utilises the new functionality.
For those who requested a live demo, click here (http://coder.4realhost.com/htmleditor/).