e277bf6a74a21c6e6dc63a656aa91414b0c3851b
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / htmlarea / plugins / EditElement / edit-element.js
1 /**
2 * This file is part of the TYPO3 CMS project.
3 *
4 * It is free software; you can redistribute it and/or modify it under
5 * the terms of the GNU General Public License, either version 2
6 * of the License, or any later version.
7 *
8 * For the full copyright and license information, please read the
9 * LICENSE.txt file that was distributed with this source code.
10 *
11 * The TYPO3 project - inspiring people to share!
12 */
13 /*
14 * EditElement plugin for htmlArea RTE
15 */
16 HTMLArea.EditElement = Ext.extend(HTMLArea.Plugin, {
17 /*
18 * This function gets called by the class constructor
19 */
20 configurePlugin: function (editor) {
21 this.pageTSConfiguration = this.editorConfiguration.buttons.editelement;
22 this.removedFieldsets = (this.pageTSConfiguration && this.pageTSConfiguration.removeFieldsets) ? this.pageTSConfiguration.removeFieldsets : '';
23 this.properties = (this.pageTSConfiguration && this.pageTSConfiguration.properties) ? this.pageTSConfiguration.properties : '';
24 this.removedProperties = (this.properties && this.properties.removed) ? this.properties.removed : '';
25 /*
26 * Registering plugin "About" information
27 */
28 var pluginInformation = {
29 version : '2.0',
30 developer : 'Stanislas Rolland',
31 developerUrl : 'http://www.sjbr.ca/',
32 copyrightOwner : 'Stanislas Rolland',
33 sponsor : 'SJBR',
34 sponsorUrl : 'http://www.sjbr.ca/',
35 license : 'GPL'
36 };
37 this.registerPluginInformation(pluginInformation);
38 /*
39 * Registering the button
40 */
41 var buttonId = 'EditElement';
42 var buttonConfiguration = {
43 id : buttonId,
44 tooltip : this.localize('editElement'),
45 action : 'onButtonPress',
46 dialog : true,
47 iconCls : 'htmlarea-action-element-edit'
48 };
49 this.registerButton(buttonConfiguration);
50 return true;
51 },
52 /*
53 * Sets of default configuration values for dialogue form fields
54 */
55 configDefaults: {
56 combo: {
57 editable: true,
58 selectOnFocus: true,
59 typeAhead: true,
60 triggerAction: 'all',
61 forceSelection: true,
62 mode: 'local',
63 valueField: 'value',
64 displayField: 'text',
65 helpIcon: true,
66 tpl: '<tpl for="."><div ext:qtip="{value}" style="text-align:left;font-size:11px;" class="x-combo-list-item">{text}</div></tpl>'
67 }
68 },
69 /*
70 * This function gets called when the button was pressed
71 *
72 * @param object editor: the editor instance
73 * @param string id: the button id or the key
74 *
75 * @return boolean false if action is completed
76 */
77 onButtonPress: function(editor, id) {
78 // Could be a button or its hotkey
79 var buttonId = this.translateHotKey(id);
80 buttonId = buttonId ? buttonId : id;
81 // Get the parent element of the current selection
82 this.element = this.editor.getSelection().getParentElement();
83 if (this.element && !/^body$/i.test(this.element.nodeName)) {
84 // Open the dialogue window
85 this.openDialogue(
86 buttonId,
87 'editElement',
88 this.getWindowDimensions(
89 {
90 width: 450
91 },
92 buttonId
93 ),
94 this.buildTabItemsConfig(this.element),
95 this.buildButtonsConfig(this.element, this.okHandler, this.deleteHandler)
96 );
97 }
98 return false;
99 },
100 /*
101 * Open the dialogue window
102 *
103 * @param string buttonId: the button id
104 * @param string title: the window title
105 * @param object dimensions: the opening dimensions of the window
106 * @param object tabItems: the configuration of the tabbed panel
107 * @param object buttonsConfig: the configuration of the buttons
108 *
109 * @return void
110 */
111 openDialogue: function (buttonId, title, dimensions, tabItems, buttonsConfig) {
112 this.dialog = new Ext.Window({
113 title: this.getHelpTip('', title),
114 cls: 'htmlarea-window',
115 border: false,
116 width: dimensions.width,
117 height: 'auto',
118 iconCls: this.getButton(buttonId).iconCls,
119 listeners: {
120 close: {
121 fn: this.onClose,
122 scope: this
123 }
124 },
125 items: {
126 xtype: 'tabpanel',
127 activeTab: 0,
128 defaults: {
129 xtype: 'container',
130 layout: 'form',
131 defaults: {
132 labelWidth: 150
133 }
134 },
135 listeners: {
136 tabchange: {
137 fn: this.syncHeight,
138 scope: this
139 }
140 },
141 items: tabItems
142 },
143 buttons: buttonsConfig
144 });
145 this.show();
146 },
147 /*
148 * Build the dialogue tab items config
149 *
150 * @param object element: the element being edited, if any
151 *
152 * @return object the tab items configuration
153 */
154 buildTabItemsConfig: function (element) {
155 var tabItems = [];
156 var generalTabItemConfig = [];
157 if (this.removedFieldsets.indexOf('identification') == -1) {
158 this.addConfigElement(this.buildIdentificationFieldsetConfig(element), generalTabItemConfig);
159 }
160 if (this.removedFieldsets.indexOf('style') == -1 && this.removedProperties.indexOf('className') == -1) {
161 this.addConfigElement(this.buildClassFieldsetConfig(element), generalTabItemConfig);
162 }
163 tabItems.push({
164 title: this.localize('general'),
165 itemId: 'general',
166 items: generalTabItemConfig
167 });
168 if (this.removedFieldsets.indexOf('language') == -1 && this.getPluginInstance('Language')) {
169 var languageTabItemConfig = [];
170 this.addConfigElement(this.buildLanguageFieldsetConfig(element), languageTabItemConfig);
171 tabItems.push({
172 title: this.localize('Language'),
173 itemId: 'language',
174 items: languageTabItemConfig
175 });
176 }
177 if (this.removedFieldsets.indexOf('microdata') == -1 && this.getPluginInstance('MicrodataSchema')) {
178 var microdataTabItemConfig = [];
179 this.addConfigElement(this.getPluginInstance('MicrodataSchema').buildMicrodataFieldsetConfig(element, this.properties), microdataTabItemConfig);
180 tabItems.push({
181 title: this.getPluginInstance('MicrodataSchema').localize('microdata'),
182 itemId: 'microdata',
183 items: microdataTabItemConfig
184 });
185 }
186 if (this.removedFieldsets.indexOf('events') == -1) {
187 var eventsTabItemConfig = [];
188 this.addConfigElement(this.buildEventsFieldsetConfig(element), eventsTabItemConfig);
189 tabItems.push({
190 title: this.localize('events'),
191 itemId: 'events',
192 items: eventsTabItemConfig
193 });
194 }
195 return tabItems;
196 },
197 /*
198 * This function builds the configuration object for the Identification fieldset
199 *
200 * @param object element: the element being edited, if any
201 *
202 * @return object the fieldset configuration object
203 */
204 buildIdentificationFieldsetConfig: function (element) {
205 var itemsConfig = [];
206 if (this.removedProperties.indexOf('id') == -1) {
207 itemsConfig.push({
208 itemId: 'id',
209 fieldLabel: this.getHelpTip('id', 'id'),
210 value: element ? element.getAttribute('id') : '',
211 width: ((this.properties['id'] && this.properties['id'].width) ? this.properties['id'].width : 300)
212 });
213 }
214 if (this.removedProperties.indexOf('title') == -1) {
215 itemsConfig.push({
216 itemId: 'title',
217 fieldLabel: this.getHelpTip('title', 'title'),
218 value: element ? element.getAttribute('title') : '',
219 width: ((this.properties['title'] && this.properties['title'].width) ? this.properties['title'].width : 300)
220 });
221 }
222 return {
223 xtype: 'fieldset',
224 title: this.localize('identification'),
225 defaultType: 'textfield',
226 labelWidth: 100,
227 defaults: {
228 labelSeparator: ':'
229 },
230 items: itemsConfig
231 };
232 },
233 /*
234 * This function builds the configuration object for the CSS Class fieldset
235 *
236 * @param object element: the element being edited, if any
237 *
238 * @return object the fieldset configuration object
239 */
240 buildClassFieldsetConfig: function (element) {
241 var itemsConfig = [];
242 var stylingCombo = this.buildStylingField('className', 'className', 'className');
243 this.setStyleOptions(stylingCombo, element);
244 itemsConfig.push(stylingCombo);
245 return {
246 xtype: 'fieldset',
247 title: this.localize('className'),
248 labelWidth: 100,
249 defaults: {
250 labelSeparator: ':'
251 },
252 items: itemsConfig
253 };
254 },
255 /*
256 * This function builds a style selection field
257 *
258 * @param string fieldName: the name of the field
259 * @param string fieldLabel: the label for the field
260 * @param string cshKey: the csh key
261 *
262 * @return object the style selection field object
263 */
264 buildStylingField: function (fieldName, fieldLabel, cshKey) {
265 return new Ext.form.ComboBox(Ext.apply({
266 xtype: 'combo',
267 itemId: fieldName,
268 fieldLabel: this.getHelpTip(fieldLabel, cshKey),
269 width: ((this.properties['className'] && this.properties['className'].width) ? this.properties['className'].width : 300),
270 store: new Ext.data.ArrayStore({
271 autoDestroy: true,
272 fields: [ { name: 'text'}, { name: 'value'}, { name: 'style'} ],
273 data: [[this.localize('No style'), 'none']]
274 })
275 }, {
276 tpl: '<tpl for="."><div ext:qtip="{value}" style="{style}text-align:left;font-size:11px;" class="x-combo-list-item">{text}</div></tpl>'
277 }, this.configDefaults['combo']
278 ));
279 },
280 /*
281 * This function populates the class store and sets the selected option
282 *
283 * @param object: comboBox: the combobox object
284 * @param object element: the element being edited, if any
285 *
286 * @return object the fieldset configuration object
287 */
288 setStyleOptions: function (comboBox, element) {
289 var nodeName = element.nodeName.toLowerCase();
290 this.stylePlugin = this.getPluginInstance(HTMLArea.DOM.isBlockElement(element) ? 'BlockStyle' : 'TextStyle');
291 if (comboBox && this.stylePlugin) {
292 var classNames = HTMLArea.DOM.getClassNames(element);
293 this.stylePlugin.buildDropDownOptions(comboBox, nodeName);
294 this.stylePlugin.setSelectedOption(comboBox, classNames, 'noUnknown');
295 }
296 },
297 /*
298 * This function builds the configuration object for the Language fieldset
299 *
300 * @param object element: the element being edited, if any
301 *
302 * @return object the fieldset configuration object
303 */
304 buildLanguageFieldsetConfig: function (element) {
305 var itemsConfig = [];
306 var languagePlugin = this.getPluginInstance('Language');
307 var languageConfigurationUrl;
308 if (this.editorConfiguration.buttons && this.editorConfiguration.buttons.language && this.editorConfiguration.buttons.language.dataUrl) {
309 languageConfigurationUrl = this.editorConfiguration.buttons.language.dataUrl;
310 }
311 if (languagePlugin && languageConfigurationUrl && this.removedProperties.indexOf('language') == -1) {
312 var selectedLanguage = !Ext.isEmpty(element) ? languagePlugin.getLanguageAttribute(element) : 'none';
313 function initLanguageStore (store) {
314 if (selectedLanguage !== 'none') {
315 store.removeAt(0);
316 store.insert(0, new store.recordType({
317 text: languagePlugin.localize('Remove language mark'),
318 value: 'none'
319 }));
320 }
321 }
322 var languageStore = new Ext.data.JsonStore({
323 autoDestroy: true,
324 autoLoad: true,
325 root: 'options',
326 fields: [ { name: 'text'}, { name: 'value'} ],
327 url: languageConfigurationUrl,
328 listeners: {
329 load: initLanguageStore
330 }
331 });
332 itemsConfig.push(Ext.apply({
333 xtype: 'combo',
334 fieldLabel: languagePlugin.getHelpTip('languageCombo', 'Language'),
335 itemId: 'lang',
336 store: languageStore,
337 width: ((this.properties['language'] && this.properties['language'].width) ? this.properties['language'].width : 200),
338 value: selectedLanguage
339 }, this.configDefaults['combo']));
340 }
341 if (this.removedProperties.indexOf('direction') == -1) {
342 itemsConfig.push(Ext.apply({
343 xtype: 'combo',
344 fieldLabel: languagePlugin.getHelpTip('directionCombo', 'Text direction'),
345 itemId: 'dir',
346 store: new Ext.data.ArrayStore({
347 autoDestroy: true,
348 fields: [ { name: 'text'}, { name: 'value'}],
349 data: [
350 [languagePlugin.localize('Not set'), 'not set'],
351 [languagePlugin.localize('RightToLeft'), 'rtl'],
352 [languagePlugin.localize('LeftToRight'), 'ltr']
353 ]
354 }),
355 width: ((this.properties['direction'] && this.properties['dirrection'].width) ? this.properties['direction'].width : 200),
356 value: !Ext.isEmpty(element) && element.dir ? element.dir : 'not set'
357 }, this.configDefaults['combo']));
358 }
359 return {
360 xtype: 'fieldset',
361 title: this.localize('Language'),
362 labelWidth: 100,
363 defaults: {
364 labelSeparator: ':'
365 },
366 items: itemsConfig
367 };
368 },
369 /*
370 * This function builds the configuration object for the Events fieldset
371 *
372 * @param object element: the element being edited, if any
373 *
374 * @return object the fieldset configuration object
375 */
376 buildEventsFieldsetConfig: function (element) {
377 var itemsConfig = [];
378 var events = ['onkeydown', 'onkeypress', 'onkeyup', 'onclick', 'ondblclick', 'onmousedown', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup'];
379 if (!/^(base|bdo|br|frame|frameset|head|html|iframe|meta|param|script|style|title)$/i.test(element.nodeName)) {
380 Ext.each(events, function (event) {
381 if (this.removedProperties.indexOf(event) == -1) {
382 itemsConfig.push({
383 itemId: event,
384 fieldLabel: this.getHelpTip(event, event),
385 value: element ? element.getAttribute(event) : ''
386 });
387 }
388 }, this);
389 }
390 return itemsConfig.length ? {
391 xtype: 'fieldset',
392 title: this.getHelpTip('events', 'events'),
393 defaultType: 'textfield',
394 labelWidth: 100,
395 defaults: {
396 labelSeparator: ':',
397 width: ((this.properties['event'] && this.properties['event'].width) ? this.properties['event'].width : 300)
398 },
399 items: itemsConfig
400 } : null;
401 },
402 /*
403 * Build the dialogue buttons config
404 *
405 * @param object element: the element being edited, if any
406 * @param function okHandler: the handler for the ok button
407 * @param function deleteHandler: the handler for the delete button
408 *
409 * @return object the buttons configuration
410 */
411 buildButtonsConfig: function (element, okHandler, deleteHandler) {
412 var buttonsConfig = [this.buildButtonConfig('OK', okHandler)];
413 if (element) {
414 buttonsConfig.push(this.buildButtonConfig('Delete', deleteHandler));
415 }
416 buttonsConfig.push(this.buildButtonConfig('Cancel', this.onCancel));
417 return buttonsConfig;
418 },
419 /*
420 * Handler when the ok button is pressed
421 */
422 okHandler: function (button, event) {
423 this.restoreSelection();
424 var textFields = this.dialog.findByType('textfield');
425 Ext.each(textFields, function (field) {
426 if (field.getXType() !== 'combo') {
427 this.element.setAttribute(field.getItemId(), field.getValue());
428 }
429 }, this);
430 var comboFields = this.dialog.findByType('combo');
431 var languageCombo = this.dialog.find('itemId', 'lang')[0];
432 Ext.each(comboFields, function (field) {
433 var itemId = field.getItemId();
434 var value = field.getValue();
435 switch (itemId) {
436 case 'className':
437 if (HTMLArea.DOM.isBlockElement(this.element)) {
438 this.stylePlugin.applyClassChange(this.element, value);
439 } else {
440 // Do not remove the span element if the language attribute is to be removed
441 this.stylePlugin.applyClassChange(this.element, value, languageCombo && (languageCombo.getValue() === 'none'));
442 }
443 break;
444 case 'dir':
445 this.element.setAttribute(itemId, (value === 'not set') ? '' : value);
446 break;
447 }
448 }, this);
449 var microdataTab = this.dialog.find('itemId', 'microdata')[0];
450 if (microdataTab) {
451 this.getPluginInstance('MicrodataSchema').setMicrodataAttributes(this.element);
452 }
453 if (languageCombo) {
454 this.getPluginInstance('Language').setLanguageAttributes(this.element, languageCombo.getValue());
455 }
456 this.close();
457 event.stopEvent();
458 },
459 /*
460 * Handler when the delete button is pressed
461 */
462 deleteHandler: function (button, event) {
463 this.restoreSelection();
464 if (this.element) {
465 // Delete the element
466 HTMLArea.DOM.removeFromParent(this.element);
467 }
468 this.close();
469 event.stopEvent();
470 },
471 /*
472 * This function gets called when the toolbar is updated
473 */
474 onUpdateToolbar: function (button, mode, selectionEmpty, ancestors) {
475 if ((mode === 'wysiwyg') && this.editor.isEditable()) {
476 // Disable the button if the first ancestor is the document body
477 button.setDisabled(!ancestors.length || /^body$/i.test(ancestors[0].nodeName));
478 if (this.dialog) {
479 this.dialog.focus();
480 }
481 }
482 }
483 });