X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/ee06f37b0f6f6d94cd05a6ffae556660f7c4a2bc..c930e9176a5a85509c5b0230e2bff5c22a591432:/src/widgets/form/HtmlEditor.js
diff --git a/src/widgets/form/HtmlEditor.js b/src/widgets/form/HtmlEditor.js
new file mode 100644
index 00000000..10f8e994
--- /dev/null
+++ b/src/widgets/form/HtmlEditor.js
@@ -0,0 +1,1179 @@
+/*!
+ * Ext JS Library 3.0.0
+ * Copyright(c) 2006-2009 Ext JS, LLC
+ * licensing@extjs.com
+ * http://www.extjs.com/license
+ */
+/**
+ * @class Ext.form.HtmlEditor
+ * @extends Ext.form.Field
+ * Provides a lightweight HTML Editor component. Some toolbar features are not supported by Safari and will be
+ * automatically hidden when needed. These are noted in the config options where appropriate.
+ *
The editor's toolbar buttons have tooltips defined in the {@link #buttonTips} property, but they are not
+ * enabled by default unless the global {@link Ext.QuickTips} singleton is {@link Ext.QuickTips#init initialized}.
+ *
Note: The focus/blur and validation marking functionality inherited from Ext.form.Field is NOT
+ * supported by this editor.
+ *
An Editor is a sensitive component that can't be used in all spots standard fields can be used. Putting an Editor within
+ * any element that has display set to 'none' can cause problems in Safari and Firefox due to their default iframe reloading bugs.
+ *
Example usage:
+ *
+// Simple example rendered with default options:
+Ext.QuickTips.init(); // enable tooltips
+new Ext.form.HtmlEditor({
+ renderTo: Ext.getBody(),
+ width: 800,
+ height: 300
+});
+
+// Passed via xtype into a container and with custom options:
+Ext.QuickTips.init(); // enable tooltips
+new Ext.Panel({
+ title: 'HTML Editor',
+ renderTo: Ext.getBody(),
+ width: 600,
+ height: 300,
+ frame: true,
+ layout: 'fit',
+ items: {
+ xtype: 'htmleditor',
+ enableColors: false,
+ enableAlignments: false
+ }
+});
+
+ * @constructor
+ * Create a new HtmlEditor
+ * @param {Object} config
+ * @xtype htmleditor
+ */
+
+Ext.form.HtmlEditor = Ext.extend(Ext.form.Field, {
+ /**
+ * @cfg {Boolean} enableFormat Enable the bold, italic and underline buttons (defaults to true)
+ */
+ enableFormat : true,
+ /**
+ * @cfg {Boolean} enableFontSize Enable the increase/decrease font size buttons (defaults to true)
+ */
+ enableFontSize : true,
+ /**
+ * @cfg {Boolean} enableColors Enable the fore/highlight color buttons (defaults to true)
+ */
+ enableColors : true,
+ /**
+ * @cfg {Boolean} enableAlignments Enable the left, center, right alignment buttons (defaults to true)
+ */
+ enableAlignments : true,
+ /**
+ * @cfg {Boolean} enableLists Enable the bullet and numbered list buttons. Not available in Safari. (defaults to true)
+ */
+ enableLists : true,
+ /**
+ * @cfg {Boolean} enableSourceEdit Enable the switch to source edit button. Not available in Safari. (defaults to true)
+ */
+ enableSourceEdit : true,
+ /**
+ * @cfg {Boolean} enableLinks Enable the create link button. Not available in Safari. (defaults to true)
+ */
+ enableLinks : true,
+ /**
+ * @cfg {Boolean} enableFont Enable font selection. Not available in Safari. (defaults to true)
+ */
+ enableFont : true,
+ /**
+ * @cfg {String} createLinkText The default text for the create link prompt
+ */
+ createLinkText : 'Please enter the URL for the link:',
+ /**
+ * @cfg {String} defaultLinkValue The default value for the create link prompt (defaults to http:/ /)
+ */
+ defaultLinkValue : 'http:/'+'/',
+ /**
+ * @cfg {Array} fontFamilies An array of available font families
+ */
+ fontFamilies : [
+ 'Arial',
+ 'Courier New',
+ 'Tahoma',
+ 'Times New Roman',
+ 'Verdana'
+ ],
+ defaultFont: 'tahoma',
+ /**
+ * @cfg {String} defaultValue A default value to be put into the editor to resolve focus issues (defaults to (Zero-width space), (Non-breaking space) in Opera and IE6).
+ */
+ defaultValue: (Ext.isOpera || Ext.isIE6) ? ' ' : '',
+
+ // private properties
+ actionMode: 'wrap',
+ validationEvent : false,
+ deferHeight: true,
+ initialized : false,
+ activated : false,
+ sourceEditMode : false,
+ onFocus : Ext.emptyFn,
+ iframePad:3,
+ hideMode:'offsets',
+ defaultAutoCreate : {
+ tag: "textarea",
+ style:"width:500px;height:300px;",
+ autocomplete: "off"
+ },
+
+ // private
+ initComponent : function(){
+ this.addEvents(
+ /**
+ * @event initialize
+ * Fires when the editor is fully initialized (including the iframe)
+ * @param {HtmlEditor} this
+ */
+ 'initialize',
+ /**
+ * @event activate
+ * Fires when the editor is first receives the focus. Any insertion must wait
+ * until after this event.
+ * @param {HtmlEditor} this
+ */
+ 'activate',
+ /**
+ * @event beforesync
+ * Fires before the textarea is updated with content from the editor iframe. Return false
+ * to cancel the sync.
+ * @param {HtmlEditor} this
+ * @param {String} html
+ */
+ 'beforesync',
+ /**
+ * @event beforepush
+ * Fires before the iframe editor is updated with content from the textarea. Return false
+ * to cancel the push.
+ * @param {HtmlEditor} this
+ * @param {String} html
+ */
+ 'beforepush',
+ /**
+ * @event sync
+ * Fires when the textarea is updated with content from the editor iframe.
+ * @param {HtmlEditor} this
+ * @param {String} html
+ */
+ 'sync',
+ /**
+ * @event push
+ * Fires when the iframe editor is updated with content from the textarea.
+ * @param {HtmlEditor} this
+ * @param {String} html
+ */
+ 'push',
+ /**
+ * @event editmodechange
+ * Fires when the editor switches edit modes
+ * @param {HtmlEditor} this
+ * @param {Boolean} sourceEdit True if source edit, false if standard editing.
+ */
+ 'editmodechange'
+ )
+ },
+
+ // private
+ createFontOptions : function(){
+ var buf = [], fs = this.fontFamilies, ff, lc;
+ for(var i = 0, len = fs.length; i< len; i++){
+ ff = fs[i];
+ lc = ff.toLowerCase();
+ buf.push(
+ ''
+ );
+ }
+ return buf.join('');
+ },
+
+ /*
+ * Protected method that will not generally be called directly. It
+ * is called when the editor creates its toolbar. Override this method if you need to
+ * add custom toolbar buttons.
+ * @param {HtmlEditor} editor
+ */
+ createToolbar : function(editor){
+
+ var tipsEnabled = Ext.QuickTips && Ext.QuickTips.isEnabled();
+
+ function btn(id, toggle, handler){
+ return {
+ itemId : id,
+ cls : 'x-btn-icon',
+ iconCls: 'x-edit-'+id,
+ enableToggle:toggle !== false,
+ scope: editor,
+ handler:handler||editor.relayBtnCmd,
+ clickEvent:'mousedown',
+ tooltip: tipsEnabled ? editor.buttonTips[id] || undefined : undefined,
+ overflowText: editor.buttonTips[id].title || undefined,
+ tabIndex:-1
+ };
+ }
+
+ // build the toolbar
+ var tb = new Ext.Toolbar({
+ renderTo:this.wrap.dom.firstChild
+ });
+
+ // stop form submits
+ this.mon(tb.el, 'click', function(e){
+ e.preventDefault();
+ });
+
+ if(this.enableFont && !Ext.isSafari2){
+ this.fontSelect = tb.el.createChild({
+ tag:'select',
+ cls:'x-font-select',
+ html: this.createFontOptions()
+ });
+ this.mon(this.fontSelect, 'change', function(){
+ var font = this.fontSelect.dom.value;
+ this.relayCmd('fontname', font);
+ this.deferFocus();
+ }, this);
+
+ tb.add(
+ this.fontSelect.dom,
+ '-'
+ );
+ }
+
+ if(this.enableFormat){
+ tb.add(
+ btn('bold'),
+ btn('italic'),
+ btn('underline')
+ );
+ }
+
+ if(this.enableFontSize){
+ tb.add(
+ '-',
+ btn('increasefontsize', false, this.adjustFont),
+ btn('decreasefontsize', false, this.adjustFont)
+ );
+ }
+
+ if(this.enableColors){
+ tb.add(
+ '-', {
+ itemId:'forecolor',
+ cls:'x-btn-icon',
+ iconCls: 'x-edit-forecolor',
+ clickEvent:'mousedown',
+ tooltip: tipsEnabled ? editor.buttonTips.forecolor || undefined : undefined,
+ tabIndex:-1,
+ menu : new Ext.menu.ColorMenu({
+ allowReselect: true,
+ focus: Ext.emptyFn,
+ value:'000000',
+ plain:true,
+ listeners: {
+ scope: this,
+ select: function(cp, color){
+ this.execCmd('forecolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
+ this.deferFocus();
+ }
+ },
+ clickEvent:'mousedown'
+ })
+ }, {
+ itemId:'backcolor',
+ cls:'x-btn-icon',
+ iconCls: 'x-edit-backcolor',
+ clickEvent:'mousedown',
+ tooltip: tipsEnabled ? editor.buttonTips.backcolor || undefined : undefined,
+ tabIndex:-1,
+ menu : new Ext.menu.ColorMenu({
+ focus: Ext.emptyFn,
+ value:'FFFFFF',
+ plain:true,
+ allowReselect: true,
+ listeners: {
+ scope: this,
+ select: 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.isWebKit || Ext.isIE ? '#'+color : color);
+ this.deferFocus();
+ }
+ }
+ },
+ clickEvent:'mousedown'
+ })
+ }
+ );
+ }
+
+ if(this.enableAlignments){
+ tb.add(
+ '-',
+ btn('justifyleft'),
+ btn('justifycenter'),
+ btn('justifyright')
+ );
+ }
+
+ if(!Ext.isSafari2){
+ if(this.enableLinks){
+ tb.add(
+ '-',
+ btn('createlink', false, this.createLink)
+ );
+ }
+
+ if(this.enableLists){
+ tb.add(
+ '-',
+ btn('insertorderedlist'),
+ btn('insertunorderedlist')
+ );
+ }
+ if(this.enableSourceEdit){
+ tb.add(
+ '-',
+ btn('sourceedit', true, function(btn){
+ this.toggleSourceEdit(!this.sourceEditMode);
+ })
+ );
+ }
+ }
+
+ this.tb = tb;
+ },
+
+ /**
+ * Protected method that will not generally be called directly. It
+ * is called when the editor initializes the iframe with HTML contents. Override this method if you
+ * want to change the initialization markup of the iframe (e.g. to add stylesheets).
+ */
+ getDocMarkup : function(){
+ return '';
+ },
+
+ // private
+ getEditorBody : function(){
+ return this.doc.body || this.doc.documentElement;
+ },
+
+ // private
+ getDoc : function(){
+ return Ext.isIE ? this.getWin().document : (this.iframe.contentDocument || this.getWin().document);
+ },
+
+ // private
+ getWin : function(){
+ return Ext.isIE ? this.iframe.contentWindow : window.frames[this.iframe.name];
+ },
+
+ // private
+ onRender : function(ct, position){
+ Ext.form.HtmlEditor.superclass.onRender.call(this, ct, position);
+ this.el.dom.style.border = '0 none';
+ this.el.dom.setAttribute('tabIndex', -1);
+ this.el.addClass('x-hidden');
+ if(Ext.isIE){ // fix IE 1px bogus margin
+ this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
+ }
+ this.wrap = this.el.wrap({
+ cls:'x-html-editor-wrap', cn:{cls:'x-html-editor-tb'}
+ });
+
+ this.createToolbar(this);
+
+ this.disableItems(true);
+ // is this needed?
+ // this.tb.doLayout();
+
+ this.createIFrame();
+
+ if(!this.width){
+ var sz = this.el.getSize();
+ this.setSize(sz.width, this.height || sz.height);
+ }
+ },
+
+ createIFrame: function(){
+ var iframe = document.createElement('iframe');
+ iframe.name = Ext.id();
+ iframe.frameBorder = '0';
+ iframe.src = Ext.isIE ? Ext.SSL_SECURE_URL : "javascript:;";
+ this.wrap.dom.appendChild(iframe);
+
+ this.iframe = iframe;
+
+ this.monitorTask = Ext.TaskMgr.start({
+ run: this.checkDesignMode,
+ scope: this,
+ interval:100
+ });
+ },
+
+ initFrame : function(){
+ Ext.TaskMgr.stop(this.monitorTask);
+ this.doc = this.getDoc();
+ this.win = this.getWin();
+
+ this.doc.open();
+ this.doc.write(this.getDocMarkup());
+ this.doc.close();
+
+ var task = { // must defer to wait for browser to be ready
+ run : function(){
+ if(this.doc.body || this.doc.readyState == 'complete'){
+ Ext.TaskMgr.stop(task);
+ this.doc.designMode="on";
+ this.initEditor.defer(10, this);
+ }
+ },
+ interval : 10,
+ duration:10000,
+ scope: this
+ };
+ Ext.TaskMgr.start(task);
+ },
+
+
+ checkDesignMode : function(){
+ if(this.wrap && this.wrap.dom.offsetWidth){
+ var doc = this.getDoc();
+ if(!doc){
+ return;
+ }
+ if(!doc.editorInitialized || String(doc.designMode).toLowerCase() != 'on'){
+ this.initFrame();
+ }
+ }
+ },
+
+ disableItems: function(disabled){
+ if(this.fontSelect){
+ this.fontSelect.dom.disabled = disabled;
+ }
+ this.tb.items.each(function(item){
+ if(item.itemId != 'sourceedit'){
+ item.setDisabled(disabled);
+ }
+ });
+ },
+
+ // private
+ onResize : function(w, h){
+ Ext.form.HtmlEditor.superclass.onResize.apply(this, arguments);
+ if(this.el && this.iframe){
+ if(typeof w == 'number'){
+ var aw = w - this.wrap.getFrameWidth('lr');
+ this.el.setWidth(this.adjustWidth('textarea', aw));
+ this.tb.setWidth(aw);
+ this.iframe.style.width = Math.max(aw, 0) + 'px';
+ }
+ if(typeof h == 'number'){
+ var ah = h - this.wrap.getFrameWidth('tb') - this.tb.el.getHeight();
+ this.el.setHeight(this.adjustWidth('textarea', ah));
+ this.iframe.style.height = Math.max(ah, 0) + 'px';
+ if(this.doc){
+ this.getEditorBody().style.height = Math.max((ah - (this.iframePad*2)), 0) + 'px';
+ }
+ }
+ }
+ },
+
+ /**
+ * Toggles the editor between standard and source edit mode.
+ * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
+ */
+ toggleSourceEdit : function(sourceEditMode){
+ if(sourceEditMode === undefined){
+ sourceEditMode = !this.sourceEditMode;
+ }
+ this.sourceEditMode = sourceEditMode === true;
+ var btn = this.tb.items.get('sourceedit');
+ if(btn.pressed !== this.sourceEditMode){
+ btn.toggle(this.sourceEditMode);
+ if(!btn.xtbHidden){
+ return;
+ }
+ }
+ if(this.sourceEditMode){
+ this.disableItems(true);
+ this.syncValue();
+ this.iframe.className = 'x-hidden';
+ this.el.removeClass('x-hidden');
+ this.el.dom.removeAttribute('tabIndex');
+ this.el.focus();
+ }else{
+ if(this.initialized){
+ this.disableItems(false);
+ }
+ this.pushValue();
+ this.iframe.className = '';
+ this.el.addClass('x-hidden');
+ this.el.dom.setAttribute('tabIndex', -1);
+ this.deferFocus();
+ }
+ var lastSize = this.lastSize;
+ if(lastSize){
+ delete this.lastSize;
+ this.setSize(lastSize);
+ }
+ this.fireEvent('editmodechange', this, this.sourceEditMode);
+ },
+
+ // private used internally
+ createLink : function(){
+ var url = prompt(this.createLinkText, this.defaultLinkValue);
+ if(url && url != 'http:/'+'/'){
+ this.relayCmd('createlink', url);
+ }
+ },
+
+ // private (for BoxComponent)
+ adjustSize : Ext.BoxComponent.prototype.adjustSize,
+
+ // private (for BoxComponent)
+ getResizeEl : function(){
+ return this.wrap;
+ },
+
+ // private (for BoxComponent)
+ getPositionEl : function(){
+ return this.wrap;
+ },
+
+ // private
+ initEvents : function(){
+ this.originalValue = this.getValue();
+ },
+
+ /**
+ * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
+ * @method
+ */
+ markInvalid : Ext.emptyFn,
+
+ /**
+ * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
+ * @method
+ */
+ clearInvalid : Ext.emptyFn,
+
+ // docs inherit from Field
+ setValue : function(v){
+ Ext.form.HtmlEditor.superclass.setValue.call(this, v);
+ this.pushValue();
+ return this;
+ },
+
+ /**
+ * Protected method that will not generally be called directly. If you need/want
+ * custom HTML cleanup, this is the method you should override.
+ * @param {String} html The HTML to be cleaned
+ * @return {String} The cleaned HTML
+ */
+ cleanHtml : function(html){
+ html = String(html);
+ if(html.length > 5){
+ if(Ext.isWebKit){ // strip safari nonsense
+ html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
+ }
+ }
+ if(html == this.defaultValue){
+ html = '';
+ }
+ return html;
+ },
+
+ /**
+ * Protected method that will not generally be called directly. Syncs the contents
+ * of the editor iframe with the textarea.
+ */
+ syncValue : function(){
+ if(this.initialized){
+ var bd = this.getEditorBody();
+ var html = bd.innerHTML;
+ if(Ext.isWebKit){
+ var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
+ var m = bs.match(/text-align:(.*?);/i);
+ if(m && m[1]){
+ html = '
+{
+ bold : {
+ title: 'Bold (Ctrl+B)',
+ text: 'Make the selected text bold.',
+ cls: 'x-html-editor-tip'
+ },
+ italic : {
+ title: 'Italic (Ctrl+I)',
+ text: 'Make the selected text italic.',
+ cls: 'x-html-editor-tip'
+ },
+ ...
+
+ * @type Object
+ */
+ buttonTips : {
+ bold : {
+ title: 'Bold (Ctrl+B)',
+ text: 'Make the selected text bold.',
+ cls: 'x-html-editor-tip'
+ },
+ italic : {
+ title: 'Italic (Ctrl+I)',
+ text: 'Make the selected text italic.',
+ cls: 'x-html-editor-tip'
+ },
+ underline : {
+ title: 'Underline (Ctrl+U)',
+ text: 'Underline the selected text.',
+ cls: 'x-html-editor-tip'
+ },
+ increasefontsize : {
+ title: 'Grow Text',
+ text: 'Increase the font size.',
+ cls: 'x-html-editor-tip'
+ },
+ decreasefontsize : {
+ title: 'Shrink Text',
+ text: 'Decrease the font size.',
+ cls: 'x-html-editor-tip'
+ },
+ backcolor : {
+ title: 'Text Highlight Color',
+ text: 'Change the background color of the selected text.',
+ cls: 'x-html-editor-tip'
+ },
+ forecolor : {
+ title: 'Font Color',
+ text: 'Change the color of the selected text.',
+ cls: 'x-html-editor-tip'
+ },
+ justifyleft : {
+ title: 'Align Text Left',
+ text: 'Align text to the left.',
+ cls: 'x-html-editor-tip'
+ },
+ justifycenter : {
+ title: 'Center Text',
+ text: 'Center text in the editor.',
+ cls: 'x-html-editor-tip'
+ },
+ justifyright : {
+ title: 'Align Text Right',
+ text: 'Align text to the right.',
+ cls: 'x-html-editor-tip'
+ },
+ insertunorderedlist : {
+ title: 'Bullet List',
+ text: 'Start a bulleted list.',
+ cls: 'x-html-editor-tip'
+ },
+ insertorderedlist : {
+ title: 'Numbered List',
+ text: 'Start a numbered list.',
+ cls: 'x-html-editor-tip'
+ },
+ createlink : {
+ title: 'Hyperlink',
+ text: 'Make the selected text a hyperlink.',
+ cls: 'x-html-editor-tip'
+ },
+ sourceedit : {
+ title: 'Source Edit',
+ text: 'Switch to source editing mode.',
+ cls: 'x-html-editor-tip'
+ }
+ }
+
+ // hide stuff that is not compatible
+ /**
+ * @event blur
+ * @hide
+ */
+ /**
+ * @event change
+ * @hide
+ */
+ /**
+ * @event focus
+ * @hide
+ */
+ /**
+ * @event specialkey
+ * @hide
+ */
+ /**
+ * @cfg {String} fieldClass @hide
+ */
+ /**
+ * @cfg {String} focusClass @hide
+ */
+ /**
+ * @cfg {String} autoCreate @hide
+ */
+ /**
+ * @cfg {String} inputType @hide
+ */
+ /**
+ * @cfg {String} invalidClass @hide
+ */
+ /**
+ * @cfg {String} invalidText @hide
+ */
+ /**
+ * @cfg {String} msgFx @hide
+ */
+ /**
+ * @cfg {String} validateOnBlur @hide
+ */
+ /**
+ * @cfg {Boolean} allowDomMove @hide
+ */
+ /**
+ * @cfg {String} applyTo @hide
+ */
+ /**
+ * @cfg {String} autoHeight @hide
+ */
+ /**
+ * @cfg {String} autoWidth @hide
+ */
+ /**
+ * @cfg {String} cls @hide
+ */
+ /**
+ * @cfg {String} disabled @hide
+ */
+ /**
+ * @cfg {String} disabledClass @hide
+ */
+ /**
+ * @cfg {String} msgTarget @hide
+ */
+ /**
+ * @cfg {String} readOnly @hide
+ */
+ /**
+ * @cfg {String} style @hide
+ */
+ /**
+ * @cfg {String} validationDelay @hide
+ */
+ /**
+ * @cfg {String} validationEvent @hide
+ */
+ /**
+ * @cfg {String} tabIndex @hide
+ */
+ /**
+ * @property disabled
+ * @hide
+ */
+ /**
+ * @method applyToMarkup
+ * @hide
+ */
+ /**
+ * @method disable
+ * @hide
+ */
+ /**
+ * @method enable
+ * @hide
+ */
+ /**
+ * @method validate
+ * @hide
+ */
+ /**
+ * @event valid
+ * @hide
+ */
+ /**
+ * @method setDisabled
+ * @hide
+ */
+ /**
+ * @cfg keys
+ * @hide
+ */
+});
+Ext.reg('htmleditor', Ext.form.HtmlEditor);
\ No newline at end of file