1 /***************************************************************
4 * (c) 2002-2004 interactivetools.com, inc.
5 * (c) 2003-2004 dynarch.com
6 * (c) 2004-2010 Stanislas Rolland <typo3(arobas)sjbr.ca>
9 * This script is part of the TYPO3 project. The TYPO3 project is
10 * free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * The GNU General Public License can be found at
16 * http://www.gnu.org/copyleft/gpl.html.
17 * A copy is found in the textfile GPL.txt and important notices to the license
18 * from the author is found in LICENSE.txt distributed with these scripts.
21 * This script is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
26 * This script is a modified version of a script published under the htmlArea License.
27 * A copy of the htmlArea License may be found in the textfile HTMLAREA_LICENSE.txt.
29 * This copyright notice MUST APPEAR in all copies of the script!
30 ***************************************************************/
32 * Main script of TYPO3 htmlArea RTE
36 // Avoid re-initialization on AJax call when HTMLArea object was already initialized
37 if (typeof(HTMLArea
) == 'undefined') {
38 // Establish HTMLArea name space
39 Ext
.namespace('HTMLArea.util.TYPO3', 'HTMLArea.util.Tips', 'HTMLArea.util.Color', 'Ext.ux.form', 'Ext.ux.menu', 'Ext.ux.Toolbar');
40 /***************************************************
42 ***************************************************/
44 /*************************************************************************
45 * THESE BROWSER IDENTIFICATION CONSTANTS ARE DEPRECATED AS OF TYPO3 4.4 *
46 *************************************************************************/
47 // Browser identification
48 is_gecko
: Ext
.isGecko
|| Ext
.isOpera
|| Ext
.isWebKit
,
49 is_ff2
: Ext
.isGecko2
,
51 is_safari
: Ext
.isWebKit
,
52 is_chrome
: Ext
.isChrome
,
53 is_opera
: Ext
.isOpera
,
54 // Compile some regular expressions
55 RE_tagName
: /(<\/|<)\s*([^ \t\n>]+)/ig,
56 RE_head
: /<head>((.|\n)*?)<\/head>/i,
57 RE_body
: /<body>((.|\n)*?)<\/body>/i,
58 Reg_body
: new RegExp('<\/?(body)[^>]*>', 'gi'),
59 reservedClassNames
: /htmlarea/,
60 RE_email
: /([0-9a-z]+([a-z0-9_-]*[0-9a-z])*){1}(\.[0-9a-z]+([a-z0-9_-]*[0-9a-z])*)*@([0-9a-z]+([a-z0-9_-]*[0-9a-z])*\.)+[a-z]{2,9}/i,
61 RE_url
: /(([^:/?#]+):\/\/)?(([a-z0-9_]+:[a-z0-9_]+@)?[a-z0-9_-]{2,}(\.[a-z0-9_-]{2,})+\.[a-z]{2,5}(:[0-9]+)?(\/\S+)*)/i,
62 RE_blockTags
: /^(body|p|h1|h2|h3|h4|h5|h6|ul|ol|pre|dl|dt|dd|div|noscript|blockquote|form|hr|table|caption|fieldset|address|td|tr|th|li|tbody|thead|tfoot|iframe)$/,
63 RE_closingTags
: /^(p|blockquote|a|li|ol|ul|dl|dt|td|th|tr|tbody|thead|tfoot|caption|colgroup|table|div|b|bdo|big|cite|code|del|dfn|em|i|ins|kbd|label|q|samp|small|span|strike|strong|sub|sup|tt|u|var|abbr|acronym|font|center|object|embed|style|script|title|head)$/,
64 RE_noClosingTag
: /^(img|br|hr|col|input|area|base|link|meta|param)$/
66 /***************************************************
68 ***************************************************/
69 HTMLArea
._appendToLog = function(str
){
70 if (HTMLArea
.enableDebugMode
) {
71 var log
= document
.getElementById('HTMLAreaLog');
73 log
.appendChild(document
.createTextNode(str
));
74 log
.appendChild(document
.createElement('br'));
78 /***************************************************
79 * HTMLArea INITIALIZATION
80 ***************************************************/
81 HTMLArea
.init = function() {
82 // Apply global configuration settings
83 Ext
.apply(HTMLArea
, RTEarea
[0]);
84 Ext
.applyIf(HTMLArea
, {
85 editorSkin
: HTMLArea
.editorUrl
+ 'skins/default/',
86 editorCSS
: HTMLArea
.editorUrl
+ 'skins/default/htmlarea.css'
88 if (!Ext
.isString(HTMLArea
.editedContentCSS
)) {
89 HTMLArea
.editedContentCSS
= HTMLArea
.editorSkin
+ 'htmlarea-edited-content.css';
91 HTMLArea
.isReady
= true;
92 HTMLArea
._appendToLog("[HTMLArea::init]: Editor url set to: " + HTMLArea
.editorUrl
);
93 HTMLArea
._appendToLog("[HTMLArea::init]: Editor skin CSS set to: " + HTMLArea
.editorCSS
);
94 HTMLArea
._appendToLog("[HTMLArea::init]: Editor content skin CSS set to: " + HTMLArea
.editedContentCSS
);
96 /***************************************************
97 * EDITOR CONFIGURATION
98 ***************************************************/
99 HTMLArea
.Config = function (editorId
) {
100 this.editorId
= editorId
;
101 // if the site is secure, create a secure iframe
102 this.useHTTPS
= false;
105 this.enableMozillaExtension
= true;
106 this.disableEnterParagraphs
= false;
107 this.disableObjectResizing
= false;
108 this.removeTrailingBR
= false;
109 // style included in the iframe document
110 this.editedContentStyle
= HTMLArea
.editedContentCSS
;
113 // remove tags (these have to be a regexp, or null if this functionality is not desired)
114 this.htmlRemoveTags
= null;
115 // remove tags and any contents (these have to be a regexp, or null if this functionality is not desired)
116 this.htmlRemoveTagsAndContents
= null;
118 this.htmlRemoveComments
= false;
119 // custom tags (these have to be a regexp, or null if this functionality is not desired)
120 this.customTags
= null;
121 // BaseURL included in the iframe document
122 this.baseURL
= document
.baseURI
|| document
.URL
;
123 if (this.baseURL
&& this.baseURL
.match(/(.*)\/([^\/]+)/)) {
124 this.baseURL
= RegExp
.$1 + "/";
127 this.popupURL
= "popups/";
129 this.documentType
= '<!DOCTYPE html\r'
130 + ' PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\r'
131 + ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\r';
132 this.resizable
= TYPO3
.settings
.textareaResize
;
133 this.maxHeight
= TYPO3
.settings
.textareaMaxHeight
;
134 // Hold the configuration of buttons and hot keys registered by plugins
135 this.buttonsConfig
= {};
136 this.hotKeyList
= {};
137 // Default configurations for toolbar items
138 this.configDefaults
= {
140 xtype
: 'htmlareabutton',
141 disabledClass
: 'buttonDisabled',
150 overCls
: 'buttonHover'
156 triggerAction
: 'all',
158 selectOnFocus
: !Ext
.isIE
,
159 validationEvent
: false,
160 validateOnBlur
: false,
162 forceSelection
: true,
164 storeRoot
: 'options',
165 storeFields
: [ { name
: 'text'}, { name
: 'value'}],
167 displayField
: 'text',
170 tpl
: '<tpl for="."><div ext:qtip="{value}" style="text-align:left;font-size:11px;" class="x-combo-list-item">{text}</div></tpl>'
174 HTMLArea
.Config
= Ext
.extend(HTMLArea
.Config
, {
176 * Registers a button for inclusion in the toolbar, adding some standard configuration properties for the ExtJS widgets
178 * @param object buttonConfiguration: the configuration object of the button:
179 * id : unique id for the button
180 * tooltip : tooltip for the button
181 * textMode : enable in text mode
182 * context : disable if not inside one of listed elements
183 * hidden : hide in menu and show only in context menu
184 * selection : disable if there is no selection
185 * hotkey : hotkey character
186 * dialog : if true, the button opens a dialogue
187 * dimensions : the opening dimensions object of the dialogue window: { width: nn, height: mm }
188 * and potentially other ExtJS config properties (will be forwarded)
190 * @return boolean true if the button was successfully registered
192 registerButton: function (config
) {
193 config
.itemId
= config
.id
;
194 if (Ext
.type(this.buttonsConfig
[config
.id
])) {
195 HTMLArea
._appendToLog('[HTMLArea.Config::registerButton]: A toolbar item with the same Id: ' + config
.id
+ ' already exists and will be overidden.');
198 config
= Ext
.applyIf(config
, this.configDefaults
['all']);
199 config
= Ext
.applyIf(config
, this.configDefaults
[config
.xtype
]);
200 // Set some additional properties
201 switch (config
.xtype
) {
202 case 'htmlareacombo':
203 if (config
.options
) {
204 // Create combo array store
205 config
.store
= new Ext
.data
.ArrayStore({
207 fields
: config
.storeFields
,
210 } else if (config
.storeUrl
) {
211 // Create combo json store
212 config
.store
= new Ext
.data
.JsonStore({
215 root
: config
.storeRoot
,
216 fields
: config
.storeFields
,
220 config
.hideLabel
= Ext
.isEmpty(config
.fieldLabel
) || Ext
.isIE6
;
221 config
.helpTitle
= config
.tooltip
;
224 if (!config
.iconCls
) {
225 config
.iconCls
= config
.id
;
229 config
.cmd
= config
.id
;
230 config
.tooltip
= { title
: config
.tooltip
};
231 this.buttonsConfig
[config
.id
] = config
;
235 * Register a hotkey with the editor configuration.
237 registerHotKey: function (hotKeyConfiguration
) {
238 if (Ext
.isDefined(this.hotKeyList
[hotKeyConfiguration
.id
])) {
239 HTMLArea
._appendToLog('[HTMLArea.Config::registerHotKey]: A hotkey with the same key ' + hotKeyConfiguration
.id
+ ' already exists and will be overidden.');
241 if (Ext
.isDefined(hotKeyConfiguration
.cmd
) && !Ext
.isEmpty(hotKeyConfiguration
.cmd
) && Ext
.isDefined(this.buttonsConfig
[hotKeyConfiguration
.cmd
])) {
242 this.hotKeyList
[hotKeyConfiguration
.id
] = hotKeyConfiguration
;
243 HTMLArea
._appendToLog('[HTMLArea.Config::registerHotKey]: A hotkey with key ' + hotKeyConfiguration
.id
+ ' was registered for toolbar item ' + hotKeyConfiguration
.cmd
+ '.');
246 HTMLArea
._appendToLog('[HTMLArea.Config::registerHotKey]: A hotkey with key ' + hotKeyConfiguration
.id
+ ' could not be registered because toolbar item with id ' + hotKeyConfiguration
.cmd
+ ' was not registered.');
251 * Get the configured document type for dialogue windows
253 getDocumentType: function () {
254 return this.documentType
;
257 /***************************************************
259 ***************************************************/
261 * Ext.ux.HTMLAreaButton extends Ext.Button
263 Ext
.ux
.HTMLAreaButton
= Ext
.extend(Ext
.Button
, {
265 * Component initialization
267 initComponent: function () {
268 Ext
.ux
.HTMLAreaButton
.superclass
.initComponent
.call(this);
272 * Fires when the button hotkey is pressed
278 fn
: this.initEventListeners
,
284 * Initialize listeners
286 initEventListeners: function () {
289 fn
: this.onButtonClick
295 // Monitor toolbar updates in order to refresh the state of the button
296 this.mon(this.getToolbar(), 'update', this.onUpdateToolbar
, this);
299 * Get a reference to the editor
301 getEditor: function() {
302 return RTEarea
[this.ownerCt
.editorId
].editor
;
305 * Get a reference to the toolbar
307 getToolbar: function() {
311 * Add properties and function to set button active or not depending on current selection
314 activeClass
: 'buttonActive',
315 setInactive: function (inactive
) {
316 this.inactive
= inactive
;
317 return inactive
? this.removeClass(this.activeClass
) : this.addClass(this.activeClass
);
320 * Determine if the button should be enabled based on the current selection and context configuration property
322 isInContext: function (mode
, selectionEmpty
, ancestors
) {
323 var editor
= this.getEditor();
324 var inContext
= true;
325 if (mode
=== 'wysiwyg' && this.context
) {
328 if (/(.*)\[(.*?)\]/.test(this.context
)) {
329 contexts
= RegExp
.$1.split(',');
330 attributes
= RegExp
.$2.split(',');
332 contexts
= this.context
.split(',');
334 contexts
= new RegExp( '^(' + contexts
.join('|') + ')$', 'i');
335 var matchAny
= contexts
.test('*');
336 Ext
.each(ancestors
, function (ancestor
) {
337 inContext
= matchAny
|| contexts
.test(ancestor
.nodeName
);
339 Ext
.each(attributes
, function (attribute
) {
340 inContext
= eval("ancestor." + attribute
);
347 return inContext
&& (!this.selection
|| !selectionEmpty
);
350 * Handler invoked when the button is clicked
352 onButtonClick: function (button
, event
, key
) {
353 if (!this.disabled
) {
354 if (!this.plugins
[this.action
](this.getEditor(), key
|| this.itemId
) && event
) {
358 this.getEditor().focus();
361 this.setDisabled(true);
363 this.getToolbar().update();
369 * Handler invoked when the hotkey configured for this button is pressed
371 onHotKey: function (key
, event
) {
372 return this.onButtonClick(this, event
, key
);
375 * Handler invoked when the toolbar is updated
377 onUpdateToolbar: function (mode
, selectionEmpty
, ancestors
, endPointsInSameBlock
) {
378 this.setDisabled(mode
=== 'textmode' && !this.textMode
);
379 if (!this.disabled
) {
380 if (!this.noAutoUpdate
) {
381 this.setDisabled(!this.isInContext(mode
, selectionEmpty
, ancestors
));
383 this.plugins
['onUpdateToolbar'](this, mode
, selectionEmpty
, ancestors
, endPointsInSameBlock
);
387 Ext
.reg('htmlareabutton', Ext
.ux
.HTMLAreaButton
);
389 * Ext.ux.Toolbar.HTMLAreaToolbarText extends Ext.Toolbar.TextItem
391 Ext
.ux
.Toolbar
.HTMLAreaToolbarText
= Ext
.extend(Ext
.Toolbar
.TextItem
, {
395 initComponent: function () {
396 Ext
.ux
.Toolbar
.HTMLAreaToolbarText
.superclass
.initComponent
.call(this);
399 fn
: this.initEventListeners
,
405 * Initialize listeners
407 initEventListeners: function () {
408 // Monitor toolbar updates in order to refresh the state of the button
409 this.mon(this.getToolbar(), 'update', this.onUpdateToolbar
, this);
412 * Get a reference to the editor
414 getEditor: function() {
415 return RTEarea
[this.ownerCt
.editorId
].editor
;
418 * Get a reference to the toolbar
420 getToolbar: function() {
424 * Handler invoked when the toolbar is updated
426 onUpdateToolbar: function (mode
, selectionEmpty
, ancestors
, endPointsInSameBlock
) {
427 this.setDisabled(mode
=== 'textmode' && !this.textMode
);
428 if (!this.disabled
) {
429 this.plugins
['onUpdateToolbar'](this, mode
, selectionEmpty
, ancestors
, endPointsInSameBlock
);
433 Ext
.reg('htmlareatoolbartext', Ext
.ux
.Toolbar
.HTMLAreaToolbarText
);
435 * Ext.ux.form.HTMLAreaCombo extends Ext.form.ComboBox
437 Ext
.ux
.form
.HTMLAreaCombo
= Ext
.extend(Ext
.form
.ComboBox
, {
441 initComponent: function () {
442 Ext
.ux
.form
.HTMLAreaCombo
.superclass
.initComponent
.call(this);
446 * Fires when a hotkey configured for the combo is pressed
452 fn
: this.initEventListeners
,
458 * Initialize listeners
460 initEventListeners: function () {
463 fn
: this.onComboSelect
466 fn
: this.onSpecialKey
472 fn
: this.onBeforeDestroy
,
476 // Monitor toolbar updates in order to refresh the state of the combo
477 this.mon(this.getToolbar(), 'update', this.onUpdateToolbar
, this);
478 // Monitor framework becoming ready
479 this.mon(this.getToolbar().ownerCt
, 'frameworkready', this.onFrameworkReady
, this);
482 * Get a reference to the editor
484 getEditor: function() {
485 return RTEarea
[this.ownerCt
.editorId
].editor
;
488 * Get a reference to the toolbar
490 getToolbar: function() {
494 * Handler invoked when an item is selected in the dropdown list
496 onComboSelect: function (combo
, record
, index
) {
497 if (!combo
.disabled
) {
498 var editor
= this.getEditor();
499 // In IE, reclaim lost focus on the editor iframe and restore the bookmarked selection
502 if (!Ext
.isEmpty(this.savedRange
)) {
503 editor
.selectRange(this.savedRange
);
504 this.savedRange
= null;
507 // Invoke the plugin onChange handler
508 this.plugins
[this.action
](editor
, combo
, record
, index
);
509 // In IE, bookmark the updated selection as the editor will be loosing focus
512 this.savedRange
= editor
._createRange(editor
._getSelection());
513 this.triggered
= true;
518 this.getToolbar().update();
523 * Handler invoked when the trigger element is clicked
524 * In IE, need to reclaim lost focus for the editor in order to restore the selection
526 onTriggerClick: function () {
527 Ext
.ux
.form
.HTMLAreaCombo
.superclass
.onTriggerClick
.call(this);
528 // In IE, avoid focus being stolen and selection being lost
530 this.triggered
= true;
531 this.getEditor().focus();
535 * Handler invoked when the list of options is clicked in
537 onViewClick: function (doFocus
) {
538 // Avoid stealing focus from the editor
539 Ext
.ux
.form
.HTMLAreaCombo
.superclass
.onViewClick
.call(this, false);
542 * Handler invoked in IE when the mouse moves out of the editor iframe
544 saveSelection: function (event
) {
545 var editor
= this.getEditor();
546 if (editor
.document
.hasFocus()) {
547 this.savedRange
= editor
._createRange(editor
._getSelection());
551 * Handler invoked in IE when the editor gets the focus back
553 restoreSelection: function (event
) {
554 if (!Ext
.isEmpty(this.savedRange
) && this.triggered
) {
555 this.getEditor().selectRange(this.savedRange
);
556 this.triggered
= false;
560 * Handler invoked when the enter key is pressed while the combo has focus
562 onSpecialKey: function (combo
, event
) {
563 if (event
.getKey() == event
.ENTER
) {
569 * Handler invoked when a hot key configured for this dropdown list is pressed
571 onHotKey: function (key
) {
572 if (!this.disabled
) {
573 this.plugins
.onHotKey(this.getEditor(), key
);
575 this.getEditor().focus();
577 this.getToolbar().update();
582 * Handler invoked when the toolbar is updated
584 onUpdateToolbar: function (mode
, selectionEmpty
, ancestors
, endPointsInSameBlock
) {
585 this.setDisabled(mode
=== 'textmode' && !this.textMode
);
586 if (!this.disabled
) {
587 this.plugins
['onUpdateToolbar'](this, mode
, selectionEmpty
, ancestors
, endPointsInSameBlock
);
591 * The iframe must have been rendered
593 onFrameworkReady: function () {
594 var iframe
= this.getEditor().iframe
;
595 // Close the combo on a click in the iframe
596 // Note: ExtJS is monitoring events only on the parent window
597 this.mon(Ext
.get(iframe
.document
.documentElement
), 'click', this.collapse
, this);
598 // Special handling for combo stealing focus in IE
600 // Take a bookmark in case the editor looses focus by activation of this combo
601 this.mon(iframe
.getEl(), 'mouseleave', this.saveSelection
, this);
602 // Restore the selection if combo was triggered
603 this.mon(iframe
.getEl(), 'focus', this.restoreSelection
, this);
609 onBeforeDestroy: function () {
610 this.savedRange
= null;
611 this.getStore().removeAll();
612 this.getStore().destroy();
615 Ext
.reg('htmlareacombo', Ext
.ux
.form
.HTMLAreaCombo
);
616 /***************************************************
618 ***************************************************/
620 * HTMLArea.Toolbar extends Ext.Container
622 HTMLArea
.Toolbar
= Ext
.extend(Ext
.Container
, {
626 initComponent: function () {
627 HTMLArea
.Toolbar
.superclass
.initComponent
.call(this);
631 * Fires when the toolbar is updated
635 // Build the deferred toolbar update task
636 this.updateLater
= new Ext
.util
.DelayedTask(this.update
, this);
637 // Add the toolbar items
641 fn
: this.initEventListeners
,
647 * Initialize listeners
649 initEventListeners: function () {
652 fn
: this.onBeforeDestroy
,
656 // Monitor editor becoming ready
657 this.mon(this.getEditor(), 'editorready', this.update
, this, {single
: true});
660 * editorId should be set in config
664 * Get a reference to the editor
666 getEditor: function() {
667 return RTEarea
[this.editorId
].editor
;
670 * Create the toolbar items based on editor toolbar configuration
672 addItems: function () {
673 var editor
= this.getEditor();
674 // Walk through the editor toolbar configuration nested arrays: [ toolbar [ row [ group ] ] ]
675 var firstOnRow
= true;
676 var firstInGroup
= true;
677 Ext
.each(editor
.config
.toolbar
, function (row
) {
679 // If a visible item was added to the previous line
682 cls
: 'x-form-clear-left'
687 Ext
.each(row
, function (group
) {
688 // To do: this.config.keepButtonGroupTogether ...
689 if (!firstOnRow
&& !firstInGroup
) {
690 // If a visible item was added to the line
692 xtype
: 'tbseparator',
698 Ext
.each(group
, function (item
) {
699 if (item
== 'space') {
705 // Get the item's config as registered by some plugin
706 var itemConfig
= editor
.config
.buttonsConfig
[item
];
707 if (!Ext
.isEmpty(itemConfig
)) {
708 itemConfig
.id
= this.editorId
+ '-' + itemConfig
.id
;
709 this.add(itemConfig
);
710 firstInGroup
= firstInGroup
&& itemConfig
.hidden
;
711 firstOnRow
= firstOnRow
&& firstInGroup
;
722 cls
: 'x-form-clear-left'
726 * Retrieve a toolbar item by itemId
728 getButton: function (buttonId
) {
729 return this.find('itemId', buttonId
)[0];
732 * Update the state of the toolbar
735 var editor
= this.getEditor(),
736 mode
= editor
.getMode(),
737 selectionEmpty
= true,
739 endPointsInSameBlock
= true;
740 if (editor
.getMode() === 'wysiwyg') {
741 selectionEmpty
= editor
._selectionEmpty(editor
._getSelection());
742 ancestors
= editor
.getAllAncestors();
743 endPointsInSameBlock
= editor
.endPointsInSameBlock();
745 this.fireEvent('update', mode
, selectionEmpty
, ancestors
, endPointsInSameBlock
);
750 onBeforeDestroy: function () {
751 this.removeAll(true);
755 Ext
.reg('htmlareatoolbar', HTMLArea
.Toolbar
);
757 * HTMLArea.Iframe extends Ext.BoxComponent
759 HTMLArea
.Iframe
= Ext
.extend(Ext
.BoxComponent
, {
763 initComponent: function () {
764 HTMLArea
.Iframe
.superclass
.initComponent
.call(this);
768 * Fires when the iframe style sheets become accessible
774 fn
: this.initEventListeners
,
778 fn
: this.onBeforeDestroy
,
782 this.config
= this.getEditor().config
;
783 if (!this.config
.showStatusBar
) {
784 this.addClass('noStatusBar');
788 * Initialize event listeners and the document after the iframe has rendered
790 initEventListeners: function () {
791 this.initStyleChangeEventListener();
793 this.mon(this.getEl(), 'load', this.initializeIframe
, this, {single
: true});
795 this.initializeIframe();
799 * The editor iframe may become hidden with style.display = "none" on some parent div
800 * This breaks the editor in Firefox: the designMode attribute needs to be reset after the style.display of the container div is reset to "block"
801 * In all browsers, it breaks the evaluation of the framework dimensions
803 initStyleChangeEventListener: function () {
804 if (this.isNested
&& !Ext
.isWebKit
) {
811 Ext
.each(this.nestedParentElements
.sorted
, function (nested
) {
813 options
.target
= Ext
.get(nested
);
817 Ext
.isIE
? 'propertychange' : 'DOMAttrModified',
826 * editorId should be set in config
830 * Get a reference to the editor
832 getEditor: function() {
833 return RTEarea
[this.editorId
].editor
;
836 * Get a reference to the toolbar
838 getToolbar: function () {
839 return this.ownerCt
.getTopToolbar();
842 * Get a reference to a button
844 getButton: function (buttonId
) {
845 return this.getToolbar().getButton(buttonId
);
848 * Flag set to true when the iframe becomes usable for editing
852 * Create the iframe element at rendering time
854 onRender: function (ct
, position
){
855 // from Ext.Component
856 if (!this.el
&& this.autoEl
) {
857 if (Ext
.isString(this.autoEl
)) {
858 this.el
= document
.createElement(this.autoEl
);
860 // ExtJS Default method will not work with iframe element
861 this.el
= Ext
.DomHelper
.append(ct
, this.autoEl
, true);
864 this.el
.id
= this.getId();
867 // from Ext.BoxComponent
869 this.resizeEl
= Ext
.get(this.resizeEl
);
871 if (this.positionEl
){
872 this.positionEl
= Ext
.get(this.positionEl
);
876 * Proceed to build the iframe document head and ensure style sheets are available after the iframe document becomes available
878 initializeIframe: function () {
879 var iframe
= this.getEl().dom
;
881 if (!iframe
|| (!iframe
.contentWindow
&& !iframe
.contentDocument
)) {
882 this.initializeIframe
.defer(50, this);
884 } else if (iframe
.contentWindow
&& !Ext
.isWebKit
&& (!iframe
.contentWindow
.document
|| !iframe
.contentWindow
.document
.documentElement
)) {
885 this.initializeIframe
.defer(50, this);
887 } else if (Ext
.isWebKit
&& (!iframe
.contentDocument
.documentElement
|| !iframe
.contentDocument
.body
)) {
888 this.initializeIframe
.defer(50, this);
890 this.document
= iframe
.contentWindow
? iframe
.contentWindow
.document
: iframe
.contentDocument
;
891 this.getEditor().document
= this.document
;
892 this.getEditor()._doc
= this.document
;
893 this.getEditor()._iframe
= iframe
;
895 this.getStyleSheets();
899 * Build the iframe document head
901 createHead: function () {
902 var head
= this.document
.getElementsByTagName('head')[0];
904 head
= this.document
.createElement('head');
905 this.document
.documentElement
.appendChild(head
);
907 if (this.config
.baseURL
) {
908 var base
= this.document
.getElementsByTagName('base')[0];
910 base
= this.document
.createElement('base');
911 base
.href
= this.config
.baseURL
;
912 head
.appendChild(base
);
914 HTMLArea
._appendToLog('[HTMLArea.Iframe::createHead]: Iframe baseURL set to: ' + this.config
.baseURL
);
916 var link0
= this.document
.getElementsByTagName('link')[0];
918 link0
= this.document
.createElement('link');
919 link0
.rel
= 'stylesheet';
920 link0
.href
= this.config
.editedContentStyle
;
921 head
.appendChild(link0
);
922 HTMLArea
._appendToLog('[HTMLArea.Iframe::createHead]: Skin CSS set to: ' + this.config
.editedContentStyle
);
924 if (this.config
.defaultPageStyle
) {
925 var link
= this.document
.getElementsByTagName('link')[1];
927 link
= this.document
.createElement('link');
928 link
.rel
= 'stylesheet';
929 link
.href
= this.config
.defaultPageStyle
;
930 head
.appendChild(link
);
932 HTMLArea
._appendToLog('[HTMLArea.Iframe::createHead]: Override CSS set to: ' + this.config
.defaultPageStyle
);
934 if (this.config
.pageStyle
) {
935 var link
= this.document
.getElementsByTagName('link')[2];
937 link
= this.document
.createElement('link');
938 link
.rel
= 'stylesheet';
939 link
.href
= this.config
.pageStyle
;
940 head
.appendChild(link
);
942 HTMLArea
._appendToLog('[HTMLArea.Iframe::createHead]: Content CSS set to: ' + this.config
.pageStyle
);
944 HTMLArea
._appendToLog('[HTMLArea.Iframe::createHead]: Editor iframe document head successfully built.');
947 * Fire event 'iframeready' when the iframe style sheets become accessible
949 getStyleSheets: function () {
950 var stylesAreLoaded
= true;
954 if (this.document
.readyState
!= 'complete') {
955 stylesAreLoaded
= false;
956 errorText
= 'Document.readyState not complete';
959 // Test if the styleSheets array is at all accessible
961 try { rules
= this.document
.styleSheets
[0].rules
; } catch(e
) { stylesAreLoaded
= false; errorText
= e
; }
963 try { rules
= this.document
.styleSheets
[0].cssRules
; } catch(e
) { stylesAreLoaded
= false; errorText
= e
; }
965 // Then test if all stylesheets are accessible
966 if (stylesAreLoaded
) {
967 Ext
.each(this.document
.styleSheets
, function (styleSheet
) {
969 try { rules
= styleSheet
.rules
; } catch(e
) { stylesAreLoaded
= false; errorText
= e
; return false; }
970 try { rules
= styleSheet
.imports
; } catch(e
) { stylesAreLoaded
= false; errorText
= e
; return false; }
972 try { rules
= styleSheet
.cssRules
; } catch(e
) { stylesAreLoaded
= false; errorText
= e
; return false; }
977 if (!stylesAreLoaded
) {
978 this.getStyleSheets
.defer(100, this);
979 HTMLArea
._appendToLog('[HTMLArea.Iframe::getStyleSheets]: Stylesheets not yet loaded (' + errorText
+ '). Retrying...');
980 if (/Security/i.test(errorText
)) {
981 HTMLArea
._appendToLog('ERROR [HTMLArea.Iframe::getStyleSheets]: A security error occurred. Make sure all stylesheets are accessed from the same domain/subdomain and using the same protocol as the current script.');
984 HTMLArea
._appendToLog('[HTMLArea.Iframe::getStyleSheets]: Stylesheets successfully accessed.');
985 // Style the document body
986 Ext
.get(this.document
.body
).addClass('htmlarea-content-body');
987 // Start listening to things happening in the iframe
988 // For some unknown reason, this is too early for Opera
990 this.startListening();
996 this.fireEvent('iframeready');
1000 * Focus on the iframe
1002 focus: function () {
1005 this.getEl().dom
.focus();
1007 this.getEl().dom
.contentWindow
.focus();
1012 * Flag indicating whether the framework is inside a tab or inline element that may be hidden
1013 * Should be set in config
1017 * All nested tabs and inline levels in the sorting order they were applied
1018 * Should be set in config
1020 nestedParentElements
: {},
1024 * @param boolean on: if true set designMode to on, otherwise set to off
1028 setDesignMode: function (on
) {
1032 // In Firefox, we can't set designMode when we are in a hidden TYPO3 tab or inline element
1033 if (!this.isNested
|| HTMLArea
.util
.TYPO3
.allElementsAreDisplayed(this.nestedParentElements
.sorted
)) {
1034 this.document
.designMode
= 'on';
1038 this.document
.designMode
= 'on';
1042 if (Ext
.isIE
|| Ext
.isWebKit
) {
1043 this.document
.body
.contentEditable
= true;
1047 this.document
.designMode
= 'off';
1049 if (Ext
.isIE
|| Ext
.isWebKit
) {
1050 this.document
.body
.contentEditable
= false;
1055 * Set editing mode options (if we can... raises exception in Firefox 3)
1059 setOptions: function () {
1062 if (this.document
.queryCommandEnabled('insertBrOnReturn')) {
1063 this.document
.execCommand('insertBrOnReturn', false, this.config
.disableEnterParagraphs
);
1065 if (this.document
.queryCommandEnabled('styleWithCSS')) {
1066 this.document
.execCommand('styleWithCSS', false, this.config
.useCSS
);
1067 } else if (Ext
.isGecko
&& this.document
.queryCommandEnabled('useCSS')) {
1068 this.document
.execCommand('useCSS', false, !this.config
.useCSS
);
1071 if (this.document
.queryCommandEnabled('enableObjectResizing')) {
1072 this.document
.execCommand('enableObjectResizing', false, !this.config
.disableObjectResizing
);
1074 if (this.document
.queryCommandEnabled('enableInlineTableEditing')) {
1075 this.document
.execCommand('enableInlineTableEditing', false, (this.config
.buttons
.table
&& this.config
.buttons
.table
.enableHandles
) ? true : false);
1082 * Handler invoked when an hidden TYPO3 hidden nested tab or inline element is shown
1084 onNestedShow: function (event
, target
) {
1085 var styleEvent
= true;
1086 // In older versions of Gecko attrName is not set and refering to it causes a non-catchable crash
1087 if ((Ext
.isGecko
&& navigator
.productSub
> 20071127) || Ext
.isOpera
) {
1088 styleEvent
= (event
.browserEvent
.attrName
== 'style');
1089 } else if (Ext
.isIE
) {
1090 styleEvent
= (event
.browserEvent
.propertyName
== 'style.display');
1092 if (styleEvent
&& this.nestedParentElements
.sorted
.indexOf(target
.id
) != -1 && (target
.style
.display
== '' || target
.style
.display
== 'block')) {
1093 // Check if all container nested elements are displayed
1094 if (HTMLArea
.util
.TYPO3
.allElementsAreDisplayed(this.nestedParentElements
.sorted
)) {
1095 if (this.getEditor().getMode() === 'wysiwyg') {
1097 this.setDesignMode(true);
1099 this.fireEvent('show');
1101 this.ownerCt
.textAreaContainer
.fireEvent('show');
1103 this.getToolbar().update();
1109 * Get the HTML content of the iframe
1111 getHTML: function () {
1112 return HTMLArea
.getHTML(this.document
.body
, false, this.getEditor());
1115 * Start listening to things happening in the iframe
1117 startListening: function () {
1118 // Create keyMap so that plugins may bind key handlers
1119 this.keyMap
= new Ext
.KeyMap(Ext
.get(this.document
.documentElement
), [], (Ext
.isIE
|| Ext
.isWebKit
) ? 'keydown' : 'keypress');
1121 this.keyMap
.addBinding([
1123 key
: [Ext
.EventObject
.DOWN
, Ext
.EventObject
.UP
, Ext
.EventObject
.LEFT
, Ext
.EventObject
.RIGHT
],
1125 handler
: this.onArrow
,
1129 key
: Ext
.EventObject
.TAB
,
1132 handler
: this.onTab
,
1136 key
: Ext
.EventObject
.SPACE
,
1140 handler
: this.onCtrlSpace
,
1144 if (Ext
.isGecko
|| Ext
.isIE
) {
1145 this.keyMap
.addBinding(
1147 key
: [Ext
.EventObject
.BACKSPACE
, Ext
.EventObject
.DELETE
],
1149 handler
: this.onBackSpace
,
1153 if (!Ext
.isIE
&& !this.config
.disableEnterParagraphs
) {
1154 this.keyMap
.addBinding(
1156 key
: Ext
.EventObject
.ENTER
,
1158 handler
: this.onEnter
,
1163 this.keyMap
.addBinding(
1165 key
: Ext
.EventObject
.ENTER
,
1167 handler
: this.onWebKitEnter
,
1171 // Hot key map (on keydown for all browsers)
1173 Ext
.iterate(this.config
.hotKeyList
, function (key
) {
1174 if (key
.length
== 1) {
1175 hotKeys
+= key
.toUpperCase();
1178 // Make hot key map available, even if empty, so that plugins may add bindings
1179 this.hotKeyMap
= new Ext
.KeyMap(Ext
.get(this.document
.documentElement
));
1180 if (!Ext
.isEmpty(hotKeys
)) {
1181 this.hotKeyMap
.addBinding({
1186 handler
: this.onHotKey
,
1190 this.mon(Ext
.get(this.document
.documentElement
), (Ext
.isIE
|| Ext
.isWebKit
) ? 'keydown' : 'keypress', this.onAnyKey
, this);
1191 this.mon(Ext
.get(this.document
.documentElement
), 'mouseup', this.onMouse
, this);
1192 this.mon(Ext
.get(this.document
.documentElement
), 'click', this.onMouse
, this);
1193 this.mon(Ext
.get(this.document
.documentElement
), Ext
.isWebKit
? 'dragend' : 'drop', this.onDrop
, this);
1196 * Handler for other key events
1198 onAnyKey: function(event
) {
1199 if (this.inhibitKeyboardInput(event
)) {
1202 /*****************************************************
1203 * onKeyPress DEPRECATED AS OF TYPO3 4.4 *
1204 *****************************************************/
1205 if (this.getEditor().hasPluginWithOnKeyPressHandler
) {
1206 var letBubble
= true;
1207 Ext
.iterate(this.getEditor().plugins
, function (pluginId
) {
1208 var plugin
= this.getEditor().getPlugin(pluginId
);
1209 if (Ext
.isFunction(plugin
.onKeyPress
)) {
1210 if (!plugin
.onKeyPress(event
.browserEvent
)) {
1221 if (!event
.altKey
&& !event
.ctrlKey
) {
1222 // Detect URL in non-IE browsers
1223 if (!Ext
.isIE
&& (event
.getKey() != Ext
.EventObject
.ENTER
|| (event
.shiftKey
&& !Ext
.isWebKit
))) {
1224 this.getEditor()._detectURL(event
);
1226 // Handle option+SPACE for Mac users
1227 if (Ext
.isMac
&& event
.browserEvent
.charCode
== 160) {
1228 return this.onOptionSpace(event
.browserEvent
.charCode
, event
);
1234 * On any key input event, check if input is currently inhibited
1236 inhibitKeyboardInput: function (event
) {
1237 // Inhibit key events while server-based cleaning is being processed
1238 if (this.getEditor().inhibitKeyboardInput
) {
1246 * Handler for mouse events
1248 onMouse: function () {
1249 this.getToolbar().updateLater
.delay(100);
1253 * Handlers for drag and drop operations
1255 onDrop: function (event
) {
1257 this.getEditor().cleanAppleStyleSpans
.defer(50, this.getEditor(), [this.getEditor().document
.body
]);
1259 this.getToolbar().updateLater
.delay(100);
1262 * Handler for UP, DOWN, LEFT and RIGHT keys
1264 onArrow: function () {
1265 this.getToolbar().updateLater
.delay(100);
1269 * Handler for TAB and SHIFT-TAB keys
1271 * If available, BlockElements plugin will handle the TAB key
1273 onTab: function (key
, event
) {
1274 if (this.inhibitKeyboardInput(event
)) {
1277 var keyName
= (event
.shiftKey
? 'SHIFT-' : '') + 'TAB';
1278 if (this.config
.hotKeyList
[keyName
] && this.config
.hotKeyList
[keyName
].cmd
) {
1279 var button
= this.getButton(this.config
.hotKeyList
[keyName
].cmd
);
1282 button
.fireEvent('hotkey', keyName
, event
);
1289 * Handler for BACKSPACE and DELETE keys
1291 onBackSpace: function (key
, event
) {
1292 if (this.inhibitKeyboardInput(event
)) {
1295 if ((!Ext
.isIE
&& !event
.shiftKey
) || Ext
.isIE
) {
1296 if (this.getEditor()._checkBackspace()) {
1300 // Update the toolbar state after some time
1301 this.getToolbar().updateLater
.delay(200);
1305 * Handler for ENTER key in non-IE browsers
1307 onEnter: function (key
, event
) {
1308 if (this.inhibitKeyboardInput(event
)) {
1311 this.getEditor()._detectURL(event
);
1312 if (this.getEditor()._checkInsertP()) {
1315 // Update the toolbar state after some time
1316 this.getToolbar().updateLater
.delay(200);
1320 * Handler for ENTER key in WebKit browsers
1322 onWebKitEnter: function (key
, event
) {
1323 if (this.inhibitKeyboardInput(event
)) {
1326 if (event
.shiftKey
|| this.config
.disableEnterParagraphs
) {
1327 var editor
= this.getEditor();
1328 editor
._detectURL(event
);
1330 var brNode
= editor
.document
.createElement('br');
1331 editor
.insertNodeAtSelection(brNode
);
1332 brNode
.parentNode
.normalize();
1333 // Selection issue when an URL was detected
1334 if (editor
._unlinkOnUndo
) {
1335 brNode
= brNode
.parentNode
.parentNode
.insertBefore(brNode
, brNode
.parentNode
.nextSibling
);
1337 if (!brNode
.nextSibling
|| !/\S+/i.test(brNode
.nextSibling
.textContent
)) {
1338 var secondBrNode
= editor
.document
.createElement('br');
1339 secondBrNode
= brNode
.parentNode
.appendChild(secondBrNode
);
1341 editor
.selectNode(brNode
, false);
1345 // Update the toolbar state after some time
1346 this.getToolbar().updateLater
.delay(200);
1350 * Handler for CTRL-SPACE keys
1352 onCtrlSpace: function (key
, event
) {
1353 if (this.inhibitKeyboardInput(event
)) {
1356 this.getEditor().insertHTML(' ');
1361 * Handler for OPTION-SPACE keys on Mac
1363 onOptionSpace: function (key
, event
) {
1364 if (this.inhibitKeyboardInput(event
)) {
1367 this.getEditor().insertHTML(' ');
1372 * Handler for configured hotkeys
1374 onHotKey: function (key
, event
) {
1375 if (this.inhibitKeyboardInput(event
)) {
1378 var hotKey
= String
.fromCharCode(key
).toLowerCase();
1379 this.getButton(this.config
.hotKeyList
[hotKey
].cmd
).fireEvent('hotkey', hotKey
, event
);
1385 onBeforeDestroy: function () {
1386 // ExtJS KeyMap object makes IE leak memory
1387 // Nullify EXTJS private handlers
1388 Ext
.each(this.keyMap
.bindings
, function (binding
, index
) {
1389 this.keyMap
.bindings
[index
] = null;
1391 this.keyMap
.handleKeyDown
= null;
1392 Ext
.each(this.hotKeyMap
.bindings
, function (binding
, index
) {
1393 this.hotKeyMap
.bindings
[index
] = null;
1395 this.hotKeyMap
.handleKeyDown
= null;
1396 this.keyMap
.disable();
1397 this.hotKeyMap
.disable();
1398 // Cleaning references to DOM in order to avoid IE memory leaks
1399 Ext
.get(this.document
.body
).purgeAllListeners();
1400 Ext
.get(this.document
.body
).dom
= null;
1401 Ext
.get(this.document
.documentElement
).purgeAllListeners();
1402 Ext
.get(this.document
.documentElement
).dom
= null;
1403 this.document
= null;
1404 this.getEditor().document
= null;
1405 this.getEditor()._doc
= null;
1406 this.getEditor()._iframe
= null;
1407 Ext
.each(this.nestedParentElements
.sorted
, function (nested
) {
1408 Ext
.get(nested
).purgeAllListeners();
1409 Ext
.get(nested
).dom
= null;
1411 Ext
.destroy(this.autoEl
, this.el
, this.resizeEl
, this.positionEl
);
1415 Ext
.reg('htmlareaiframe', HTMLArea
.Iframe
);
1417 * Ext JS Library 3.1.1
1418 * Copyright(c) 2006-2010 Ext JS, LLC
1419 * licensing@extjs.com
1420 * http://www.extjs.com/license
1423 * @class Ext.ux.StatusBar
1424 * <p>Basic status bar component that can be used as the bottom toolbar of any {@link Ext.Panel}. In addition to
1425 * supporting the standard {@link Ext.Toolbar} interface for adding buttons, menus and other items, the StatusBar
1426 * provides a greedy status element that can be aligned to either side and has convenient methods for setting the
1427 * status text and icon. You can also indicate that something is processing using the {@link #showBusy} method.</p>
1432 bbar: new Ext.ux.StatusBar({
1435 // defaults to use when the status is cleared:
1436 defaultText: 'Default status text',
1437 defaultIconCls: 'default-icon',
1439 // values to set initially:
1441 iconCls: 'ready-icon',
1443 // any standard Toolbar items:
1446 }, '-', 'Plain Text']
1450 // Update the status bar later in code:
1451 var sb = Ext.getCmp('my-status');
1455 clear: true // auto-clear after a set interval
1458 // Set the status bar to show that something is processing:
1463 sb.clearStatus(); // once completeed
1465 * @extends Ext.Toolbar
1467 * Creates a new StatusBar
1468 * @param {Object/Array} config A config object
1470 Ext
.ux
.StatusBar
= Ext
.extend(Ext
.Toolbar
, {
1472 * @cfg {String} statusAlign
1473 * The alignment of the status element within the overall StatusBar layout. When the StatusBar is rendered,
1474 * it creates an internal div containing the status text and icon. Any additional Toolbar items added in the
1475 * StatusBar's {@link #items} config, or added via {@link #add} or any of the supported add* methods, will be
1476 * rendered, in added order, to the opposite side. The status element is greedy, so it will automatically
1477 * expand to take up all sapce left over by any other items. Example usage:
1479 // Create a left-aligned status bar containing a button,
1480 // separator and text item that will be right-aligned (default):
1484 bbar: new Ext.ux.StatusBar({
1485 defaultText: 'Default status text',
1489 }, '-', 'Plain Text']
1493 // By adding the statusAlign config, this will create the
1494 // exact same toolbar, except the status and toolbar item
1495 // layout will be reversed from the previous example:
1499 bbar: new Ext.ux.StatusBar({
1500 defaultText: 'Default status text',
1502 statusAlign: 'right',
1505 }, '-', 'Plain Text']
1511 * @cfg {String} defaultText
1512 * The default {@link #text} value. This will be used anytime the status bar is cleared with the
1513 * <tt>useDefaults:true</tt> option (defaults to '').
1516 * @cfg {String} defaultIconCls
1517 * The default {@link #iconCls} value (see the iconCls docs for additional details about customizing the icon).
1518 * This will be used anytime the status bar is cleared with the <tt>useDefaults:true</tt> option (defaults to '').
1521 * @cfg {String} text
1522 * A string that will be <b>initially</b> set as the status message. This string
1523 * will be set as innerHTML (html tags are accepted) for the toolbar item.
1524 * If not specified, the value set for <code>{@link #defaultText}</code>
1528 * @cfg {String} iconCls
1529 * A CSS class that will be <b>initially</b> set as the status bar icon and is
1530 * expected to provide a background image (defaults to '').
1531 * Example usage:<pre><code>
1532 // Example CSS rule:
1533 .x-statusbar .x-status-custom {
1535 background: transparent url(images/custom-icon.gif) no-repeat 3px 2px;
1538 // Setting a default icon:
1539 var sb = new Ext.ux.StatusBar({
1540 defaultIconCls: 'x-status-custom'
1543 // Changing the icon:
1546 iconCls: 'x-status-custom'
1553 * The base class applied to the containing element for this component on render (defaults to 'x-statusbar')
1555 cls
: 'x-statusbar',
1557 * @cfg {String} busyIconCls
1558 * The default <code>{@link #iconCls}</code> applied when calling
1559 * <code>{@link #showBusy}</code> (defaults to <tt>'x-status-busy'</tt>).
1560 * It can be overridden at any time by passing the <code>iconCls</code>
1561 * argument into <code>{@link #showBusy}</code>.
1563 busyIconCls
: 'x-status-busy',
1565 * @cfg {String} busyText
1566 * The default <code>{@link #text}</code> applied when calling
1567 * <code>{@link #showBusy}</code> (defaults to <tt>'Loading...'</tt>).
1568 * It can be overridden at any time by passing the <code>text</code>
1569 * argument into <code>{@link #showBusy}</code>.
1571 busyText
: 'Loading...',
1573 * @cfg {Number} autoClear
1574 * The number of milliseconds to wait after setting the status via
1575 * <code>{@link #setStatus}</code> before automatically clearing the status
1576 * text and icon (defaults to <tt>5000</tt>). Note that this only applies
1577 * when passing the <tt>clear</tt> argument to <code>{@link #setStatus}</code>
1578 * since that is the only way to defer clearing the status. This can
1579 * be overridden by specifying a different <tt>wait</tt> value in
1580 * <code>{@link #setStatus}</code>. Calls to <code>{@link #clearStatus}</code>
1581 * always clear the status bar immediately and ignore this value.
1586 * @cfg {String} emptyText
1587 * The text string to use if no text has been set. Defaults to
1588 * <tt>' '</tt>). If there are no other items in the toolbar using
1589 * an empty string (<tt>''</tt>) for this value would end up in the toolbar
1590 * height collapsing since the empty string will not maintain the toolbar
1591 * height. Use <tt>''</tt> if the toolbar should collapse in height
1592 * vertically when no text is specified and there are no other items in
1595 emptyText
: ' ',
1601 initComponent : function(){
1602 if(this.statusAlign
=='right'){
1603 this.cls
+= ' x-status-right';
1605 Ext
.ux
.StatusBar
.superclass
.initComponent
.call(this);
1609 afterRender : function(){
1610 Ext
.ux
.StatusBar
.superclass
.afterRender
.call(this);
1612 var right
= this.statusAlign
== 'right';
1613 this.currIconCls
= this.iconCls
|| this.defaultIconCls
;
1614 this.statusEl
= new Ext
.Toolbar
.TextItem({
1615 cls
: 'x-status-text ' + (this.currIconCls
|| ''),
1616 text
: this.text
|| this.defaultText
|| ''
1621 this.add(this.statusEl
);
1623 this.insert(0, this.statusEl
);
1624 this.insert(1, '->');
1630 * Sets the status {@link #text} and/or {@link #iconCls}. Also supports automatically clearing the
1631 * status that was set after a specified interval.
1632 * @param {Object/String} config A config object specifying what status to set, or a string assumed
1633 * to be the status text (and all other options are defaulted as explained below). A config
1634 * object containing any or all of the following properties can be passed:<ul>
1635 * <li><tt>text</tt> {String} : (optional) The status text to display. If not specified, any current
1636 * status text will remain unchanged.</li>
1637 * <li><tt>iconCls</tt> {String} : (optional) The CSS class used to customize the status icon (see
1638 * {@link #iconCls} for details). If not specified, any current iconCls will remain unchanged.</li>
1639 * <li><tt>clear</tt> {Boolean/Number/Object} : (optional) Allows you to set an internal callback that will
1640 * automatically clear the status text and iconCls after a specified amount of time has passed. If clear is not
1641 * specified, the new status will not be auto-cleared and will stay until updated again or cleared using
1642 * {@link #clearStatus}. If <tt>true</tt> is passed, the status will be cleared using {@link #autoClear},
1643 * {@link #defaultText} and {@link #defaultIconCls} via a fade out animation. If a numeric value is passed,
1644 * it will be used as the callback interval (in milliseconds), overriding the {@link #autoClear} value.
1645 * All other options will be defaulted as with the boolean option. To customize any other options,
1646 * you can pass an object in the format:<ul>
1647 * <li><tt>wait</tt> {Number} : (optional) The number of milliseconds to wait before clearing
1648 * (defaults to {@link #autoClear}).</li>
1649 * <li><tt>anim</tt> {Number} : (optional) False to clear the status immediately once the callback
1650 * executes (defaults to true which fades the status out).</li>
1651 * <li><tt>useDefaults</tt> {Number} : (optional) False to completely clear the status text and iconCls
1652 * (defaults to true which uses {@link #defaultText} and {@link #defaultIconCls}).</li>
1654 * Example usage:<pre><code>
1655 // Simple call to update the text
1656 statusBar.setStatus('New status');
1658 // Set the status and icon, auto-clearing with default options:
1659 statusBar.setStatus({
1661 iconCls: 'x-status-custom',
1665 // Auto-clear with custom options:
1666 statusBar.setStatus({
1668 iconCls: 'x-status-custom',
1676 * @return {Ext.ux.StatusBar} this
1678 setStatus : function(o
){
1681 if(typeof o
== 'string'){
1684 if(o
.text
!== undefined){
1685 this.setText(o
.text
);
1687 if(o
.iconCls
!== undefined){
1688 this.setIcon(o
.iconCls
);
1693 wait
= this.autoClear
,
1694 defaults
= {useDefaults
: true, anim
: true};
1696 if(typeof c
== 'object'){
1697 c
= Ext
.applyIf(c
, defaults
);
1701 }else if(typeof c
== 'number'){
1704 }else if(typeof c
== 'boolean'){
1708 c
.threadId
= this.activeThreadId
;
1709 this.clearStatus
.defer(wait
, this, [c
]);
1715 * Clears the status {@link #text} and {@link #iconCls}. Also supports clearing via an optional fade out animation.
1716 * @param {Object} config (optional) A config object containing any or all of the following properties. If this
1717 * object is not specified the status will be cleared using the defaults below:<ul>
1718 * <li><tt>anim</tt> {Boolean} : (optional) True to clear the status by fading out the status element (defaults
1719 * to false which clears immediately).</li>
1720 * <li><tt>useDefaults</tt> {Boolean} : (optional) True to reset the text and icon using {@link #defaultText} and
1721 * {@link #defaultIconCls} (defaults to false which sets the text to '' and removes any existing icon class).</li>
1723 * @return {Ext.ux.StatusBar} this
1725 clearStatus : function(o
){
1728 if(o
.threadId
&& o
.threadId
!== this.activeThreadId
){
1729 // this means the current call was made internally, but a newer
1730 // thread has set a message since this call was deferred. Since
1731 // we don't want to overwrite a newer message just ignore.
1735 var text
= o
.useDefaults
? this.defaultText
: this.emptyText
,
1736 iconCls
= o
.useDefaults
? (this.defaultIconCls
? this.defaultIconCls
: '') : '';
1739 // animate the statusEl Ext.Element
1740 this.statusEl
.el
.fadeOut({
1744 callback: function(){
1750 this.statusEl
.el
.show();
1754 // hide/show the el to avoid jumpy text or icon
1755 this.statusEl
.hide();
1760 this.statusEl
.show();
1766 * Convenience method for setting the status text directly. For more flexible options see {@link #setStatus}.
1767 * @param {String} text (optional) The text to set (defaults to '')
1768 * @return {Ext.ux.StatusBar} this
1770 setText : function(text
){
1771 this.activeThreadId
++;
1772 this.text
= text
|| '';
1774 this.statusEl
.setText(this.text
);
1780 * Returns the current status text.
1781 * @return {String} The status text
1783 getText : function(){
1788 * Convenience method for setting the status icon directly. For more flexible options see {@link #setStatus}.
1789 * See {@link #iconCls} for complete details about customizing the icon.
1790 * @param {String} iconCls (optional) The icon class to set (defaults to '', and any current icon class is removed)
1791 * @return {Ext.ux.StatusBar} this
1793 setIcon : function(cls
){
1794 this.activeThreadId
++;
1798 if(this.currIconCls
){
1799 this.statusEl
.removeClass(this.currIconCls
);
1800 this.currIconCls
= null;
1803 this.statusEl
.addClass(cls
);
1804 this.currIconCls
= cls
;
1807 this.currIconCls
= cls
;
1813 * Convenience method for setting the status text and icon to special values that are pre-configured to indicate
1814 * a "busy" state, usually for loading or processing activities.
1815 * @param {Object/String} config (optional) A config object in the same format supported by {@link #setStatus}, or a
1816 * string to use as the status text (in which case all other options for setStatus will be defaulted). Use the
1817 * <tt>text</tt> and/or <tt>iconCls</tt> properties on the config to override the default {@link #busyText}
1818 * and {@link #busyIconCls} settings. If the config argument is not specified, {@link #busyText} and
1819 * {@link #busyIconCls} will be used in conjunction with all of the default options for {@link #setStatus}.
1820 * @return {Ext.ux.StatusBar} this
1822 showBusy : function(o
){
1823 if(typeof o
== 'string'){
1826 o
= Ext
.applyIf(o
|| {}, {
1827 text
: this.busyText
,
1828 iconCls
: this.busyIconCls
1830 return this.setStatus(o
);
1833 Ext
.reg('statusbar', Ext
.ux
.StatusBar
);
1835 * HTMLArea.StatusBar extends Ext.Container
1837 HTMLArea
.StatusBar
= Ext
.extend(Ext
.Container
, {
1841 initComponent: function () {
1842 HTMLArea
.StatusBar
.superclass
.initComponent
.call(this);
1845 fn
: this.addComponents
,
1849 fn
: this.initEventListeners
,
1855 * Initialize listeners
1857 initEventListeners: function () {
1860 fn
: this.onBeforeDestroy
,
1864 // Monitor toolbar updates in order to refresh the contents of the statusbar
1865 // The toolbar must have been rendered
1866 this.mon(this.ownerCt
.toolbar
, 'update', this.onUpdateToolbar
, this);
1867 // Monitor editor changing mode
1868 this.mon(this.getEditor(), 'modeChange', this.onModeChange
, this);
1871 * editorId should be set in config
1875 * Get a reference to the editor
1877 getEditor: function() {
1878 return RTEarea
[this.editorId
].editor
;
1881 * Create span elements to display when the status bar tree or a message when the editor is in text mode
1883 addComponents: function () {
1884 this.statusBarTree
= Ext
.DomHelper
.append(this.getEl(), {
1885 id
: this.editorId
+ '-statusBarTree',
1887 cls
: 'statusBarTree',
1888 html
: HTMLArea
.I18N
.msg
['Path'] + ': '
1889 }, true).setVisibilityMode(Ext
.Element
.DISPLAY
).setVisible(true);
1890 this.statusBarTextMode
= Ext
.DomHelper
.append(this.getEl(), {
1891 id
: this.editorId
+ '-statusBarTextMode',
1893 cls
: 'statusBarTextMode',
1894 html
: HTMLArea
.I18N
.msg
['TEXT_MODE']
1895 }, true).setVisibilityMode(Ext
.Element
.DISPLAY
).setVisible(false);
1898 * Clear the status bar tree
1900 clear: function () {
1901 this.statusBarTree
.removeAllListeners();
1902 Ext
.each(this.statusBarTree
.query('a'), function (node
) {
1903 Ext
.QuickTips
.unregister(node
);
1904 Ext
.get(node
).dom
.ancestor
= null;
1907 this.statusBarTree
.update('');
1908 this.setSelection(null);
1911 * Flag indicating that the status bar should not be updated on this toolbar update
1915 * Update the status bar
1917 onUpdateToolbar: function (mode
, selectionEmpty
, ancestors
, endPointsInSameBlock
) {
1918 if (mode
=== 'wysiwyg' && !this.noUpdate
) {
1921 languageObject
= this.getEditor().getPlugin('Language'),
1922 classes
= new Array(),
1925 var path
= Ext
.DomHelper
.append(this.statusBarTree
, {
1927 html
: HTMLArea
.I18N
.msg
['Path'] + ': '
1929 Ext
.each(ancestors
, function (ancestor
, index
) {
1933 text
= ancestor
.nodeName
.toLowerCase();
1934 // Do not show any id generated by ExtJS
1935 if (ancestor
.id
&& text
!== 'body' && ancestor
.id
.substr(0, 7) !== 'ext-gen') {
1936 text
+= '#' + ancestor
.id
;
1938 if (languageObject
&& languageObject
.getLanguageAttribute
) {
1939 language
= languageObject
.getLanguageAttribute(ancestor
);
1940 if (language
!= 'none') {
1941 text
+= '[' + language
+ ']';
1944 if (ancestor
.className
) {
1946 classes
= ancestor
.className
.trim().split(' ');
1947 for (var j
= 0, n
= classes
.length
; j
< n
; ++j
) {
1948 if (!HTMLArea
.reservedClassNames
.test(classes
[j
])) {
1949 classText
+= '.' + classes
[j
];
1954 var element
= Ext
.DomHelper
.insertAfter(path
, {
1957 'ext:qtitle': HTMLArea
.I18N
.dialogs
['statusBarStyle'],
1958 'ext:qtip': ancestor
.style
.cssText
.split(';').join('<br />'),
1961 // Ext.DomHelper does not honour the custom attribute
1962 element
.dom
.ancestor
= ancestor
;
1964 element
.on('click', this.onClick
, this);
1966 element
.on('mousedown', this.onMouseDown
, this);
1969 element
.on('contextmenu', this.onContextMenu
, this);
1972 Ext
.DomHelper
.insertAfter(element
, {
1974 html
: String
.fromCharCode(0xbb)
1979 this.noUpdate
= false;
1982 * Adapt status bar to current editor mode
1984 * @param string mode: the mode to which the editor got switched to
1986 onModeChange: function (mode
) {
1989 this.statusBarTextMode
.setVisible(false);
1990 this.statusBarTree
.setVisible(true);
1994 this.statusBarTree
.setVisible(false);
1995 this.statusBarTextMode
.setVisible(true);
2000 * Refrence to the element last selected on the status bar
2004 * Get the status bar selection
2006 getSelection: function() {
2007 return this.selected
;
2010 * Set the status bar selection
2012 * @param object element: set the status bar selection to the given element
2014 setSelection: function(element
) {
2015 this.selected
= element
? element
: null;
2018 * Select the element that was clicked in the status bar and set the status bar selection
2020 selectElement: function (element
) {
2021 var editor
= this.getEditor();
2024 if (/^(img)$/i.test(element
.ancestor
.nodeName
)) {
2025 editor
.selectNode(element
.ancestor
);
2027 editor
.selectNodeContents(element
.ancestor
);
2030 if (/^(img|table)$/i.test(element
.ancestor
.nodeName
)) {
2031 var range
= editor
.document
.body
.createControlRange();
2032 range
.addElement(element
.ancestor
);
2035 editor
.selectNode(element
.ancestor
);
2038 this.setSelection(element
.ancestor
);
2039 this.noUpdate
= true;
2040 editor
.toolbar
.update();
2045 onClick: function (event
, element
) {
2046 this.selectElement(element
);
2053 onMouseDown: function (event
, element
) {
2054 this.selectElement(element
);
2063 * ContextMenu handler
2065 onContextMenu: function (event
, target
) {
2066 this.selectElement(target
);
2067 return this.getEditor().getPlugin('ContextMenu') ? this.getEditor().getPlugin('ContextMenu').show(event
, target
.ancestor
) : false;
2072 onBeforeDestroy: function() {
2074 this.removeAll(true);
2075 Ext
.destroy(this.statusBarTree
, this.statusBarTextMode
);
2079 Ext
.reg('htmlareastatusbar', HTMLArea
.StatusBar
);
2081 * HTMLArea.Framework extends Ext.Panel
2083 HTMLArea
.Framework
= Ext
.extend(Ext
.Panel
, {
2087 initComponent: function () {
2088 HTMLArea
.Framework
.superclass
.initComponent
.call(this);
2089 // Set some references
2090 this.toolbar
= this.getTopToolbar();
2091 this.statusBar
= this.getBottomToolbar();
2092 this.iframe
= this.getComponent('iframe');
2093 this.textAreaContainer
= this.getComponent('textAreaContainer');
2096 * @event frameworkready
2097 * Fires when the iframe is ready and all components are rendered
2103 fn
: this.initEventListeners
,
2107 // Let the framefork render itself, but it will fail to do so if inside a hidden tab or inline element
2108 if (!this.isNested
|| HTMLArea
.util
.TYPO3
.allElementsAreDisplayed(this.nestedParentElements
.sorted
)) {
2109 this.render(this.textArea
.parent(), this.textArea
.id
);
2111 // Clone the array of nested tabs and inline levels instead of using a reference as HTMLArea.util.TYPO3.accessParentElements will modify the array
2112 var parentElements
= [].concat(this.nestedParentElements
.sorted
);
2113 // Walk through all nested tabs and inline levels to get correct sizes
2114 HTMLArea
.util
.TYPO3
.accessParentElements(parentElements
, 'args[0].render(args[0].textArea.parent(), args[0].textArea.id)', [this]);
2118 * Initiate events monitoring
2120 initEventListeners: function () {
2121 // Monitor iframe becoming ready
2122 this.mon(this.iframe
, 'iframeready', this.onIframeReady
, this, {single
: true});
2123 // Make the framework resizable, if configured by the user
2124 this.makeResizable();
2125 // Monitor textArea container becoming shown or hidden as it may change the height of the status bar
2126 this.mon(this.textAreaContainer
, 'show', this.resizable
? this.onTextAreaShow
: this.onWindowResize
, this);
2127 // Monitor iframe becoming shown or hidden as it may change the height of the status bar
2128 this.mon(this.iframe
, 'show', this.resizable
? this.onIframeShow
: this.onWindowResize
, this);
2129 // Monitor window resizing
2130 Ext
.EventManager
.onWindowResize(this.onWindowResize
, this);
2131 // If the textarea is inside a form, on reset, re-initialize the HTMLArea content and update the toolbar
2132 var form
= this.textArea
.dom
.form
;
2134 if (Ext
.isFunction(form
.onreset
)) {
2135 if (typeof(form
.htmlAreaPreviousOnReset
) == 'undefined') {
2136 form
.htmlAreaPreviousOnReset
= [];
2138 form
.htmlAreaPreviousOnReset
.push(form
.onreset
);
2140 this.mon(Ext
.get(form
), 'reset', this.onReset
, this);
2144 fn
: this.onFrameworkResize
2147 fn
: this.onBeforeDestroy
,
2153 * editorId should be set in config
2157 * Get a reference to the editor
2159 getEditor: function() {
2160 return RTEarea
[this.editorId
].editor
;
2163 * Flag indicating whether the framework is inside a tab or inline element that may be hidden
2164 * Should be set in config
2168 * All nested tabs and inline levels in the sorting order they were applied
2169 * Should be set in config
2171 nestedParentElements
: {},
2173 * Flag set to true when the framework is ready
2177 * All nested tabs and inline levels in the sorting order they were applied
2178 * Should be set in config
2180 nestedParentElements
: {},
2182 * Whether the framework should be made resizable
2183 * May be set in config
2187 * Maximum height to which the framework may resized (in pixels)
2188 * May be set in config
2192 * Initial textArea dimensions
2193 * Should be set in config
2195 textAreaInitialSize
: {
2201 * doLayout will fail if inside a hidden tab or inline element
2203 doLayout: function () {
2204 if (!this.isNested
|| HTMLArea
.util
.TYPO3
.allElementsAreDisplayed(this.nestedParentElements
.sorted
)) {
2205 HTMLArea
.Framework
.superclass
.doLayout
.call(this);
2207 // Clone the array of nested tabs and inline levels instead of using a reference as HTMLArea.util.TYPO3.accessParentElements will modify the array
2208 var parentElements
= [].concat(this.nestedParentElements
.sorted
);
2209 // Walk through all nested tabs and inline levels to get correct sizes
2210 HTMLArea
.util
.TYPO3
.accessParentElements(parentElements
, 'HTMLArea.Framework.superclass.doLayout.call(args[0])', [this]);
2214 * Make the framework resizable, if configured
2216 makeResizable: function () {
2217 if (this.resizable
) {
2218 this.addClass('resizable');
2219 this.resizer
= new Ext
.Resizable(this.getEl(), {
2221 maxHeight
: this.maxHeight
,
2224 this.resizer
.on('resize', this.onHtmlAreaResize
, this);
2228 * Resize the framework when the resizer handles are used
2230 onHtmlAreaResize: function (resizer
, width
, height
, event
) {
2231 // Set width first as it may change the height of the toolbar and of the statusBar
2232 this.setWidth(width
);
2233 // Set height of iframe and textarea
2234 this.iframe
.setHeight(this.getInnerHeight());
2235 this.textArea
.setSize(this.getInnerWidth(), this.getInnerHeight());
2238 * Size the iframe according to initial textarea size as set by Page and User TSConfig
2240 onWindowResize: function (width
, height
) {
2241 if (!this.isNested
|| HTMLArea
.util
.TYPO3
.allElementsAreDisplayed(this.nestedParentElements
.sorted
)) {
2242 this.resizeFramework(width
, height
);
2244 // Clone the array of nested tabs and inline levels instead of using a reference as HTMLArea.util.TYPO3.accessParentElements will modify the array
2245 var parentElements
= [].concat(this.nestedParentElements
.sorted
);
2246 // Walk through all nested tabs and inline levels to get correct sizes
2247 HTMLArea
.util
.TYPO3
.accessParentElements(parentElements
, 'args[0].resizeFramework(args[1], args[2])', [this, width
, height
]);
2251 * Resize the framework to its initial size
2253 resizeFramework: function (width
, height
) {
2254 var frameworkHeight
= parseInt(this.textAreaInitialSize
.height
);
2255 if (this.textAreaInitialSize
.width
.indexOf('%') === -1) {
2256 // Width is specified in pixels
2257 var frameworkWidth
= parseInt(this.textAreaInitialSize
.width
) - this.getFrameWidth();
2259 // Width is specified in %
2260 if (Ext
.isNumber(width
)) {
2261 // Framework sizing on actual window resize
2262 var frameworkWidth
= parseInt(((width
- this.textAreaInitialSize
.wizardsWidth
- (this.fullScreen
? 10 : Ext
.getScrollBarWidth()) - this.getBox().x
- 15) * parseInt(this.textAreaInitialSize
.width
))/100);
2264 // Initial framework sizing
2265 var frameworkWidth
= parseInt(((HTMLArea
.util
.TYPO3
.getWindowSize().width
- this.textAreaInitialSize
.wizardsWidth
- (this.fullScreen
? 10 : Ext
.getScrollBarWidth()) - this.getBox().x
- 15) * parseInt(this.textAreaInitialSize
.width
))/100);
2268 if (this.resizable
) {
2269 this.resizer
.resizeTo(frameworkWidth
, frameworkHeight
);
2271 this.setSize(frameworkWidth
, frameworkHeight
);
2275 * Resize the framework components
2277 onFrameworkResize: function () {
2278 this.iframe
.setSize(this.getInnerWidth(), this.getInnerHeight());
2279 this.textArea
.setSize(this.getInnerWidth(), this.getInnerHeight());
2282 * Adjust the height to the changing size of the statusbar when the textarea is shown
2284 onTextAreaShow: function () {
2285 this.iframe
.setHeight(this.getInnerHeight());
2286 this.textArea
.setHeight(this.getInnerHeight());
2289 * Adjust the height to the changing size of the statusbar when the iframe is shown
2291 onIframeShow: function () {
2292 if (this.getInnerHeight() <= 0) {
2293 this.onWindowResize();
2295 this.iframe
.setHeight(this.getInnerHeight());
2296 this.textArea
.setHeight(this.getInnerHeight());
2300 * Calculate the height available for the editing iframe
2302 getInnerHeight: function () {
2303 return this.getSize().height
- this.toolbar
.getHeight() - this.statusBar
.getHeight() - 5;
2306 * Fire the editor when all components of the framework are rendered and ready
2308 onIframeReady: function () {
2309 this.ready
= this.toolbar
.rendered
&& this.statusBar
.rendered
&& this.textAreaContainer
.rendered
;
2311 this.textAreaContainer
.show();
2312 if (!this.getEditor().config
.showStatusBar
) {
2313 this.statusBar
.hide();
2315 // Set the initial size of the framework
2316 this.onWindowResize();
2317 this.fireEvent('frameworkready');
2319 this.onIframeReady
.defer(50, this);
2323 * Handler invoked if we are inside a form and the form is reset
2324 * On reset, re-initialize the HTMLArea content and update the toolbar
2326 onReset: function (event
) {
2327 this.getEditor().setHTML(this.textArea
.getValue());
2328 this.toolbar
.update();
2329 // Invoke previous reset handlers, if any
2330 var htmlAreaPreviousOnReset
= event
.getTarget().dom
.htmlAreaPreviousOnReset
;
2331 if (typeof(htmlAreaPreviousOnReset
) != 'undefined') {
2332 Ext
.each(htmlAreaPreviousOnReset
, function (onReset
) {
2339 * Cleanup on framework destruction
2341 onBeforeDestroy: function () {
2342 Ext
.EventManager
.removeResizeListener(this.onWindowResize
, this);
2343 // Cleaning references to DOM in order to avoid IE memory leaks
2344 var form
= this.textArea
.dom
.form
;
2346 form
.htmlAreaPreviousOnReset
= null;
2347 Ext
.get(form
).dom
= null;
2349 Ext
.getBody().dom
= null;
2350 // ExtJS is not releasing any resources when the iframe is unloaded
2351 this.toolbar
.destroy();
2352 this.statusBar
.destroy();
2353 this.removeAll(true);
2354 if (this.resizable
) {
2355 this.resizer
.destroy();
2360 Ext
.reg('htmlareaframework', HTMLArea
.Framework
);
2361 /***************************************************
2362 * HTMLArea.Editor extends Ext.util.Observable
2363 ***************************************************/
2364 HTMLArea
.Editor
= Ext
.extend(Ext
.util
.Observable
, {
2366 * HTMLArea.Editor constructor
2368 constructor: function (config
) {
2369 HTMLArea
.Editor
.superclass
.constructor.call(this, {});
2371 this.config
= config
;
2372 // Establish references to this editor
2373 this.editorId
= this.config
.editorId
;
2374 RTEarea
[this.editorId
].editor
= this;
2375 // Get textarea size and wizard context
2376 this.textArea
= Ext
.get(this.config
.id
);
2377 this.textAreaInitialSize
= {
2378 width
: this.config
.RTEWidthOverride
? this.config
.RTEWidthOverride
: this.textArea
.getStyle('width'),
2379 height
: this.config
.fullScreen
? HTMLArea
.util
.TYPO3
.getWindowSize().height
- 20 : this.textArea
.getStyle('height'),
2382 // TYPO3 Inline elements and tabs
2383 this.nestedParentElements
= {
2384 all
: this.config
.tceformsNested
,
2385 sorted
: HTMLArea
.util
.TYPO3
.simplifyNested(this.config
.tceformsNested
)
2387 this.isNested
= !Ext
.isEmpty(this.nestedParentElements
.sorted
);
2388 // If in BE, get width of wizards
2389 if (Ext
.get('typo3-docheader')) {
2390 this.wizards
= this.textArea
.parent().parent().next();
2392 if (!this.isNested
|| HTMLArea
.util
.TYPO3
.allElementsAreDisplayed(this.nestedParentElements
.sorted
)) {
2393 this.textAreaInitialSize
.wizardsWidth
= this.wizards
.getWidth();
2395 // Clone the array of nested tabs and inline levels instead of using a reference as HTMLArea.util.TYPO3.accessParentElements will modify the array
2396 var parentElements
= [].concat(this.nestedParentElements
.sorted
);
2397 // Walk through all nested tabs and inline levels to get correct size
2398 this.textAreaInitialSize
.wizardsWidth
= HTMLArea
.util
.TYPO3
.accessParentElements(parentElements
, 'args[0].getWidth()', [this.wizards
]);
2400 // Hide the wizards so that they do not move around while the editor framework is being sized
2401 this.wizards
.hide();
2406 // Register the plugins included in the configuration
2407 Ext
.iterate(this.config
.plugin
, function (plugin
) {
2408 if (this.config
.plugin
[plugin
]) {
2409 this.registerPlugin(plugin
);
2412 // Initialize keyboard input inhibit flag
2413 this.inhibitKeyboardInput
= false;
2416 * @event editorready
2417 * Fires when initialization of the editor is complete
2422 * Fires when the editor changes mode
2426 * @event beforedestroy
2427 * Fires before the editor is to be destroyed
2433 * Flag set to true when the editor initialization has completed
2437 * The current mode of the editor: 'wysiwyg' or 'textmode'
2441 * Create the htmlArea framework
2443 generate: function () {
2444 // Create the editor framework
2445 this.htmlArea
= new HTMLArea
.Framework({
2446 id
: this.editorId
+ '-htmlArea',
2448 baseCls
: 'htmlarea',
2449 editorId
: this.editorId
,
2450 textArea
: this.textArea
,
2451 textAreaInitialSize
: this.textAreaInitialSize
,
2452 fullScreen
: this.config
.fullScreen
,
2453 resizable
: this.config
.resizable
,
2454 maxHeight
: this.config
.maxHeight
,
2455 isNested
: this.isNested
,
2456 nestedParentElements
: this.nestedParentElements
,
2459 xtype
: 'htmlareatoolbar',
2460 id
: this.editorId
+ '-toolbar',
2464 editorId
: this.editorId
2468 xtype
: 'htmlareaiframe',
2471 width
: (this.textAreaInitialSize
.width
.indexOf('%') === -1) ? parseInt(this.textAreaInitialSize
.width
) : 300,
2472 height
: parseInt(this.textAreaInitialSize
.height
),
2474 id
: this.editorId
+ '-iframe',
2476 cls
: 'editorIframe',
2477 src
: Ext
.isGecko
? 'javascript:void(0);' : HTMLArea
.editorUrl
+ 'popups/blank.html'
2479 isNested
: this.isNested
,
2480 nestedParentElements
: this.nestedParentElements
,
2481 editorId
: this.editorId
2483 // Box container for the textarea
2485 itemId
: 'textAreaContainer',
2487 width
: (this.textAreaInitialSize
.width
.indexOf('%') === -1) ? parseInt(this.textAreaInitialSize
.width
) : 300,
2488 // Let the framework swallow the textarea and throw it back
2491 fn: function (textAreaContainer
) {
2492 this.originalParent
= this.textArea
.parent().dom
;
2493 textAreaContainer
.getEl().appendChild(this.textArea
);
2499 fn: function (textAreaContainer
) {
2500 this.originalParent
.appendChild(this.textArea
.dom
);
2511 xtype
: 'htmlareastatusbar',
2514 editorId
: this.editorId
2517 // Set some references
2518 this.toolbar
= this.htmlArea
.getTopToolbar();
2519 this.statusBar
= this.htmlArea
.getBottomToolbar();
2520 this.iframe
= this.htmlArea
.getComponent('iframe');
2521 this.textAreaContainer
= this.htmlArea
.getComponent('textAreaContainer');
2522 // Get triggered when the framework becomes ready
2523 this.relayEvents(this.htmlArea
, ['frameworkready']);
2524 this.on('frameworkready', this.onFrameworkReady
, this, {single
: true});
2527 * Initialize the editor
2529 onFrameworkReady: function () {
2530 // Initialize editor mode
2531 this.setMode('wysiwyg');
2532 // Initiate events listening
2533 this.initEventsListening();
2535 this.generatePlugins();
2536 // Make the editor visible
2538 // Make the wizards visible again
2540 this.wizards
.show();
2542 // Focus on the first editor that is not hidden
2543 Ext
.iterate(RTEarea
, function (editorId
, RTE
) {
2544 if (!Ext
.isDefined(RTE
.editor
) || (RTE
.editor
.isNested
&& !HTMLArea
.util
.TYPO3
.allElementsAreDisplayed(RTE
.editor
.nestedParentElements
.sorted
))) {
2552 this.fireEvent('editorready');
2553 HTMLArea
._appendToLog('[HTMLArea.Editor::start]: Editor ready.');
2558 * @param string mode: 'textmode' or 'wysiwyg'
2562 setMode: function (mode
) {
2565 this.textArea
.set({ value
: this.getHTML() }, false);
2566 this.iframe
.setDesignMode(false);
2568 this.textAreaContainer
.show();
2573 this.document
.body
.innerHTML
= this.getHTML();
2575 HTMLArea
._appendToLog('[HTMLArea.Editor::setMode]: The HTML document is not well-formed.');
2576 alert(HTMLArea
.I18N
.msg
['HTML-document-not-well-formed']);
2579 this.textAreaContainer
.hide();
2581 this.iframe
.setDesignMode(true);
2585 this.fireEvent('modeChange', this.mode
);
2587 Ext
.iterate(this.plugins
, function(pluginId
) {
2588 this.getPlugin(pluginId
).onMode(this.mode
);
2592 * Get current editor mode
2594 getMode: function () {
2599 * In the case of the wysiwyg mode, the html content is parsed
2601 * @return string the textual html content from the current editing mode
2603 getHTML: function () {
2604 switch (this.mode
) {
2606 return this.iframe
.getHTML();
2608 return this.textArea
.getValue();
2616 * @return string the textual html content from the current editing mode
2618 getInnerHTML: function () {
2619 switch (this.mode
) {
2621 return this.document
.body
.innerHTML
;
2623 return this.textArea
.getValue();
2629 * Replace the html content
2631 * @param string html: the textual html
2635 setHTML: function (html
) {
2636 switch (this.mode
) {
2638 this.document
.body
.innerHTML
= html
;
2641 this.textArea
.set({ value
: html
}, false);;
2646 * Instantiate the specified plugin and register it with the editor
2648 * @param string plugin: the name of the plugin
2650 * @return boolean true if the plugin was successfully registered
2652 registerPlugin: function (pluginName
) {
2654 if (Ext
.isString(pluginName
)) {
2655 /*******************************************************************************
2656 * USE OF PLUGIN NAME OUTSIDE HTMLArea NAMESPACE IS DEPRECATED AS OF TYPO3 4.4 *
2657 *******************************************************************************/
2659 plugin
= eval(pluginName
);
2662 plugin
= eval('HTMLArea.' + pluginName
);
2664 HTMLArea
._appendToLog('ERROR [HTMLArea.Editor::registerPlugin]: Cannot register invalid plugin: ' + error
);
2669 if (!Ext
.isFunction(plugin
)) {
2670 HTMLArea
._appendToLog('ERROR [HTMLArea.Editor::registerPlugin]: Cannot register undefined plugin.');
2673 var pluginInstance
= new plugin(this, pluginName
);
2674 if (pluginInstance
) {
2675 var pluginInformation
= pluginInstance
.getPluginInformation();
2676 pluginInformation
.instance
= pluginInstance
;
2677 this.plugins
[pluginName
] = pluginInformation
;
2678 HTMLArea
._appendToLog('[HTMLArea.Editor::registerPlugin]: Plugin ' + pluginName
+ ' was successfully registered.');
2681 HTMLArea
._appendToLog("ERROR [HTMLArea.Editor::registerPlugin]: Can't register plugin " + pluginName
+ '.');
2686 * Generate registered plugins
2688 generatePlugins: function () {
2689 this.hasPluginWithOnKeyPressHandler
= false;
2690 Ext
.iterate(this.plugins
, function (pluginId
) {
2691 var plugin
= this.getPlugin(pluginId
);
2692 plugin
.onGenerate();
2693 // onKeyPress deprecated as of TYPO3 4.4
2694 if (Ext
.isFunction(plugin
.onKeyPress
)) {
2695 this.hasPluginWithOnKeyPressHandler
= true;
2696 HTMLArea
._appendToLog('[HTMLArea.Editor::generatePlugins]: Deprecated use of onKeyPress function by plugin ' + pluginId
+ '. Use keyMap instead.');
2699 HTMLArea
._appendToLog('[HTMLArea.Editor::generatePlugins]: All plugins successfully generated.');
2702 * Get the instance of the specified plugin, if it exists
2704 * @param string pluginName: the name of the plugin
2705 * @return object the plugin instance or null
2707 getPlugin: function(pluginName
) {
2708 return (this.plugins
[pluginName
] ? this.plugins
[pluginName
].instance
: null);
2711 * Unregister the instance of the specified plugin
2713 * @param string pluginName: the name of the plugin
2716 unRegisterPlugin: function(pluginName
) {
2717 delete this.plugins
[pluginName
].instance
;
2718 delete this.plugins
[pluginName
];
2721 * Focus on the editor
2723 focus: function () {
2724 switch (this.getMode()) {
2726 this.iframe
.focus();
2729 this.textArea
.focus();
2736 initEventsListening: function () {
2738 this.iframe
.startListening();
2740 // Add unload handler
2741 var iframe
= this.iframe
.getEl().dom
;
2742 Ext
.EventManager
.on(iframe
.contentWindow
? iframe
.contentWindow
: iframe
.contentDocument
, 'unload', this.onUnload
, this, {single
: true});
2745 * Make the editor framework visible
2748 document
.getElementById('pleasewait' + this.editorId
).style
.display
= 'none';
2749 document
.getElementById('editorWrap' + this.editorId
).style
.visibility
= 'visible';
2752 * Iframe unload handler: Update the textarea for submission and cleanup
2754 onUnload: function (event
) {
2755 // Save the HTML content into the original textarea for submit, back/forward, etc.
2758 value
: this.getHTML()
2762 this.fireEvent('beforedestroy');
2763 Ext
.TaskMgr
.stopAll();
2764 // ExtJS is not releasing any resources when the iframe is unloaded
2765 this.htmlArea
.destroy();
2766 Ext
.iterate(this.plugins
, function (pluginId
) {
2767 this.unRegisterPlugin(pluginId
);
2769 this.purgeListeners();
2770 // Cleaning references to DOM in order to avoid IE memory leaks
2772 this.wizards
.dom
= null;
2773 this.textArea
.parent().parent().dom
= null;
2774 this.textArea
.parent().dom
= null;
2776 this.textArea
.dom
= null;
2777 RTEarea
[this.editorId
].editor
= null;
2780 /***************************************************
2781 * HTMLArea.util.TYPO3: Utility functions for dealing with tabs and inline elements in TYPO3 forms
2782 ***************************************************/
2783 HTMLArea
.util
.TYPO3 = function () {
2786 * Simplify the array of nested levels. Create an indexed array with the correct names of the elements.
2788 * @param object nested: The array with the nested levels
2789 * @return object The simplified array
2790 * @author Oliver Hader <oh@inpublica.de>
2792 simplifyNested: function(nested
) {
2793 var i
, type
, level
, max
, simplifiedNested
=[];
2794 if (nested
&& nested
.length
) {
2795 if (nested
[0][0]=='inline') {
2796 nested
= inline
.findContinuedNestedLevel(nested
, nested
[0][1]);
2798 for (i
=0, max
=nested
.length
; i
<max
; i
++) {
2799 type
= nested
[i
][0];
2800 level
= nested
[i
][1];
2802 simplifiedNested
.push(level
+'-DIV');
2803 } else if (type
=='inline') {
2804 simplifiedNested
.push(level
+'_fields');
2808 return simplifiedNested
;
2811 * Access an inline relational element or tab menu and make it "accessible".
2812 * If a parent or ancestor object has the style "display: none", offsetWidth & offsetHeight are '0'.
2814 * @params arry parentElements: array of parent elements id's; note that this input array will be modified
2815 * @params object callbackFunc: A function to be called, when the embedded objects are "accessible".
2816 * @params array args: array of arguments
2817 * @return object An object returned by the callbackFunc.
2818 * @author Oliver Hader <oh@inpublica.de>
2820 accessParentElements: function (parentElements
, callbackFunc
, args
) {
2822 if (parentElements
.length
) {
2823 var currentElement
= parentElements
.pop();
2824 currentElement
= Ext
.get(currentElement
);
2825 var actionRequired
= (currentElement
.getStyle('display') == 'none');
2826 if (actionRequired
) {
2827 var originalStyles
= currentElement
.getStyles('visibility', 'position', 'top', 'display');
2828 currentElement
.setStyle({
2829 visibility
: 'hidden',
2830 position
: 'absolute',
2835 result
= this.accessParentElements(parentElements
, callbackFunc
, args
);
2836 if (actionRequired
) {
2837 currentElement
.setStyle(originalStyles
);
2840 result
= eval(callbackFunc
);
2845 * Check if all elements in input array are currently displayed
2847 * @param array elements: array of element id's
2848 * @return boolean true if all elements are displayed
2850 allElementsAreDisplayed: function(elements
) {
2851 var allDisplayed
= true;
2852 Ext
.each(elements
, function (element
) {
2853 allDisplayed
= Ext
.get(element
).getStyle('display') != 'none';
2854 return allDisplayed
;
2856 return allDisplayed
;
2859 * Get current size of window
2861 * @return object width and height of window
2863 getWindowSize: function () {
2865 var size
= Ext
.getBody().getSize();
2868 width
: window
.innerWidth
,
2869 height
: window
.innerHeight
2872 // Subtract the docheader height from the calculated window height
2873 var docHeader
= Ext
.get('typo3-docheader');
2875 size
.height
-= docHeader
.getHeight();
2876 docHeader
.dom
= null;
2883 * Load a stylesheet file
2884 ***********************************************
2885 * THIS FUNCTION IS DEPRECATED AS OF TYPO3 4.4 *
2886 ***********************************************
2888 HTMLArea
.loadStyle = function(style
, plugin
, url
) {
2889 if (typeof(url
) == "undefined") {
2890 var url
= HTMLArea
.editorUrl
|| '';
2891 if (typeof(plugin
) != "undefined") { url
+= "plugins/" + plugin
+ "/"; }
2893 if (/^\//.test(style
)) { url
= style
; }
2895 var head
= document
.getElementsByTagName("head")[0];
2896 var link
= document
.createElement("link");
2897 link
.rel
= "stylesheet";
2899 head
.appendChild(link
);
2903 * Get the url of some popup
2904 ***********************************************
2905 * THIS FUNCTION IS DEPRECATED AS OF TYPO3 4.4 *
2906 ***********************************************
2908 HTMLArea
.Editor
.prototype.popupURL = function(file
) {
2910 if(file
.match(/^plugin:\/\/(.*?)\/(.*)/)) {
2911 var pluginId
= RegExp
.$1;
2912 var popup
= RegExp
.$2;
2913 if(!/\.html$/.test(popup
)) popup
+= ".html";
2914 if (this.config
.pathToPluginDirectory
[pluginId
]) {
2915 url
= this.config
.pathToPluginDirectory
[pluginId
] + "popups/" + popup
;
2917 url
= HTMLArea
.editorUrl
+ "plugins/" + pluginId
+ "/popups/" + popup
;
2920 url
= HTMLArea
.editorUrl
+ this.config
.popupURL
+ file
;
2925 /***************************************************
2927 ***************************************************/
2928 HTMLArea
.getInnerText = function(el
) {
2931 for(i
=el
.firstChild
;i
;i
=i
.nextSibling
) {
2932 if(i
.nodeType
== 3) txt
+= i
.data
;
2933 else if(i
.nodeType
== 1) txt
+= HTMLArea
.getInnerText(i
);
2936 if(el
.nodeType
== 3) txt
= el
.data
;
2941 HTMLArea
.Editor
.prototype.forceRedraw = function() {
2942 this.htmlArea
.doLayout();
2946 * Focus the editor iframe window or the textarea.
2947 ***********************************************
2948 * THIS FUNCTION IS DEPRECATED AS OF TYPO3 4.4 *
2949 ***********************************************
2951 HTMLArea
.Editor
.prototype.focusEditor = function() {
2953 return this.document
;
2957 * Check if any plugin has an opened window
2958 ***********************************************
2959 * THIS FUNCTION IS DEPRECATED AS OF TYPO3 4.4 *
2960 ***********************************************
2962 HTMLArea
.Editor
.prototype.hasOpenedWindow = function () {
2963 for (var plugin
in this.plugins
) {
2964 if (this.plugins
.hasOwnProperty(plugin
)) {
2965 if (HTMLArea
.Dialog
[plugin
.name
] && HTMLArea
.Dialog
[plugin
.name
].hasOpenedWindow
&& HTMLArea
.Dialog
[plugin
.name
].hasOpenedWindow()) {
2972 HTMLArea
.Editor
.prototype.updateToolbar = function(noStatus
) {
2973 this.toolbar
.update(noStatus
);
2975 /***************************************************
2976 * DOM TREE MANIPULATION
2977 ***************************************************/
2980 * Surround the currently selected HTML source code with the given tags.
2981 * Delete the selection, if any.
2983 HTMLArea
.Editor
.prototype.surroundHTML = function(startTag
,endTag
) {
2984 this.insertHTML(startTag
+ this.getSelectedHTML().replace(HTMLArea
.Reg_body
, "") + endTag
);
2988 * Change the tag name of a node.
2990 HTMLArea
.Editor
.prototype.convertNode = function(el
,newTagName
) {
2991 var newel
= this.document
.createElement(newTagName
), p
= el
.parentNode
;
2992 while (el
.firstChild
) newel
.appendChild(el
.firstChild
);
2993 p
.insertBefore(newel
, el
);
2999 * Find a parent of an element with a specified tag
3001 HTMLArea
.getElementObject = function(el
,tagName
) {
3003 while (oEl
!= null && oEl
.nodeName
.toLowerCase() != tagName
) oEl
= oEl
.parentNode
;
3008 * This function removes the given markup element
3010 * @param object element: the inline element to be removed, content being preserved
3014 HTMLArea
.Editor
.prototype.removeMarkup = function(element
) {
3015 var bookmark
= this.getBookmark(this._createRange(this._getSelection()));
3016 var parent
= element
.parentNode
;
3017 while (element
.firstChild
) {
3018 parent
.insertBefore(element
.firstChild
, element
);
3020 parent
.removeChild(element
);
3021 this.selectRange(this.moveToBookmark(bookmark
));
3025 * This function verifies if the element has any allowed attributes
3027 * @param object element: the DOM element
3028 * @param array allowedAttributes: array of allowed attribute names
3030 * @return boolean true if the element has one of the allowed attributes
3032 HTMLArea
.hasAllowedAttributes = function(element
,allowedAttributes
) {
3034 for (var i
= allowedAttributes
.length
; --i
>= 0;) {
3035 value
= element
.getAttribute(allowedAttributes
[i
]);
3037 if (allowedAttributes
[i
] == "style" && element
.style
.cssText
) {
3047 /***************************************************
3048 * SELECTIONS AND RANGES
3049 ***************************************************/
3052 * Return true if we have some selected content
3054 HTMLArea
.Editor
.prototype.hasSelectedText = function() {
3055 return this.getSelectedHTML() != "";
3059 * Get an array with all the ancestor nodes of the selection.
3061 HTMLArea
.Editor
.prototype.getAllAncestors = function() {
3062 var p
= this.getParentElement();
3064 while (p
&& (p
.nodeType
=== 1) && (p
.nodeName
.toLowerCase() !== "body")) {
3068 a
.push(this.document
.body
);
3073 * Get the block ancestors of an element within a given block
3075 HTMLArea
.Editor
.prototype.getBlockAncestors = function(element
, withinBlock
) {
3076 var ancestors
= new Array();
3077 var ancestor
= element
;
3078 while (ancestor
&& (ancestor
.nodeType
=== 1) && !/^(body)$/i.test(ancestor
.nodeName
) && ancestor
!= withinBlock
) {
3079 if (HTMLArea
.isBlockElement(ancestor
)) {
3080 ancestors
.unshift(ancestor
);
3082 ancestor
= ancestor
.parentNode
;
3084 ancestors
.unshift(ancestor
);
3089 * Get the block elements containing the start and the end points of the selection
3091 HTMLArea
.Editor
.prototype.getEndBlocks = function(selection
) {
3092 var range
= this._createRange(selection
);
3094 var parentStart
= range
.startContainer
;
3095 if (/^(body)$/i.test(parentStart
.nodeName
)) {
3096 parentStart
= parentStart
.firstChild
;
3098 var parentEnd
= range
.endContainer
;
3099 if (/^(body)$/i.test(parentEnd
.nodeName
)) {
3100 parentEnd
= parentEnd
.lastChild
;
3103 if (selection
.type
!== "Control" ) {
3104 var rangeEnd
= range
.duplicate();
3105 range
.collapse(true);
3106 var parentStart
= range
.parentElement();
3107 rangeEnd
.collapse(false);
3108 var parentEnd
= rangeEnd
.parentElement();
3110 var parentStart
= range
.item(0);
3111 var parentEnd
= parentStart
;
3114 while (parentStart
&& !HTMLArea
.isBlockElement(parentStart
)) {
3115 parentStart
= parentStart
.parentNode
;
3117 while (parentEnd
&& !HTMLArea
.isBlockElement(parentEnd
)) {
3118 parentEnd
= parentEnd
.parentNode
;
3120 return { start
: parentStart
,
3126 * This function determines if the end poins of the current selection are within the same block
3128 * @return boolean true if the end points of the current selection are inside the same block element
3130 HTMLArea
.Editor
.prototype.endPointsInSameBlock = function() {
3131 var selection
= this._getSelection();
3132 if (this._selectionEmpty(selection
)) {
3135 var parent
= this.getParentElement(selection
);
3136 var endBlocks
= this.getEndBlocks(selection
);
3137 return (endBlocks
.start
=== endBlocks
.end
&& !/^(table|thead|tbody|tfoot|tr)$/i.test(parent
.nodeName
));
3142 * Get the deepest ancestor of the selection that is of the specified type
3143 * Borrowed from Xinha (is not htmlArea) - http://xinha.gogo.co.nz/
3145 HTMLArea
.Editor
.prototype._getFirstAncestor = function(sel
,types
) {
3146 var prnt
= this._activeElement(sel
);
3149 prnt
= (Ext
.isIE
? this._createRange(sel
).parentElement() : this._createRange(sel
).commonAncestorContainer
);
3154 if (typeof(types
) == 'string') types
= [types
];
3157 if (prnt
.nodeType
== 1) {
3158 if (types
== null) return prnt
;
3159 for (var i
= 0; i
< types
.length
; i
++) {
3160 if(prnt
.tagName
.toLowerCase() == types
[i
]) return prnt
;
3162 if(prnt
.tagName
.toLowerCase() == 'body') break;