2
* Ext JS Library 3.0 RC2
3
* Copyright(c) 2006-2009, Ext JS, LLC.
6
* http://extjs.com/license
10
* @class Ext.form.HtmlEditor
11
* @extends Ext.form.Field
12
* Provides a lightweight HTML Editor component. Some toolbar features are not supported by Safari and will be
13
* automatically hidden when needed. These are noted in the config options where appropriate.
14
* <br><br>The editor's toolbar buttons have tooltips defined in the {@link #buttonTips} property, but they are not
15
* enabled by default unless the global {@link Ext.QuickTips} singleton is {@link Ext.QuickTips#init initialized}.
16
* <br><br><b>Note: The focus/blur and validation marking functionality inherited from Ext.form.Field is NOT
17
* supported by this editor.</b>
18
* <br><br>An Editor is a sensitive component that can't be used in all spots standard fields can be used. Putting an Editor within
19
* any element that has display set to 'none' can cause problems in Safari and Firefox due to their default iframe reloading bugs.
20
* <br><br>Example usage:
22
// Simple example rendered with default options:
23
Ext.QuickTips.init(); // enable tooltips
24
new Ext.form.HtmlEditor({
25
renderTo: Ext.getBody(),
30
// Passed via xtype into a container and with custom options:
31
Ext.QuickTips.init(); // enable tooltips
34
renderTo: Ext.getBody(),
42
enableAlignments: false
47
* Create a new HtmlEditor
48
* @param {Object} config
52
Ext.form.HtmlEditor = Ext.extend(Ext.form.Field, {
54
* @cfg {Boolean} enableFormat Enable the bold, italic and underline buttons (defaults to true)
58
* @cfg {Boolean} enableFontSize Enable the increase/decrease font size buttons (defaults to true)
60
enableFontSize : true,
62
* @cfg {Boolean} enableColors Enable the fore/highlight color buttons (defaults to true)
66
* @cfg {Boolean} enableAlignments Enable the left, center, right alignment buttons (defaults to true)
68
enableAlignments : true,
70
* @cfg {Boolean} enableLists Enable the bullet and numbered list buttons. Not available in Safari. (defaults to true)
74
* @cfg {Boolean} enableSourceEdit Enable the switch to source edit button. Not available in Safari. (defaults to true)
76
enableSourceEdit : true,
78
* @cfg {Boolean} enableLinks Enable the create link button. Not available in Safari. (defaults to true)
82
* @cfg {Boolean} enableFont Enable font selection. Not available in Safari. (defaults to true)
86
* @cfg {String} createLinkText The default text for the create link prompt
88
createLinkText : 'Please enter the URL for the link:',
90
* @cfg {String} defaultLinkValue The default value for the create link prompt (defaults to http:/ /)
92
defaultLinkValue : 'http:/'+'/',
94
* @cfg {Array} fontFamilies An array of available font families
103
defaultFont: 'tahoma',
105
* @cfg {String} defaultValue A default value to be put into the editor to resolve focus issues (defaults to ​, in Opera).
107
defaultValue: Ext.isOpera ? ' ' : '​',
109
// private properties
110
validationEvent : false,
114
sourceEditMode : false,
115
onFocus : Ext.emptyFn,
118
defaultAutoCreate : {
120
style:"width:500px;height:300px;",
125
initComponent : function(){
129
* Fires when the editor is fully initialized (including the iframe)
130
* @param {HtmlEditor} this
135
* Fires when the editor is first receives the focus. Any insertion must wait
136
* until after this event.
137
* @param {HtmlEditor} this
142
* Fires before the textarea is updated with content from the editor iframe. Return false
143
* to cancel the sync.
144
* @param {HtmlEditor} this
145
* @param {String} html
150
* Fires before the iframe editor is updated with content from the textarea. Return false
151
* to cancel the push.
152
* @param {HtmlEditor} this
153
* @param {String} html
158
* Fires when the textarea is updated with content from the editor iframe.
159
* @param {HtmlEditor} this
160
* @param {String} html
165
* Fires when the iframe editor is updated with content from the textarea.
166
* @param {HtmlEditor} this
167
* @param {String} html
171
* @event editmodechange
172
* Fires when the editor switches edit modes
173
* @param {HtmlEditor} this
174
* @param {Boolean} sourceEdit True if source edit, false if standard editing.
181
createFontOptions : function(){
182
var buf = [], fs = this.fontFamilies, ff, lc;
183
for(var i = 0, len = fs.length; i< len; i++){
185
lc = ff.toLowerCase();
187
'<option value="',lc,'" style="font-family:',ff,';"',
188
(this.defaultFont == lc ? ' selected="true">' : '>'),
197
* Protected method that will not generally be called directly. It
198
* is called when the editor creates its toolbar. Override this method if you need to
199
* add custom toolbar buttons.
200
* @param {HtmlEditor} editor
202
createToolbar : function(editor){
204
var tipsEnabled = Ext.QuickTips && Ext.QuickTips.isEnabled();
206
function btn(id, toggle, handler){
210
iconCls: 'x-edit-'+id,
211
enableToggle:toggle !== false,
213
handler:handler||editor.relayBtnCmd,
214
clickEvent:'mousedown',
215
tooltip: tipsEnabled ? editor.buttonTips[id] || undefined : undefined,
221
var tb = new Ext.Toolbar({
222
renderTo:this.wrap.dom.firstChild
226
this.mon(tb.el, 'click', function(e){
230
if(this.enableFont && !Ext.isSafari2){
231
this.fontSelect = tb.el.createChild({
234
html: this.createFontOptions()
236
this.mon(this.fontSelect, 'change', function(){
237
var font = this.fontSelect.dom.value;
238
this.relayCmd('fontname', font);
248
if(this.enableFormat){
256
if(this.enableFontSize){
259
btn('increasefontsize', false, this.adjustFont),
260
btn('decreasefontsize', false, this.adjustFont)
264
if(this.enableColors){
269
iconCls: 'x-edit-forecolor',
270
clickEvent:'mousedown',
271
tooltip: tipsEnabled ? editor.buttonTips.forecolor || undefined : undefined,
273
menu : new Ext.menu.ColorMenu({
280
select: function(cp, color){
281
this.execCmd('forecolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
285
clickEvent:'mousedown'
290
iconCls: 'x-edit-backcolor',
291
clickEvent:'mousedown',
292
tooltip: tipsEnabled ? editor.buttonTips.backcolor || undefined : undefined,
294
menu : new Ext.menu.ColorMenu({
301
select: function(cp, color){
303
this.execCmd('useCSS', false);
304
this.execCmd('hilitecolor', color);
305
this.execCmd('useCSS', true);
308
this.execCmd(Ext.isOpera ? 'hilitecolor' : 'backcolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
313
clickEvent:'mousedown'
319
if(this.enableAlignments){
323
btn('justifycenter'),
329
if(this.enableLinks){
332
btn('createlink', false, this.createLink)
336
if(this.enableLists){
339
btn('insertorderedlist'),
340
btn('insertunorderedlist')
343
if(this.enableSourceEdit){
346
btn('sourceedit', true, function(btn){
347
this.toggleSourceEdit(!this.sourceEditMode);
357
* Protected method that will not generally be called directly. It
358
* is called when the editor initializes the iframe with HTML contents. Override this method if you
359
* want to change the initialization markup of the iframe (e.g. to add stylesheets).
361
getDocMarkup : function(){
362
return '<html><head><style type="text/css">body{border:0;margin:0;padding:3px;height:98%;cursor:text;}</style></head><body></body></html>';
366
getEditorBody : function(){
367
return this.doc.body || this.doc.documentElement;
372
return Ext.isIE ? this.getWin().document : (this.iframe.contentDocument || this.getWin().document);
377
return Ext.isIE ? this.iframe.contentWindow : window.frames[this.iframe.name];
381
onRender : function(ct, position){
382
Ext.form.HtmlEditor.superclass.onRender.call(this, ct, position);
383
this.el.dom.style.border = '0 none';
384
this.el.dom.setAttribute('tabIndex', -1);
385
this.el.addClass('x-hidden');
386
if(Ext.isIE){ // fix IE 1px bogus margin
387
this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
389
this.wrap = this.el.wrap({
390
cls:'x-html-editor-wrap', cn:{cls:'x-html-editor-tb'}
393
this.createToolbar(this);
395
this.disableItems(true);
397
// this.tb.doLayout();
402
var sz = this.el.getSize();
403
this.setSize(sz.width, this.height || sz.height);
407
createIFrame: function(){
408
var iframe = document.createElement('iframe');
409
iframe.name = Ext.id();
410
iframe.frameBorder = '0';
411
iframe.src = Ext.isIE ? Ext.SSL_SECURE_URL : "javascript:;";
412
this.wrap.dom.appendChild(iframe);
414
this.iframe = iframe;
416
this.monitorTask = Ext.TaskMgr.start({
417
run: this.checkDesignMode,
423
initFrame : function(){
424
Ext.TaskMgr.stop(this.monitorTask);
425
this.doc = this.getDoc();
426
this.win = this.getWin();
429
this.doc.write(this.getDocMarkup());
432
var task = { // must defer to wait for browser to be ready
434
if(this.doc.body || this.doc.readyState == 'complete'){
435
Ext.TaskMgr.stop(task);
436
this.doc.designMode="on";
437
this.initEditor.defer(10, this);
444
Ext.TaskMgr.start(task);
448
checkDesignMode : function(){
449
if(this.wrap && this.wrap.dom.offsetWidth){
450
var doc = this.getDoc();
454
if(!doc.editorInitialized || String(doc.designMode).toLowerCase() != 'on'){
460
disableItems: function(disabled){
462
this.fontSelect.dom.disabled = disabled;
464
this.tb.items.each(function(item){
465
if(item.itemId != 'sourceedit'){
466
item.setDisabled(disabled);
472
onResize : function(w, h){
473
Ext.form.HtmlEditor.superclass.onResize.apply(this, arguments);
474
if(this.el && this.iframe){
475
if(typeof w == 'number'){
476
var aw = w - this.wrap.getFrameWidth('lr');
477
this.el.setWidth(this.adjustWidth('textarea', aw));
478
this.tb.setWidth(aw);
479
this.iframe.style.width = Math.max(aw, 0) + 'px';
481
if(typeof h == 'number'){
482
var ah = h - this.wrap.getFrameWidth('tb') - this.tb.el.getHeight();
483
this.el.setHeight(this.adjustWidth('textarea', ah));
484
this.iframe.style.height = Math.max(ah, 0) + 'px';
486
this.getEditorBody().style.height = Math.max((ah - (this.iframePad*2)), 0) + 'px';
493
* Toggles the editor between standard and source edit mode.
494
* @param {Boolean} sourceEdit (optional) True for source edit, false for standard
496
toggleSourceEdit : function(sourceEditMode){
497
if(sourceEditMode === undefined){
498
sourceEditMode = !this.sourceEditMode;
500
this.sourceEditMode = sourceEditMode === true;
501
var btn = this.tb.items.get('sourceedit');
502
if(btn.pressed !== this.sourceEditMode){
503
btn.toggle(this.sourceEditMode);
508
if(this.sourceEditMode){
509
this.disableItems(true);
511
this.iframe.className = 'x-hidden';
512
this.el.removeClass('x-hidden');
513
this.el.dom.removeAttribute('tabIndex');
516
if(this.initialized){
517
this.disableItems(false);
520
this.iframe.className = '';
521
this.el.addClass('x-hidden');
522
this.el.dom.setAttribute('tabIndex', -1);
525
var lastSize = this.lastSize;
527
delete this.lastSize;
528
this.setSize(lastSize);
530
this.fireEvent('editmodechange', this, this.sourceEditMode);
533
// private used internally
534
createLink : function(){
535
var url = prompt(this.createLinkText, this.defaultLinkValue);
536
if(url && url != 'http:/'+'/'){
537
this.relayCmd('createlink', url);
541
// private (for BoxComponent)
542
adjustSize : Ext.BoxComponent.prototype.adjustSize,
544
// private (for BoxComponent)
545
getResizeEl : function(){
549
// private (for BoxComponent)
550
getPositionEl : function(){
555
initEvents : function(){
556
this.originalValue = this.getValue();
560
* Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
563
markInvalid : Ext.emptyFn,
566
* Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
569
clearInvalid : Ext.emptyFn,
571
// docs inherit from Field
572
setValue : function(v){
573
Ext.form.HtmlEditor.superclass.setValue.call(this, v);
579
* Protected method that will not generally be called directly. If you need/want
580
* custom HTML cleanup, this is the method you should override.
581
* @param {String} html The HTML to be cleaned
582
* @return {String} The cleaned HTML
584
cleanHtml : function(html){
587
if(Ext.isWebKit){ // strip safari nonsense
588
html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
591
if(html == this.defaultValue){
598
* Protected method that will not generally be called directly. Syncs the contents
599
* of the editor iframe with the textarea.
601
syncValue : function(){
602
if(this.initialized){
603
var bd = this.getEditorBody();
604
var html = bd.innerHTML;
606
var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
607
var m = bs.match(/text-align:(.*?);/i);
609
html = '<div style="'+m[0]+'">' + html + '</div>';
612
html = this.cleanHtml(html);
613
if(this.fireEvent('beforesync', this, html) !== false){
614
this.el.dom.value = html;
615
this.fireEvent('sync', this, html);
620
//docs inherit from Field
621
getValue : function() {
622
this[this.sourceEditMode ? 'pushValue' : 'syncValue']();
623
return Ext.form.HtmlEditor.superclass.getValue.call(this);
627
* Protected method that will not generally be called directly. Pushes the value of the textarea
628
* into the iframe editor.
630
pushValue : function(){
631
if(this.initialized){
632
var v = this.el.dom.value;
633
if(!this.activated && v.length < 1){
634
v = this.defaultValue;
636
if(this.fireEvent('beforepush', this, v) !== false){
637
this.getEditorBody().innerHTML = v;
638
this.fireEvent('push', this, v);
644
deferFocus : function(){
645
this.focus.defer(10, this);
648
// docs inherit from Field
650
if(this.win && !this.sourceEditMode){
658
initEditor : function(){
659
//Destroying the component during/before initEditor can cause issues.
661
var dbody = this.getEditorBody();
662
var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
663
ss['background-attachment'] = 'fixed'; // w3c
664
dbody.bgProperties = 'fixed'; // ie
666
Ext.DomHelper.applyStyles(dbody, ss);
670
Ext.EventManager.removeAll(this.doc);
674
this.doc = this.getDoc();
676
Ext.EventManager.on(this.doc, {
677
'mousedown': this.onEditorEvent,
678
'dblclick': this.onEditorEvent,
679
'click': this.onEditorEvent,
680
'keyup': this.onEditorEvent,
686
Ext.EventManager.on(this.doc, 'keypress', this.applyCommand, this);
688
if(Ext.isIE || Ext.isWebKit || Ext.isOpera){
689
Ext.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
691
this.initialized = true;
692
this.fireEvent('initialize', this);
693
this.doc.editorInitialized = true;
699
onDestroy : function(){
700
if(this.monitorTask){
701
Ext.TaskMgr.stop(this.monitorTask);
704
Ext.destroy(this.tb);
706
this.wrap.dom.innerHTML = '';
711
this.el.removeAllListeners();
717
Ext.EventManager.removeAll(this.doc);
718
for (var prop in this.doc){
719
delete this.doc[prop];
723
this.purgeListeners();
727
onFirstFocus : function(){
728
this.activated = true;
729
this.disableItems(false);
730
if(Ext.isGecko){ // prevent silly gecko errors
732
var s = this.win.getSelection();
733
if(!s.focusNode || s.focusNode.nodeType != 3){
734
var r = s.getRangeAt(0);
735
r.selectNodeContents(this.getEditorBody());
740
this.execCmd('useCSS', true);
741
this.execCmd('styleWithCSS', false);
744
this.fireEvent('activate', this);
748
adjustFont: function(btn){
749
var adjust = btn.itemId == 'increasefontsize' ? 1 : -1;
751
var v = parseInt(this.doc.queryCommandValue('FontSize') || 2, 10);
752
if((Ext.isSafari && !Ext.isSafari2) || Ext.isChrome || Ext.isAir){
754
// 1 = 10px, 2 = 13px, 3 = 16px, 4 = 18px, 5 = 24px, 6 = 32px
768
v = v.constrain(1, 6);
770
if(Ext.isSafari){ // safari
773
v = Math.max(1, v+adjust) + (Ext.isSafari ? 'px' : 0);
775
this.execCmd('FontSize', v);
779
onEditorEvent : function(e){
780
this.updateToolbar();
785
* Protected method that will not generally be called directly. It triggers
786
* a toolbar update by reading the markup state of the current selection in the editor.
788
updateToolbar: function(){
795
var btns = this.tb.items.map, doc = this.doc;
797
if(this.enableFont && !Ext.isSafari2){
798
var name = (this.doc.queryCommandValue('FontName')||this.defaultFont).toLowerCase();
799
if(name != this.fontSelect.dom.value){
800
this.fontSelect.dom.value = name;
803
if(this.enableFormat){
804
btns.bold.toggle(doc.queryCommandState('bold'));
805
btns.italic.toggle(doc.queryCommandState('italic'));
806
btns.underline.toggle(doc.queryCommandState('underline'));
808
if(this.enableAlignments){
809
btns.justifyleft.toggle(doc.queryCommandState('justifyleft'));
810
btns.justifycenter.toggle(doc.queryCommandState('justifycenter'));
811
btns.justifyright.toggle(doc.queryCommandState('justifyright'));
813
if(!Ext.isSafari2 && this.enableLists){
814
btns.insertorderedlist.toggle(doc.queryCommandState('insertorderedlist'));
815
btns.insertunorderedlist.toggle(doc.queryCommandState('insertunorderedlist'));
818
Ext.menu.MenuMgr.hideAll();
824
relayBtnCmd : function(btn){
825
this.relayCmd(btn.itemId);
829
* Executes a Midas editor command on the editor document and performs necessary focus and
830
* toolbar updates. <b>This should only be called after the editor is initialized.</b>
831
* @param {String} cmd The Midas command
832
* @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
834
relayCmd : function(cmd, value){
837
this.execCmd(cmd, value);
838
this.updateToolbar();
843
* Executes a Midas editor command directly on the editor document.
844
* For visual commands, you should use {@link #relayCmd} instead.
845
* <b>This should only be called after the editor is initialized.</b>
846
* @param {String} cmd The Midas command
847
* @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
849
execCmd : function(cmd, value){
850
this.doc.execCommand(cmd, false, value === undefined ? null : value);
855
applyCommand : function(e){
857
var c = e.getCharCode(), cmd;
859
c = String.fromCharCode(c);
882
* Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
884
* @param {String} text
886
insertAtCursor : function(text){
892
var r = this.doc.selection.createRange();
899
}else if(Ext.isGecko || Ext.isOpera){
901
this.execCmd('InsertHTML', text);
903
}else if(Ext.isWebKit){
904
this.execCmd('InsertText', text);
910
fixKeys : function(){ // load time branching for fastest keydown performance
913
var k = e.getKey(), r;
916
r = this.doc.selection.createRange();
919
r.pasteHTML(' ');
922
}else if(k == e.ENTER){
923
r = this.doc.selection.createRange();
925
var target = r.parentElement();
926
if(!target || target.tagName.toLowerCase() != 'li'){
928
r.pasteHTML('<br />');
935
}else if(Ext.isOpera){
941
this.execCmd('InsertHTML',' ');
945
}else if(Ext.isWebKit){
950
this.execCmd('InsertText','\t');
958
* Returns the editor's toolbar. <b>This is only available after the editor has been rendered.</b>
959
* @return {Ext.Toolbar}
961
getToolbar : function(){
966
* Object collection of toolbar tooltips for the buttons in the editor. The key
967
* is the command id associated with that button and the value is a valid QuickTips object.
972
title: 'Bold (Ctrl+B)',
973
text: 'Make the selected text bold.',
974
cls: 'x-html-editor-tip'
977
title: 'Italic (Ctrl+I)',
978
text: 'Make the selected text italic.',
979
cls: 'x-html-editor-tip'
987
title: 'Bold (Ctrl+B)',
988
text: 'Make the selected text bold.',
989
cls: 'x-html-editor-tip'
992
title: 'Italic (Ctrl+I)',
993
text: 'Make the selected text italic.',
994
cls: 'x-html-editor-tip'
997
title: 'Underline (Ctrl+U)',
998
text: 'Underline the selected text.',
999
cls: 'x-html-editor-tip'
1001
increasefontsize : {
1003
text: 'Increase the font size.',
1004
cls: 'x-html-editor-tip'
1006
decreasefontsize : {
1007
title: 'Shrink Text',
1008
text: 'Decrease the font size.',
1009
cls: 'x-html-editor-tip'
1012
title: 'Text Highlight Color',
1013
text: 'Change the background color of the selected text.',
1014
cls: 'x-html-editor-tip'
1017
title: 'Font Color',
1018
text: 'Change the color of the selected text.',
1019
cls: 'x-html-editor-tip'
1022
title: 'Align Text Left',
1023
text: 'Align text to the left.',
1024
cls: 'x-html-editor-tip'
1027
title: 'Center Text',
1028
text: 'Center text in the editor.',
1029
cls: 'x-html-editor-tip'
1032
title: 'Align Text Right',
1033
text: 'Align text to the right.',
1034
cls: 'x-html-editor-tip'
1036
insertunorderedlist : {
1037
title: 'Bullet List',
1038
text: 'Start a bulleted list.',
1039
cls: 'x-html-editor-tip'
1041
insertorderedlist : {
1042
title: 'Numbered List',
1043
text: 'Start a numbered list.',
1044
cls: 'x-html-editor-tip'
1048
text: 'Make the selected text a hyperlink.',
1049
cls: 'x-html-editor-tip'
1052
title: 'Source Edit',
1053
text: 'Switch to source editing mode.',
1054
cls: 'x-html-editor-tip'
1058
// hide stuff that is not compatible
1076
* @cfg {String} fieldClass @hide
1079
* @cfg {String} focusClass @hide
1082
* @cfg {String} autoCreate @hide
1085
* @cfg {String} inputType @hide
1088
* @cfg {String} invalidClass @hide
1091
* @cfg {String} invalidText @hide
1094
* @cfg {String} msgFx @hide
1097
* @cfg {String} validateOnBlur @hide
1100
* @cfg {Boolean} allowDomMove @hide
1103
* @cfg {String} applyTo @hide
1106
* @cfg {String} autoHeight @hide
1109
* @cfg {String} autoWidth @hide
1112
* @cfg {String} cls @hide
1115
* @cfg {String} disabled @hide
1118
* @cfg {String} disabledClass @hide
1121
* @cfg {String} msgTarget @hide
1124
* @cfg {String} readOnly @hide
1127
* @cfg {String} style @hide
1130
* @cfg {String} validationDelay @hide
1133
* @cfg {String} validationEvent @hide
1136
* @cfg {String} tabIndex @hide
1139
* @property disabled
1143
* @method applyToMarkup
1163
* @method setDisabled
1171
Ext.reg('htmleditor', Ext.form.HtmlEditor);
b'\\ No newline at end of file'