d92389a2258760328795761be1f2b622698a3b98
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / Resources / Public / JavaScript / Plugins / TableOperations.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 /**
15 * Table Operations Plugin for TYPO3 htmlArea RTE
16 */
17 define([
18 'TYPO3/CMS/Rtehtmlarea/HTMLArea/Plugin/Plugin',
19 'TYPO3/CMS/Rtehtmlarea/HTMLArea/UserAgent/UserAgent',
20 'TYPO3/CMS/Rtehtmlarea/HTMLArea/Util/Util',
21 'TYPO3/CMS/Rtehtmlarea/HTMLArea/DOM/DOM',
22 'TYPO3/CMS/Rtehtmlarea/HTMLArea/Event/Event',
23 'TYPO3/CMS/Rtehtmlarea/HTMLArea/Util/Color',
24 'TYPO3/CMS/Rtehtmlarea/Plugins/BlockStyle',
25 'TYPO3/CMS/Rtehtmlarea/Plugins/BlockElements',
26 'TYPO3/CMS/Rtehtmlarea/Plugins/Language',
27 'TYPO3/CMS/Rtehtmlarea/HTMLArea/Components/Select',
28 'jquery',
29 'TYPO3/CMS/Backend/Modal',
30 'TYPO3/CMS/Backend/Notification',
31 'TYPO3/CMS/Backend/Severity'
32 ], function (Plugin, UserAgent, Util, Dom, Event, Color, BlockStyle, BlockElements, Language, Select, $, Modal, Notification, Severity) {
33
34 var TableOperations = function (editor, pluginName) {
35 this.constructor.super.call(this, editor, pluginName);
36 };
37 Util.inherit(TableOperations, Plugin);
38 Util.apply(TableOperations.prototype, {
39
40 /**
41 * This function gets called by the class constructor
42 */
43 configurePlugin: function (editor) {
44 this.classesUrl = this.editorConfiguration.classesUrl;
45 this.buttonsConfiguration = this.editorConfiguration.buttons;
46 this.disableEnterParagraphs = this.buttonsConfiguration.table ? this.buttonsConfiguration.table.disableEnterParagraphs : false;
47 this.floatLeft = "float-left";
48 this.floatRight = "float-right";
49 this.floatDefault = "not set";
50 this.useHeaderClass = "thead";
51 if (this.buttonsConfiguration.table && this.buttonsConfiguration.table.properties) {
52 if (this.buttonsConfiguration.table.properties["float"]) {
53 var floatConfiguration = this.buttonsConfiguration.table.properties["float"];
54 this.floatLeft = (floatConfiguration.left && floatConfiguration.left.useClass) ? floatConfiguration.left.useClass : "float-left";
55 this.floatRight = (floatConfiguration.right && floatConfiguration.right.useClass) ? floatConfiguration.right.useClass : "float-right";
56 this.floatDefault = (floatConfiguration.defaultValue) ? floatConfiguration.defaultValue : "not set";
57 }
58 if (this.buttonsConfiguration.table.properties.headers && this.buttonsConfiguration.table.properties.headers.both
59 && this.buttonsConfiguration.table.properties.headers.both.useHeaderClass) {
60 this.useHeaderClass = this.buttonsConfiguration.table.properties.headers.both.useHeaderClass;
61 }
62 if (this.buttonsConfiguration.table.properties.tableClass) {
63 this.defaultClass = this.buttonsConfiguration.table.properties.tableClass.defaultValue;
64 }
65 }
66 if (this.buttonsConfiguration.blockstyle) {
67 this.tags = this.editorConfiguration.buttons.blockstyle.tags;
68 }
69 this.tableParts = ["tfoot", "thead", "tbody"];
70 this.convertAlignment = { "not set" : "none", "left" : "JustifyLeft", "center" : "JustifyCenter", "right" : "JustifyRight", "justify" : "JustifyFull" };
71 /**
72 * Registering plugin "About" information
73 */
74 var pluginInformation = {
75 version : '5.3',
76 developer : 'Mihai Bazon & Stanislas Rolland',
77 developerUrl : 'http://www.sjbr.ca/',
78 copyrightOwner : 'Mihai Bazon & Stanislas Rolland',
79 sponsor : this.localize('Technische Universitat Ilmenau') + ' & Zapatec Inc.',
80 sponsorUrl : 'http://www.tu-ilmenau.de/',
81 license : 'GPL'
82 };
83 this.registerPluginInformation(pluginInformation);
84 /**
85 * Registering the buttons
86 */
87 var hideToggleBorders = this.editorConfiguration.hideTableOperationsInToolbar && !(this.buttonsConfiguration.toggleborders && this.buttonsConfiguration.toggleborders.keepInToolbar);
88 var buttonList = this.buttonList, button, buttonId;
89 for (var i = 0, n = buttonList.length; i < n; ++i) {
90 button = buttonList[i];
91 buttonId = (button[0] === 'InsertTable') ? button[0] : ('TO-' + button[0]);
92 var buttonConfiguration = {
93 id : buttonId,
94 tooltip : this.localize((buttonId === 'InsertTable') ? 'Insert Table' : buttonId),
95 iconCls : 'htmlarea-action-' + button[4],
96 action : 'onButtonPress',
97 hotKey : (this.buttonsConfiguration[button[2]] ? this.buttonsConfiguration[button[2]].hotKey : null),
98 context : button[1],
99 hide : ((buttonId == 'TO-toggle-borders') ? hideToggleBorders : ((button[0] === 'InsertTable') ? false : this.editorConfiguration.hideTableOperationsInToolbar)),
100 dialog : button[3]
101 };
102 this.registerButton(buttonConfiguration);
103 }
104 return true;
105 },
106 /*
107 * The list of buttons added by this plugin
108 */
109 buttonList: [
110 ['InsertTable', null, 'table', true, 'table-insert'],
111 ['toggle-borders', null, 'toggleborders', false, 'table-show-borders'],
112 ['table-prop', 'table', 'tableproperties', true, 'table-edit-properties'],
113 ['table-restyle', 'table', 'tablerestyle', false, 'table-restyle'],
114 ['row-prop', 'tr', 'rowproperties', true, 'row-edit-properties'],
115 ['row-insert-above', 'tr', 'rowinsertabove', false, 'row-insert-above'],
116 ['row-insert-under', 'tr', 'rowinsertunder', false, 'row-insert-under'],
117 ['row-delete', 'tr', 'rowdelete', false, 'row-delete'],
118 ['row-split', 'td,th[rowSpan!=1]', 'rowsplit', false, 'row-split'],
119 ['col-prop', 'td,th', 'columnproperties', true, 'column-edit-properties'],
120 ['col-insert-before', 'td,th', 'columninsertbefore', false, 'column-insert-before'],
121 ['col-insert-after', 'td,th', 'columninsertafter', false, 'column-insert-after'],
122 ['col-delete', 'td,th', 'columndelete', false, 'column-delete'],
123 ['col-split', 'td,th[colSpan!=1]', 'columnsplit', false, 'column-split'],
124 ['cell-prop', 'td,th', 'cellproperties', true, 'cell-edit-properties'],
125 ['cell-insert-before', 'td,th', 'cellinsertbefore', false, 'cell-insert-before'],
126 ['cell-insert-after', 'td,th', 'cellinsertafter', false, 'cell-insert-after'],
127 ['cell-delete', 'td,th', 'celldelete', false, 'cell-delete'],
128 ['cell-merge', UserAgent.isGecko ? 'tr' : 'td,th', 'cellmerge', false, 'cell-merge'],
129 ['cell-split', 'td,th[colSpan!=1,rowSpan!=1]', 'cellsplit', false, 'cell-split']
130 ],
131 /*
132 * Sets of default configuration values for dialogue form fields
133 */
134 configDefaults: {
135 combo: {
136 editable: true,
137 selectOnFocus: true,
138 typeAhead: true,
139 triggerAction: 'all',
140 forceSelection: true,
141 mode: 'local',
142 valueField: 'value',
143 displayField: 'text',
144 helpIcon: true,
145 tpl: '<tpl for="."><div ext:qtip="{value}" style="text-align:left;font-size:11px;" class="x-combo-list-item">{text}</div></tpl>'
146 }
147 },
148 /**
149 * Get the integer value of a string or '' if the string is not a number
150 *
151 * @param {String} string The input value
152 *
153 * @return {Number|String} A number or ''
154 */
155 getLength: function (string) {
156 var length = parseInt(string);
157 if (isNaN(length)) {
158 length = '';
159 }
160 return length;
161 },
162 /**
163 * Open properties dialogue
164 *
165 * @param {String} type 'cell', 'column', 'row' or 'table'
166 * @param {String} buttonId The buttonId of the button that was pressed
167 */
168 openPropertiesDialogue: function (type, buttonId) {
169 var element,
170 title;
171 // Retrieve the element being edited and set configuration according to type
172 switch (type) {
173 case 'cell':
174 case 'column':
175 element = this.editor.getSelection().getFirstAncestorOfType(['td', 'th']);
176 this.properties = (this.buttonsConfiguration.cellproperties && this.buttonsConfiguration.cellproperties.properties) ? this.buttonsConfiguration.cellproperties.properties : {};
177 title = (type === 'column') ? 'Column Properties' : 'Cell Properties';
178 break;
179 case 'row':
180 element = this.editor.getSelection().getFirstAncestorOfType('tr');
181 this.properties = (this.buttonsConfiguration.rowproperties && this.buttonsConfiguration.rowproperties.properties) ? this.buttonsConfiguration.rowproperties.properties : {};
182 title = 'Row Properties';
183 break;
184 case 'table':
185 var insert = (buttonId === 'InsertTable');
186 element = insert ? null : this.editor.getSelection().getFirstAncestorOfType('table');
187 this.properties = (this.buttonsConfiguration.table && this.buttonsConfiguration.table.properties) ? this.buttonsConfiguration.table.properties : {};
188 title = insert ? 'Insert Table' : 'Table Properties';
189 break;
190 }
191 var propertySet = element ? type + 'properties' : 'table';
192 this.removedFieldsets = (this.buttonsConfiguration[propertySet] && this.buttonsConfiguration[propertySet].removeFieldsets) ? this.buttonsConfiguration[propertySet].removeFieldsets : '';
193 this.removedProperties = this.properties.removed ? this.properties.removed : '';
194
195 // Open the dialogue window
196 this.openDialogue(
197 title,
198 {
199 element: element,
200 cell: type === 'cell',
201 column: type === 'column',
202 buttonId: buttonId
203 },
204 this.buildTabItemsConfig(element, type, buttonId),
205 type === 'table' ? this.tablePropertiesUpdate : this.rowCellPropertiesUpdate
206 );
207 },
208 /**
209 * Build the dialogue tab items config
210 *
211 * @param {Object} element The element being edited, if any
212 * @param {String} type 'cell', 'column', 'row' or 'table'
213 * @param {String} buttonId The buttonId of the button that was pressed
214 *
215 * @return {Object} The tab items configuration
216 */
217 buildTabItemsConfig: function (element, type, buttonId) {
218 var $tabs = $('<ul />', {'class': 'nav nav-tabs', role: 'tablist'}),
219 $tabContent = $('<div />', {'class': 'tab-content'}),
220 $finalMarkup,
221 generalTabItems = [],
222 layoutTabItems = [],
223 languageTabItems = [],
224 alignmentAndBordersTabItems = [],
225 colorTabItems = [];
226
227 switch (type) {
228 case 'table':
229 if (this.removedFieldsets.indexOf('description') === -1) {
230 this.addConfigElement(this.buildDescriptionFieldsetConfig(element), generalTabItems);
231 }
232 if (typeof element !== 'object' || element === null || this.removedProperties.indexOf('headers') === -1) {
233 this.addConfigElement(this.buildSizeAndHeadersFieldsetConfig(element), generalTabItems);
234 }
235 break;
236 case 'column':
237 if (this.removedFieldsets.indexOf('columntype') === -1) {
238 this.addConfigElement(this.buildCellTypeFieldsetConfig(element, true), generalTabItems);
239 }
240 break;
241 case 'cell':
242 if (this.removedFieldsets.indexOf('celltype') === -1) {
243 this.addConfigElement(this.buildCellTypeFieldsetConfig(element, false), generalTabItems);
244 }
245 break;
246 case 'row':
247 if (this.removedFieldsets.indexOf('rowgroup') === -1) {
248 this.addConfigElement(this.buildRowGroupFieldsetConfig(element), generalTabItems);
249 }
250 break;
251 }
252 if (this.removedFieldsets.indexOf('style') === -1 && this.getPluginInstance('BlockStyle')) {
253 this.addConfigElement(this.buildStylingFieldsetConfig(element, buttonId), generalTabItems);
254 }
255 if (generalTabItems.length > 0) {
256 this.buildTabMarkup($tabs, $tabContent, 'general', generalTabItems, this.localize('General'));
257 }
258 if (type === 'table' && this.removedFieldsets.indexOf('spacing') === -1) {
259 this.addConfigElement(this.buildSpacingFieldsetConfig(element), layoutTabItems);
260 }
261 if (this.removedFieldsets.indexOf('layout') === -1) {
262 this.addConfigElement(this.buildLayoutFieldsetConfig(element), layoutTabItems);
263 }
264 if (layoutTabItems.length > 0) {
265 this.buildTabMarkup($tabs, $tabContent, 'layout', layoutTabItems, this.localize('Layout'));
266 }
267 if (this.removedFieldsets.indexOf('language') === -1 && (this.removedProperties.indexOf('language') === -1 || this.removedProperties.indexOf('direction') === -1) && (this.getButton('Language') || this.getButton('LeftToRight') || this.getButton('RightToLeft'))) {
268 this.addConfigElement(this.buildLanguageFieldsetConfig(element), languageTabItems);
269 }
270 if (languageTabItems.length > 0) {
271 this.buildTabMarkup($tabs, $tabContent, 'language', languageTabItems, this.localize('Language'));
272 }
273 if (this.removedFieldsets.indexOf('alignment') === -1) {
274 this.addConfigElement(this.buildAlignmentFieldsetConfig(element), alignmentAndBordersTabItems);
275 }
276 if (this.removedFieldsets.indexOf('borders') === -1) {
277 this.addConfigElement(this.buildBordersFieldsetConfig(element), alignmentAndBordersTabItems);
278 }
279 if (alignmentAndBordersTabItems.length > 0) {
280 this.buildTabMarkup($tabs, $tabContent, 'alignment', alignmentAndBordersTabItems, this.localize('Alignment') + '/' + this.localize('Border'));
281 }
282 if (this.removedFieldsets.indexOf('color') === -1) {
283 this.addConfigElement(this.buildColorsFieldsetConfig(element), colorTabItems);
284 }
285 if (colorTabItems.length > 0) {
286 this.buildTabMarkup($tabs, $tabContent, 'color', colorTabItems, this.localize('Background and colors'));
287 }
288
289 $tabs.find('li:first').addClass('active');
290 $tabContent.find('.tab-pane:first').addClass('active');
291
292 $finalMarkup = $('<form />', {'class': 'form-horizontal'}).append($tabs, $tabContent);
293
294 return $finalMarkup;
295 },
296
297 /**
298 * Open the dialogue window
299 *
300 * @param {String} title The window title
301 * @param {Object} arguments Some arguments for the handler
302 * @param {Object} $tabItems The configuration of the tabbed panel
303 * @param {Function} handler Handler when the OK button if clicked
304 */
305 openDialogue: function (title, arguments, $tabItems, handler) {
306 this.dialog = Modal.show(this.localize(title), $tabItems, Severity.notice, [
307 this.buildButtonConfig('Cancel', $.proxy(this.onCancel, this), true),
308 this.buildButtonConfig('OK', $.proxy(handler, this), false, Severity.notice)
309 ]);
310 this.dialog.arguments = arguments;
311 this.dialog.on('modal-dismiss', $.proxy(this.onClose, this));
312 },
313 /**
314 * Insert the table or update the table properties and close the dialogue
315 */
316 tablePropertiesUpdate: function () {
317 this.restoreSelection();
318 var params = {};
319
320 this.dialog.find(':input').each(function() {
321 var $field = $(this);
322 params[$field.attr('name')] = $field.val();
323 });
324
325 var errorFlag = false,
326 field,
327 tab;
328 if (this.properties.required) {
329 if (this.properties.required.indexOf('captionOrSummary') !== -1) {
330 if (!/\S/.test(params.f_caption) && !/\S/.test(params.f_summary)) {
331 Notification.error(
332 this.getButton(this.dialog.arguments.buttonId).tooltip,
333 this.localize('captionOrSummary' + '-required'),
334 5
335 );
336 field = this.dialog.find('[name="f_caption"]');
337 tab = field.closest('[role="tabpanel"]').attr('id');
338 this.dialog.find('[role="tablist"] a[href="#' + tab + '"]').tab('show');
339 field.focus();
340 return false;
341 }
342 } else {
343 required = {
344 f_caption: 'caption',
345 f_summary: 'summary'
346 };
347 for (var item in required) {
348 if (required.hasOwnProperty(item) && !params[item] && this.properties.required.indexOf(required[item]) !== -1) {
349 Notification.error(
350 this.getButton(this.dialog.arguments.buttonId).tooltip,
351 this.this.localize(required[item] + '-required'),
352 5
353 );
354 field = this.dialog.find('[name="' + item + '"]');
355 tab = field.closest('[role="tabpanel"]').attr('id');
356 this.dialog.find('[role="tablist"] a[href="#' + tab + '"]').tab('show');
357 field.focus();
358 errorFlag = true;
359 break;
360 }
361 }
362 if (errorFlag) {
363 return false;
364 }
365 }
366 }
367 var doc = this.editor.document;
368 if (this.dialog.arguments.buttonId === 'InsertTable') {
369 var required = {
370 f_rows: 'You must enter a number of rows',
371 f_cols: 'You must enter a number of columns'
372 };
373 for (var item in required) {
374 if (required.hasOwnProperty(item) && !params[item]) {
375 Notification.error(
376 this.getButton(this.dialog.arguments.buttonId).tooltip,
377 this.localize(required[item]),
378 5
379 );
380
381 field = this.dialog.find('[name="' + item + '"]');
382 tab = field.closest('[role="tabpanel"]').attr('id');
383 this.dialog.find('[role="tablist"] a[href="#' + tab + '"]').tab('show');
384 field.focus();
385 errorFlag = true;
386 break;
387 }
388 }
389 if (errorFlag) {
390 return false;
391 }
392 var table = doc.createElement('table');
393 var tbody = doc.createElement('tbody');
394 table.appendChild(tbody);
395 for (var i = params.f_rows; --i >= 0;) {
396 var tr = doc.createElement('tr');
397 tbody.appendChild(tr);
398 for (var j = params.f_cols; --j >= 0;) {
399 var td = doc.createElement('td');
400 td.innerHTML = '<br />';
401 tr.appendChild(td);
402 }
403 }
404 } else {
405 table = this.dialog.arguments.element;
406 }
407 this.setHeaders(table, params);
408 this.processStyle(table, params);
409 table.removeAttribute('border');
410 for (var item in params) {
411 var val = params[item];
412 switch (item) {
413 case "f_caption":
414 if (/\S/.test(val)) {
415 // contains non white-space characters
416 var caption = table.getElementsByTagName("caption");
417 if (caption) {
418 caption = caption[0];
419 }
420 if (!caption) {
421 caption = doc.createElement("caption");
422 table.insertBefore(caption, table.firstChild);
423 }
424 caption.innerHTML = val;
425 } else {
426 // delete the caption if found
427 if (table.caption) table.deleteCaption();
428 }
429 break;
430 case "f_summary":
431 table.summary = val;
432 break;
433 case "f_width":
434 table.style.width = ("" + val) + params.f_unit;
435 break;
436 case "f_align":
437 table.align = val;
438 break;
439 case "f_spacing":
440 table.cellSpacing = val;
441 break;
442 case "f_padding":
443 table.cellPadding = val;
444 break;
445 case "f_frames":
446 if (val !== 'not set' && table.style.borderStyle !== 'none') {
447 table.frame = val;
448 } else {
449 table.removeAttribute('rules');
450 }
451 break;
452 case "f_rules":
453 if (val !== 'not set') {
454 table.rules = val;
455 } else {
456 table.removeAttribute('rules');
457 }
458 break;
459 case "f_st_float":
460 switch (val) {
461 case "not set":
462 Dom.removeClass(table, this.floatRight);
463 Dom.removeClass(table, this.floatLeft);
464 break;
465 case "right":
466 Dom.removeClass(table, this.floatLeft);
467 Dom.addClass(table, this.floatRight);
468 break;
469 case "left":
470 Dom.removeClass(table, this.floatRight);
471 Dom.addClass(table, this.floatLeft);
472 break;
473 }
474 break;
475 case "f_st_textAlign":
476 if (this.getPluginInstance('BlockElements')) {
477 this.getPluginInstance('BlockElements').toggleAlignmentClass(table, this.convertAlignment[val]);
478 table.style.textAlign = "";
479 }
480 break;
481 case "f_class":
482 case "f_class_tbody":
483 case "f_class_thead":
484 case "f_class_tfoot":
485 var tpart = table;
486 if (item.length > 7) {
487 tpart = table.getElementsByTagName(item.substring(8,13))[0];
488 }
489 if (tpart) {
490 this.getPluginInstance('BlockStyle').applyClassChange(tpart, val);
491 }
492 break;
493 case "f_lang":
494 this.getPluginInstance('Language').setLanguageAttributes(table, val);
495 break;
496 case "f_dir":
497 table.dir = (val !== "not set") ? val : "";
498 break;
499 }
500 }
501 if (this.dialog.arguments.buttonId === "InsertTable") {
502 this.editor.getSelection().insertNode(table);
503 this.editor.getSelection().selectNodeContents(table.rows[0].cells[0], true);
504 if (this.buttonsConfiguration.toggleborders && this.buttonsConfiguration.toggleborders.setOnTableCreation) {
505 this.toggleBorders(true);
506 }
507 }
508 Modal.currentModal.trigger('modal-dismiss');
509 },
510 /**
511 * Update the row/column/cell properties
512 */
513 rowCellPropertiesUpdate: function() {
514 this.restoreSelection();
515 // Collect values from each form field
516 var params = {};
517 this.dialog.find(':input').each(function() {
518 var $field = $(this);
519 params[$field.attr('name')] = $field.val();
520 });
521 var cell = this.dialog.arguments.cell;
522 var column = this.dialog.arguments.column;
523 var section = (cell || column) ? this.dialog.arguments.element.parentNode.parentNode : this.dialog.arguments.element.parentNode;
524 var table = section.parentNode;
525 var elements = [];
526 if (column) {
527 elements = this.getColumnCells(this.dialog.arguments.element);
528 } else {
529 elements.push(this.dialog.arguments.element);
530 }
531 for (var i = elements.length; --i >= 0;) {
532 var element = elements[i];
533 this.processStyle(element, params);
534 for (var item in params) {
535 var val = params[item];
536 switch (item) {
537 case "f_cell_type":
538 if (val.substring(0,2) != element.nodeName.toLowerCase()) {
539 element = this.remapCell(element, val.substring(0,2));
540 this.editor.getSelection().selectNodeContents(element, true);
541 }
542 if (val.substring(2,10) != element.scope) {
543 element.scope = val.substring(2,10);
544 }
545 break;
546 case "f_cell_abbr":
547 if (!column) {
548 element.abbr = (element.nodeName.toLowerCase() === 'td') ? '' : val;
549 }
550 break;
551 case "f_rowgroup":
552 var nodeName = section.nodeName.toLowerCase();
553 if (val != nodeName) {
554 var newSection = table.getElementsByTagName(val)[0];
555 if (!newSection) {
556 newSection = table.insertBefore(this.editor.document.createElement(val), table.getElementsByTagName("tbody")[0]);
557 }
558 var newElement;
559 if (nodeName === "thead" && val === "tbody") {
560 newElement = newSection.insertBefore(element, newSection.firstChild);
561 } else {
562 newElement = newSection.appendChild(element);
563 }
564 if (!section.hasChildNodes()) {
565 table.removeChild(section);
566 }
567 }
568 if (params.f_convertCells) {
569 if (val === "thead") {
570 this.remapRowCells(element, "th");
571 } else {
572 this.remapRowCells(element, "td");
573 }
574 }
575 break;
576 case "f_st_textAlign":
577 if (this.getPluginInstance('BlockElements')) {
578 this.getPluginInstance('BlockElements').toggleAlignmentClass(element, this.convertAlignment[val]);
579 element.style.textAlign = "";
580 }
581 break;
582 case "f_class":
583 this.getPluginInstance('BlockStyle').applyClassChange(element, val);
584 break;
585 case "f_lang":
586 this.getPluginInstance('Language').setLanguageAttributes(element, val);
587 break;
588 case "f_dir":
589 element.dir = (val !== "not set") ? val : "";
590 break;
591 }
592 }
593 }
594 this.reStyleTable(table);
595 Modal.currentModal.trigger('modal-dismiss');
596 },
597
598 /**
599 * This function gets called when the plugin is generated
600 */
601 onGenerate: function () {
602 // Set table borders if requested by configuration
603 if (this.buttonsConfiguration.toggleborders && this.buttonsConfiguration.toggleborders.setOnRTEOpen) {
604 this.toggleBorders(true);
605 }
606 // Register handler for the enter key for IE and Opera when buttons.table.disableEnterParagraphs is set in the editor configuration
607 if ((UserAgent.isIE || UserAgent.isOpera) && this.disableEnterParagraphs) {
608 var self = this;
609 this.editor.iframe.keyMap.addBinding({
610 key: Event.ENTER,
611 shift: false,
612 handler: function (event) { return self.onKey(event); }
613 });
614 }
615 },
616
617 /**
618 * This function gets called when the toolbar is being updated
619 */
620 onUpdateToolbar: function (button, mode, selectionEmpty, ancestors) {
621 if (mode === 'wysiwyg' && this.editor.isEditable()) {
622 switch (button.itemId) {
623 case 'TO-toggle-borders':
624 button.setInactive(!Dom.hasClass(this.editor.document.body, 'htmlarea-showtableborders'));
625 break;
626 case 'TO-cell-merge':
627 if (UserAgent.isGecko) {
628 var selection = this.editor.getSelection().get().selection;
629 button.setDisabled(button.disabled || selection.rangeCount < 2);
630 }
631 break;
632 }
633 }
634 },
635 /**
636 * This function gets called when a Table Operations button was pressed.
637 *
638 * @param {Object} editor The editor instance
639 * @param {String} id The button id or the key
640 *
641 * @return {Boolean} False if action is completed
642 */
643 onButtonPress: function (editor, id, target) {
644 // Could be a button or its hotkey
645 var buttonId = this.translateHotKey(id);
646 buttonId = buttonId ? buttonId : id;
647
648 var mozbr = '<br />';
649 var tableParts = ["tfoot", "thead", "tbody"];
650 var tablePartsIndex = { tfoot : 0, thead : 1, tbody : 2 };
651
652 // helper function that clears the content in a table row
653 function clearRow(tr) {
654 var tds = tr.getElementsByTagName("td");
655 for (var i = tds.length; --i >= 0;) {
656 var td = tds[i];
657 td.rowSpan = 1;
658 td.innerHTML = mozbr;
659 }
660 var ths = tr.getElementsByTagName("th");
661 for (var i = ths.length; --i >= 0;) {
662 var th = ths[i];
663 th.rowSpan = 1;
664 th.innerHTML = mozbr;
665 }
666 delete tds;
667 delete ths;
668 }
669
670 function splitRow(td) {
671 var n = parseInt("" + td.rowSpan);
672 var colSpan = td.colSpan;
673 var nodeName = td.nodeName.toLowerCase();
674 td.rowSpan = 1;
675 var tr = td.parentNode;
676 var sectionRowIndex = tr.sectionRowIndex;
677 var rows = tr.parentNode.rows;
678 var index = td.cellIndex;
679 while (--n > 0) {
680 tr = rows[++sectionRowIndex];
681 // Last row
682 if (!tr) tr = td.parentNode.parentNode.appendChild(editor.document.createElement("tr"));
683 var otd = editor.document.createElement(nodeName);
684 otd.colSpan = colSpan;
685 otd.innerHTML = mozbr;
686 tr.insertBefore(otd, tr.cells[index]);
687 }
688 }
689
690 function splitCol(td) {
691 var nc = parseInt("" + td.colSpan);
692 var nodeName = td.nodeName.toLowerCase();
693 td.colSpan = 1;
694 var tr = td.parentNode;
695 var ref = td.nextSibling;
696 while (--nc > 0) {
697 var otd = editor.document.createElement(nodeName);
698 otd.rowSpan = td.rowSpan;
699 otd.innerHTML = mozbr;
700 tr.insertBefore(otd, ref);
701 }
702 }
703
704 function splitCell(td) {
705 var nc = parseInt("" + td.colSpan);
706 splitCol(td);
707 var cells = td.parentNode.cells;
708 var index = td.cellIndex;
709 while (nc-- > 0) {
710 splitRow(cells[index++]);
711 }
712 }
713
714 function selectNextNode(el) {
715 var node = el.nextSibling;
716 while (node && node.nodeType != 1) {
717 node = node.nextSibling;
718 }
719 if (!node) {
720 node = el.previousSibling;
721 while (node && node.nodeType != 1) {
722 node = node.previousSibling;
723 }
724 }
725 if (!node) node = el.parentNode;
726 editor.getSelection().selectNodeContents(node);
727 }
728
729 function getSelectedCells(sel) {
730 var cell, range, i = 0, cells = [];
731 try {
732 while (range = sel.getRangeAt(i++)) {
733 cell = range.startContainer.childNodes[range.startOffset];
734 while (!/^(td|th|body)$/.test(cell.nodeName.toLowerCase())) cell = cell.parentNode;
735 if (/^(td|th)$/.test(cell.nodeName.toLowerCase())) cells.push(cell);
736 }
737 } catch(e) {
738 /* finished walking through selection */
739 }
740 return cells;
741 }
742
743 function deleteEmptyTable(table) {
744 var lastPart = true;
745 for (var j = tableParts.length; --j >= 0;) {
746 var tablePart = table.getElementsByTagName(tableParts[j])[0];
747 if (tablePart) lastPart = false;
748 }
749 if (lastPart) {
750 selectNextNode(table);
751 table.parentNode.removeChild(table);
752 }
753 }
754
755 function computeCellIndexes(table) {
756 var matrix = [];
757 var lookup = {};
758 for (var m = tableParts.length; --m >= 0;) {
759 var tablePart = table.getElementsByTagName(tableParts[m])[0];
760 if (tablePart) {
761 var rows = tablePart.rows;
762 for (var i = 0, n = rows.length; i < n; i++) {
763 var cells = rows[i].cells;
764 for (var j=0; j< cells.length; j++) {
765 var cell = cells[j];
766 var rowIndex = cell.parentNode.rowIndex;
767 var cellId = tableParts[m]+"-"+rowIndex+"-"+cell.cellIndex;
768 var rowSpan = cell.rowSpan || 1;
769 var colSpan = cell.colSpan || 1;
770 var firstAvailCol;
771 if (typeof matrix[rowIndex] === 'undefined') { matrix[rowIndex] = []; }
772 // Find first available column in the first row
773 for (var k=0; k<matrix[rowIndex].length+1; k++) {
774 if (typeof matrix[rowIndex][k] === 'undefined') {
775 firstAvailCol = k;
776 break;
777 }
778 }
779 lookup[cellId] = firstAvailCol;
780 for (var k=rowIndex; k<rowIndex+rowSpan; k++) {
781 if (typeof matrix[k] === 'undefined') { matrix[k] = []; }
782 var matrixrow = matrix[k];
783 for (var l=firstAvailCol; l<firstAvailCol+colSpan; l++) {
784 matrixrow[l] = "x";
785 }
786 }
787 }
788 }
789 }
790 }
791 return lookup;
792 }
793
794 function getActualCellIndex(cell, lookup) {
795 return lookup[cell.parentNode.parentNode.nodeName.toLowerCase()+"-"+cell.parentNode.rowIndex+"-"+cell.cellIndex];
796 }
797
798 var tr, part, table, cell, sel, cells, rows, tablePart, index, otd;
799 switch (buttonId) {
800 // ROWS
801 case "TO-row-insert-above":
802 case "TO-row-insert-under":
803 tr = this.editor.getSelection().getFirstAncestorOfType("tr");
804 if (!tr) {
805 break;
806 }
807 var otr = tr.cloneNode(true);
808 clearRow(otr);
809 otr = tr.parentNode.insertBefore(otr, (/under/.test(buttonId) ? tr.nextSibling : tr));
810 this.editor.getSelection().selectNodeContents(otr.firstChild, true);
811 this.reStyleTable(tr.parentNode.parentNode);
812 break;
813 case "TO-row-delete":
814 tr = this.editor.getSelection().getFirstAncestorOfType("tr");
815 if (!tr) {
816 break;
817 }
818 part = tr.parentNode;
819 table = part.parentNode;
820 if(part.rows.length === 1) { // this the last row, delete the whole table part
821 selectNextNode(part);
822 table.removeChild(part);
823 deleteEmptyTable(table);
824 } else {
825 // set the caret first to a position that doesn't disappear.
826 selectNextNode(tr);
827 part.removeChild(tr);
828 }
829 this.reStyleTable(table);
830 break;
831 case "TO-row-split":
832 cell = this.editor.getSelection().getFirstAncestorOfType(['td', 'th']);
833 if (!cell) {
834 break;
835 }
836 sel = editor.getSelection().get().selection;
837 if (UserAgent.isGecko && !sel.isCollapsed) {
838 cells = getSelectedCells(sel);
839 for (i = 0; i < cells.length; ++i) {
840 splitRow(cells[i]);
841 }
842 } else {
843 splitRow(cell);
844 }
845 break;
846
847 // COLUMNS
848 case "TO-col-insert-before":
849 case "TO-col-insert-after":
850 cell = this.editor.getSelection().getFirstAncestorOfType(['td', 'th']);
851 if (!cell) {
852 break;
853 }
854 index = cell.cellIndex;
855 table = cell.parentNode.parentNode.parentNode;
856 for (var j = tableParts.length; --j >= 0;) {
857 tablePart = table.getElementsByTagName(tableParts[j])[0];
858 if (tablePart) {
859 rows = tablePart.rows;
860 for (var i = rows.length; --i >= 0;) {
861 tr = rows[i];
862 var ref = tr.cells[index + (/after/.test(buttonId) ? 1 : 0)];
863 if (!ref) {
864 otd = editor.document.createElement(tr.lastChild.nodeName.toLowerCase());
865 otd.innerHTML = mozbr;
866 tr.appendChild(otd);
867 } else {
868 otd = editor.document.createElement(ref.nodeName.toLowerCase());
869 otd.innerHTML = mozbr;
870 tr.insertBefore(otd, ref);
871 }
872 }
873 }
874 }
875 this.reStyleTable(table);
876 break;
877 case "TO-col-split":
878 cell = this.editor.getSelection().getFirstAncestorOfType(['td', 'th']);
879 if (!cell) {
880 break;
881 }
882 sel = this.editor.getSelection().get().selection;
883 if (UserAgent.isGecko && !sel.isCollapsed) {
884 cells = getSelectedCells(sel);
885 for (i = 0; i < cells.length; ++i) {
886 splitCol(cells[i]);
887 }
888 } else {
889 splitCol(cell);
890 }
891 this.reStyleTable(table);
892 break;
893 case "TO-col-delete":
894 cell = this.editor.getSelection().getFirstAncestorOfType(['td', 'th']);
895 if (!cell) {
896 break;
897 }
898 index = cell.cellIndex;
899 part = cell.parentNode.parentNode;
900 table = part.parentNode;
901 var lastPart = true;
902 for (var j = tableParts.length; --j >= 0;) {
903 tablePart = table.getElementsByTagName(tableParts[j])[0];
904 if (tablePart) {
905 rows = tablePart.rows;
906 var lastColumn = true;
907 for (var i = rows.length; --i >= 0;) {
908 if(rows[i].cells.length > 1) lastColumn = false;
909 }
910 if (lastColumn) {
911 // this is the last column, delete the whole tablepart
912 // set the caret first to a position that doesn't disappear
913 selectNextNode(tablePart);
914 table.removeChild(tablePart);
915 } else {
916 // set the caret first to a position that doesn't disappear
917 if (part === tablePart) selectNextNode(cell);
918 for (var i = rows.length; --i >= 0;) {
919 if(rows[i].cells[index]) rows[i].removeChild(rows[i].cells[index]);
920 }
921 lastPart = false;
922 }
923 }
924 }
925 if (lastPart) {
926 // the last table section was deleted: delete the whole table
927 // set the caret first to a position that doesn't disappear
928 selectNextNode(table);
929 table.parentNode.removeChild(table);
930 }
931 this.reStyleTable(table);
932 break;
933
934 // CELLS
935 case "TO-cell-split":
936 cell = this.editor.getSelection().getFirstAncestorOfType(['td', 'th']);
937 if (!cell) {
938 break;
939 }
940 sel = this.editor.getSelection().get().selection;
941 if (UserAgent.isGecko && !sel.isCollapsed) {
942 cells = getSelectedCells(sel);
943 for (i = 0; i < cells.length; ++i) {
944 splitCell(cells[i]);
945 }
946 } else {
947 splitCell(cell);
948 }
949 this.reStyleTable(table);
950 break;
951 case "TO-cell-insert-before":
952 case "TO-cell-insert-after":
953 cell = this.editor.getSelection().getFirstAncestorOfType(['td', 'th']);
954 if (!cell) {
955 break;
956 }
957 tr = cell.parentNode;
958 otd = editor.document.createElement(cell.nodeName.toLowerCase());
959 otd.innerHTML = mozbr;
960 tr.insertBefore(otd, (/after/.test(buttonId) ? cell.nextSibling : cell));
961 this.reStyleTable(tr.parentNode.parentNode);
962 break;
963 case "TO-cell-delete":
964 cell = this.editor.getSelection().getFirstAncestorOfType(['td', 'th']);
965 if (!cell) {
966 break;
967 }
968 row = cell.parentNode;
969 if(row.cells.length == 1) { // this is the only cell in the row, delete the row
970 part = row.parentNode;
971 table = part.parentNode;
972 if (part.rows.length == 1) { // this the last row, delete the whole table part
973 selectNextNode(part);
974 table.removeChild(part);
975 deleteEmptyTable(table);
976 } else {
977 selectNextNode(row);
978 part.removeChild(row);
979 }
980 } else {
981 // set the caret first to a position that doesn't disappear
982 selectNextNode(cell);
983 row.removeChild(cell);
984 }
985 this.reStyleTable(table);
986 break;
987 case "TO-cell-merge":
988 sel = this.editor.getSelection().get().selection;
989 var range, i = 0;
990 rows = [];
991 for (var k = tableParts.length; --k >= 0;) {
992 rows[k] = [];
993 }
994 row = null;
995 cells = null;
996 if (UserAgent.isGecko) {
997 try {
998 while (range = sel.getRangeAt(i++)) {
999 td = range.startContainer.childNodes[range.startOffset];
1000 if (td.parentNode != row) {
1001 (cells) && rows[tablePartsIndex[row.parentNode.nodeName.toLowerCase()]].push(cells);
1002 row = td.parentNode;
1003 cells = [];
1004 }
1005 cells.push(td);
1006 }
1007 } catch(e) {
1008 /* finished walking through selection */
1009 }
1010 try { rows[tablePartsIndex[row.parentNode.nodeName.toLowerCase()]].push(cells); } catch(e) { }
1011 } else {
1012 // Internet Explorer, WebKit and Opera
1013 cell = this.editor.getSelection().getFirstAncestorOfType(['td', 'th']);
1014 if (!cell) {
1015 TYPO3.Dialog.InformationDialog({
1016 title: this.getButton('TO-cell-merge').tooltip,
1017 msg: this.localize('Please click into some cell')
1018 });
1019 break;
1020 }
1021 tr = cell.parentNode;
1022 var no_cols = parseInt(prompt(this.localize("How many columns would you like to merge?"), 2));
1023 if (!no_cols) {
1024 break;
1025 }
1026 var no_rows = parseInt(prompt(this.localize("How many rows would you like to merge?"), 2));
1027 if (!no_rows) {
1028 break;
1029 }
1030 var lookup = computeCellIndexes(cell.parentNode.parentNode.parentNode);
1031 var first_index = getActualCellIndex(cell, lookup);
1032 // Collect cells on first row
1033 td = cell;
1034 cells = [];
1035 for (var i = no_cols; --i >= 0;) {
1036 if (!td) {
1037 break;
1038 }
1039 cells.push(td);
1040 var last_index = getActualCellIndex(td, lookup);
1041 td = td.nextSibling;
1042 }
1043 rows[tablePartsIndex[tr.parentNode.nodeName.toLowerCase()]].push(cells);
1044 // Collect cells on following rows
1045 var first_index_found;
1046 for (var j = 1; j < no_rows; ++j) {
1047 tr = tr.nextSibling;
1048 if (!tr) break;
1049 cells = [];
1050 first_index_found = false;
1051 for (var i = 0; i < tr.cells.length; ++i) {
1052 td = tr.cells[i];
1053 if (!td) break;
1054 index = getActualCellIndex(td, lookup);
1055 if (index > last_index) {
1056 break;
1057 }
1058 if (index == first_index) {
1059 first_index_found = true;
1060 }
1061 if (index >= first_index) {
1062 cells.push(td);
1063 }
1064 }
1065 // If not rectangle, we quit!
1066 if (!first_index_found) break;
1067 rows[tablePartsIndex[tr.parentNode.nodeName.toLowerCase()]].push(cells);
1068 }
1069 }
1070 for (var k = tableParts.length; --k >= 0;) {
1071 cell, row;
1072 var cellHTML = "";
1073 var cellRowSpan = 0;
1074 var maxCellColSpan = 0;
1075 if (rows[k] && rows[k][0]) {
1076 for (var i = 0; i < rows[k].length; ++i) {
1077 cells = rows[k][i];
1078 var cellColSpan = 0;
1079 if (!cells) {
1080 continue;
1081 }
1082 cellRowSpan += cells[0].rowSpan ? cells[0].rowSpan : 1;
1083 for (var j = 0; j < cells.length; ++j) {
1084 cell = cells[j];
1085 var row = cell.parentNode;
1086 cellHTML += cell.innerHTML;
1087 cellColSpan += cell.colSpan ? cell.colSpan : 1;
1088 if (i || j) {
1089 cell.parentNode.removeChild(cell);
1090 if(!row.cells.length) row.parentNode.removeChild(row);
1091 }
1092 }
1093 if (maxCellColSpan < cellColSpan) {
1094 maxCellColSpan = cellColSpan;
1095 }
1096 }
1097 var td = rows[k][0][0];
1098 td.innerHTML = cellHTML;
1099 td.rowSpan = cellRowSpan;
1100 td.colSpan = maxCellColSpan;
1101 editor.getSelection().selectNodeContents(td);
1102 }
1103 }
1104 this.reStyleTable(table);
1105 break;
1106
1107 // CREATION AND PROPERTIES
1108 case "InsertTable":
1109 case "TO-table-prop":
1110 this.openPropertiesDialogue('table', buttonId);
1111 break;
1112 case "TO-table-restyle":
1113 this.reStyleTable(this.editor.getSelection().getFirstAncestorOfType('table'));
1114 break;
1115 case "TO-row-prop":
1116 this.openPropertiesDialogue('row', buttonId);
1117 break;
1118 case "TO-col-prop":
1119 this.openPropertiesDialogue('column', buttonId);
1120 break;
1121 case "TO-cell-prop":
1122 this.openPropertiesDialogue('cell', buttonId);
1123 break;
1124 case "TO-toggle-borders":
1125 this.toggleBorders();
1126 break;
1127 }
1128 },
1129 /**
1130 * Returns an array of all cells in the column containing the given cell
1131 *
1132 * @param {Object} cell The cell serving as reference point for the column
1133 *
1134 * @return {Array} The array of cells of the column
1135 */
1136 getColumnCells : function (cell) {
1137 var cells = [];
1138 var index = cell.cellIndex;
1139 var table = cell.parentNode.parentNode.parentNode;
1140 for (var j = this.tableParts.length; --j >= 0;) {
1141 var tablePart = table.getElementsByTagName(this.tableParts[j])[0];
1142 if (tablePart) {
1143 var rows = tablePart.rows;
1144 for (var i = rows.length; --i >= 0;) {
1145 if(rows[i].cells.length > index) {
1146 cells.push(rows[i].cells[index]);
1147 }
1148 }
1149 }
1150 }
1151 return cells;
1152 },
1153 /**
1154 * Toggles the display of borders on tables and table cells
1155 *
1156 * @param {Boolean} forceBorders If set, borders are displayed whatever the current state
1157 */
1158 toggleBorders : function (forceBorders) {
1159 var body = this.editor.document.body;
1160 if (!Dom.hasClass(body, 'htmlarea-showtableborders')) {
1161 Dom.addClass(body,'htmlarea-showtableborders');
1162 } else if (!forceBorders) {
1163 Dom.removeClass(body,'htmlarea-showtableborders');
1164 }
1165 },
1166 /**
1167 * Applies to rows/cells the alternating and counting classes of an alternating or counting style scheme
1168 *
1169 * @param {Object} table The table to be re-styled
1170 */
1171 reStyleTable: function (table) {
1172 if (table) {
1173 if (this.classesUrl && (typeof HTMLArea.classesAlternating === 'undefined' || typeof HTMLArea.classesCounting === 'undefined')) {
1174 this.getJavascriptFile(this.classesUrl, function (options, success, response) {
1175 if (success) {
1176 try {
1177 if (typeof HTMLArea.classesAlternating === 'undefined' || typeof HTMLArea.classesCounting === 'undefined') {
1178 eval(response.responseText);
1179 }
1180 this.reStyleTable(table);
1181 } catch(e) {
1182 this.appendToLog('reStyleTable', 'Error evaluating contents of Javascript file: ' + this.classesUrl, 'error');
1183 }
1184 }
1185 });
1186 } else {
1187 var classNames = table.className.trim().split(' ');
1188 for (var i = classNames.length; --i >= 0;) {
1189 var classConfiguration = HTMLArea.classesAlternating[classNames[i]];
1190 if (classConfiguration && classConfiguration.rows) {
1191 if (classConfiguration.rows.oddClass && classConfiguration.rows.evenClass) {
1192 this.alternateRows(table, classConfiguration);
1193 }
1194 }
1195 if (classConfiguration && classConfiguration.columns) {
1196 if (classConfiguration.columns.oddClass && classConfiguration.columns.evenClass) {
1197 this.alternateColumns(table, classConfiguration);
1198 }
1199 }
1200 classConfiguration = HTMLArea.classesCounting[classNames[i]];
1201 if (classConfiguration && classConfiguration.rows) {
1202 if (classConfiguration.rows.rowClass) {
1203 this.countRows(table, classConfiguration);
1204 }
1205 }
1206 if (classConfiguration && classConfiguration.columns) {
1207 if (classConfiguration.columns.columnClass) {
1208 this.countColumns(table, classConfiguration);
1209 }
1210 }
1211 }
1212 }
1213 }
1214 },
1215 /**
1216 * Removes from rows/cells the alternating classes of an alternating style scheme
1217 *
1218 * @param {Object} table The table to be re-styled
1219 * @param {String} removeClass The name of the class that identifies the alternating style scheme
1220 */
1221 removeAlternatingClasses: function (table, removeClass) {
1222 if (table) {
1223 if (this.classesUrl && typeof HTMLArea.classesAlternating === 'undefined') {
1224 this.getJavascriptFile(this.classesUrl, function (options, success, response) {
1225 if (success) {
1226 try {
1227 if (typeof HTMLArea.classesAlternating === 'undefined') {
1228 eval(response.responseText);
1229 }
1230 this.removeAlternatingClasses(table, removeClass);
1231 } catch(e) {
1232 this.appendToLog('removeAlternatingClasses', 'Error evaluating contents of Javascript file: ' + this.classesUrl, 'error');
1233 }
1234 }
1235 });
1236 } else {
1237 var classConfiguration = HTMLArea.classesAlternating[removeClass];
1238 if (classConfiguration) {
1239 if (classConfiguration.rows && classConfiguration.rows.oddClass && classConfiguration.rows.evenClass) {
1240 this.alternateRows(table, classConfiguration, true);
1241 }
1242 if (classConfiguration.columns && classConfiguration.columns.oddClass && classConfiguration.columns.evenClass) {
1243 this.alternateColumns(table, classConfiguration, true);
1244 }
1245 }
1246 }
1247 }
1248 },
1249 /**
1250 * Applies/removes the alternating classes of an alternating rows style scheme
1251 *
1252 * @param {Object} table The table to be re-styled
1253 * @param {Object} classConfiguration The alternating sub-array of the configuration of the class
1254 * @param {Boolean} remove If true, the classes are removed
1255 */
1256 alternateRows : function (table, classConfiguration, remove) {
1257 var oddClass = { tbody : classConfiguration.rows.oddClass, thead : classConfiguration.rows.oddHeaderClass };
1258 var evenClass = { tbody : classConfiguration.rows.evenClass, thead : classConfiguration.rows.evenHeaderClass };
1259 var startAt = parseInt(classConfiguration.rows.startAt);
1260 startAt = remove ? 1 : (startAt ? startAt : 1);
1261 var rows = table.rows, type, odd, even;
1262 // Loop through the rows
1263 for (var i = startAt-1, n = rows.length; i < n; i++) {
1264 var row = rows[i];
1265 type = (row.parentNode.nodeName.toLowerCase() === "thead") ? "thead" : "tbody";
1266 odd = oddClass[type];
1267 even = evenClass[type];
1268 if (remove) {
1269 Dom.removeClass(row, odd);
1270 Dom.removeClass(row, even);
1271 // Check if i is even, and apply classes for both possible results
1272 } else if (odd && even) {
1273 if ((i % 2) == 0) {
1274 if (Dom.hasClass(row, even)) {
1275 Dom.removeClass(row, even);
1276 }
1277 Dom.addClass(row, odd);
1278 } else {
1279 if (Dom.hasClass(row, odd)) {
1280 Dom.removeClass(row, odd);
1281 }
1282 Dom.addClass(row, even);
1283 }
1284 }
1285 }
1286 },
1287 /**
1288 * Applies/removes the alternating classes of an alternating columns style scheme
1289 *
1290 * @param {Object} table The table to be re-styled
1291 * @param {Object} classConfiguration The alternating sub-array of the configuration of the class
1292 * @param {Boolean} remove If true, the classes are removed
1293 */
1294 alternateColumns : function (table, classConfiguration, remove) {
1295 var oddClass = { td : classConfiguration.columns.oddClass, th : classConfiguration.columns.oddHeaderClass };
1296 var evenClass = { td : classConfiguration.columns.evenClass, th : classConfiguration.columns.evenHeaderClass };
1297 var startAt = parseInt(classConfiguration.columns.startAt);
1298 startAt = remove ? 1 : (startAt ? startAt : 1);
1299 var rows = table.rows, type, odd, even;
1300 // Loop through the rows of the table
1301 for (var i = rows.length; --i >= 0;) {
1302 // Loop through the cells
1303 var cells = rows[i].cells;
1304 for (var j = startAt-1, n = cells.length; j < n; j++) {
1305 var cell = cells[j];
1306 type = cell.nodeName.toLowerCase();
1307 odd = oddClass[type];
1308 even = evenClass[type];
1309 if (remove) {
1310 if (odd) Dom.removeClass(cell, odd);
1311 if (even) Dom.removeClass(cell, even);
1312 } else if (odd && even) {
1313 // Check if j+startAt is even, and apply classes for both possible results
1314 if ((j % 2) == 0) {
1315 if (Dom.hasClass(cell, even)) {
1316 Dom.removeClass(cell, even);
1317 }
1318 Dom.addClass(cell, odd);
1319 } else{
1320 if (Dom.hasClass(cell, odd)) {
1321 Dom.removeClass(cell, odd);
1322 }
1323 Dom.addClass(cell, even);
1324 }
1325 }
1326 }
1327 }
1328 },
1329 /**
1330 * Removes from rows/cells the counting classes of an counting style scheme
1331 *
1332 * @param {Object} table The table to be re-styled
1333 * @param {String} removeClass The name of the class that identifies the counting style scheme
1334 */
1335 removeCountingClasses: function (table, removeClass) {
1336 if (table) {
1337 if (this.classesUrl && typeof HTMLArea.classesCounting === 'undefined') {
1338 this.getJavascriptFile(this.classesUrl, function (options, success, response) {
1339 if (success) {
1340 try {
1341 if (typeof HTMLArea.classesCounting === 'undefined') {
1342 eval(response.responseText);
1343 }
1344 this.removeCountingClasses(table, removeClass);
1345 } catch(e) {
1346 this.appendToLog('removeCountingClasses', 'Error evaluating contents of Javascript file: ' + this.classesUrl, 'error');
1347 }
1348 }
1349 });
1350 } else {
1351 var classConfiguration = HTMLArea.classesCounting[removeClass];
1352 if (classConfiguration) {
1353 if (classConfiguration.rows && classConfiguration.rows.rowClass) {
1354 this.countRows(table, classConfiguration, true);
1355 }
1356 if (classConfiguration.columns && classConfiguration.columns.columnClass) {
1357 this.countColumns(table, classConfiguration, true);
1358 }
1359 }
1360 }
1361 }
1362 },
1363 /**
1364 * Applies/removes the counting classes of an counting rows style scheme
1365 *
1366 * @param {Object} table The table to be re-styled
1367 * @param {Object} classConfiguration The counting sub-array of the configuration of the class
1368 * @param {Boolean} remove If true, the classes are removed
1369 */
1370 countRows : function (table, classConfiguration, remove) {
1371 var rowClass = { tbody : classConfiguration.rows.rowClass, thead : classConfiguration.rows.rowHeaderClass };
1372 var rowLastClass = { tbody : classConfiguration.rows.rowLastClass, thead : classConfiguration.rows.rowHeaderLastClass };
1373 var startAt = parseInt(classConfiguration.rows.startAt);
1374 startAt = remove ? 1 : (startAt ? startAt : 1);
1375 var rows = table.rows, type, baseClassName, rowClassName, lastRowClassName;
1376 // Loop through the rows
1377 for (var i = startAt-1, n = rows.length; i < n; i++) {
1378 var row = rows[i];
1379 type = (row.parentNode.nodeName.toLowerCase() == "thead") ? "thead" : "tbody";
1380 baseClassName = rowClass[type];
1381 rowClassName = baseClassName + (i+1);
1382 lastRowClassName = rowLastClass[type];
1383 if (remove) {
1384 if (baseClassName) {
1385 Dom.removeClass(row, rowClassName);
1386 }
1387 if (lastRowClassName && i == n-1) {
1388 Dom.removeClass(row, lastRowClassName);
1389 }
1390 } else {
1391 if (baseClassName) {
1392 if (Dom.hasClass(row, baseClassName, true)) {
1393 Dom.removeClass(row, baseClassName, true);
1394 }
1395 Dom.addClass(row, rowClassName);
1396 }
1397 if (lastRowClassName) {
1398 if (i == n-1) {
1399 Dom.addClass(row, lastRowClassName);
1400 } else if (Dom.hasClass(row, lastRowClassName)) {
1401 Dom.removeClass(row, lastRowClassName);
1402 }
1403 }
1404 }
1405 }
1406 },
1407 /**
1408 * Applies/removes the counting classes of a counting columns style scheme
1409 *
1410 * @param {Object} table The table to be re-styled
1411 * @param {Object} classConfiguration The counting sub-array of the configuration of the class
1412 * @param {Boolean} remove If true, the classes are removed
1413 */
1414 countColumns : function (table, classConfiguration, remove) {
1415 var columnClass = { td : classConfiguration.columns.columnClass, th : classConfiguration.columns.columnHeaderClass };
1416 var columnLastClass = { td : classConfiguration.columns.columnLastClass, th : classConfiguration.columns.columnHeaderLastClass };
1417 var startAt = parseInt(classConfiguration.columns.startAt);
1418 startAt = remove ? 1 : (startAt ? startAt : 1);
1419 var rows = table.rows, type, baseClassName, columnClassName, lastColumnClassName;
1420 // Loop through the rows of the table
1421 for (var i = rows.length; --i >= 0;) {
1422 // Loop through the cells
1423 var cells = rows[i].cells;
1424 for (var j = startAt-1, n = cells.length; j < n; j++) {
1425 var cell = cells[j];
1426 type = cell.nodeName.toLowerCase();
1427 baseClassName = columnClass[type];
1428 columnClassName = baseClassName + (j+1);
1429 lastColumnClassName = columnLastClass[type];
1430 if (remove) {
1431 if (baseClassName) {
1432 Dom.removeClass(cell, columnClassName);
1433 }
1434 if (lastColumnClassName && j == n-1) {
1435 Dom.removeClass(cell, lastColumnClassName);
1436 }
1437 } else {
1438 if (baseClassName) {
1439 if (Dom.hasClass(cell, baseClassName, true)) {
1440 Dom.removeClass(cell, baseClassName, true);
1441 }
1442 Dom.addClass(cell, columnClassName);
1443 }
1444 if (lastColumnClassName) {
1445 if (j == n-1) {
1446 Dom.addClass(cell, lastColumnClassName);
1447 } else if (Dom.hasClass(cell, lastColumnClassName)) {
1448 Dom.removeClass(cell, lastColumnClassName);
1449 }
1450 }
1451 }
1452 }
1453 }
1454 },
1455 /**
1456 * This function sets the headers cells on the table (top, left, both or none)
1457 *
1458 * @param {Object} table The table being edited
1459 * @param {Object} params The field values entered in the form
1460 */
1461 setHeaders: function (table, params) {
1462 var headers = params.f_headers;
1463 var doc = this.editor.document;
1464 var tbody = table.tBodies[0];
1465 var thead = table.tHead;
1466 var firstRow;
1467 var rows;
1468 if (thead && !thead.rows.length && !tbody.rows.length) {
1469 // Table is degenerate
1470 return table;
1471 }
1472 if (headers == "top") {
1473 if (!thead) {
1474 thead = doc.createElement("thead");
1475 thead = table.insertBefore(thead, tbody);
1476 }
1477 if (!thead.rows.length) {
1478 firstRow = thead.appendChild(tbody.rows[0]);
1479 } else {
1480 firstRow = thead.rows[0];
1481 }
1482 Dom.removeClass(firstRow, this.useHeaderClass);
1483 } else {
1484 if (thead) {
1485 rows = thead.rows;
1486 if (rows.length) {
1487 for (var i = rows.length; --i >= 0;) {
1488 this.remapRowCells(rows[i], "td");
1489 if (tbody.rows.length) {
1490 tbody.insertBefore(rows[i], tbody.rows[0]);
1491 } else {
1492 tbody.appendChild(rows[i]);
1493 }
1494 }
1495 }
1496 table.removeChild(thead);
1497 }
1498 }
1499 if (headers == "both") {
1500 firstRow = tbody.rows[0];
1501 Dom.addClass(firstRow, this.useHeaderClass);
1502 } else if (headers != "top") {
1503 firstRow = tbody.rows[0];
1504 Dom.removeClass(firstRow, this.useHeaderClass);
1505 this.remapRowCells(firstRow, "td");
1506 }
1507 if (headers == "top" || headers == "both") {
1508 this.remapRowCells(firstRow, "th");
1509 }
1510 if (headers == "left") {
1511 firstRow = tbody.rows[0];
1512 }
1513 if (headers == "left" || headers == "both") {
1514 rows = tbody.rows;
1515 for (var i = rows.length; --i >= 0;) {
1516 if (i || rows[i] == firstRow) {
1517 if (rows[i].cells[0].nodeName.toLowerCase() != "th") {
1518 var th = this.remapCell(rows[i].cells[0], "th");
1519 th.scope = "row";
1520 }
1521 }
1522 }
1523 } else {
1524 rows = tbody.rows;
1525 for (var i = rows.length; --i >= 0;) {
1526 if (rows[i].cells[0].nodeName.toLowerCase() != "td") {
1527 rows[i].cells[0].scope = "";
1528 var td = this.remapCell(rows[i].cells[0], "td");
1529 }
1530 }
1531 }
1532 this.reStyleTable(table);
1533 },
1534
1535 /**
1536 * This function remaps the given cell to the specified node name
1537 */
1538 remapCell: function(element, nodeName) {
1539 var newCell = Dom.convertNode(element, nodeName);
1540 var attributes = element.attributes, attributeName, attributeValue;
1541 for (var i = attributes.length; --i >= 0;) {
1542 attributeName = attributes.item(i).nodeName;
1543 if (nodeName != 'td' || (attributeName != 'scope' && attributeName != 'abbr')) {
1544 attributeValue = element.getAttribute(attributeName);
1545 if (attributeValue) {
1546 newCell.setAttribute(attributeName, attributeValue);
1547 }
1548 }
1549 }
1550 if (this.tags && this.tags[nodeName] && this.tags[nodeName].allowedClasses) {
1551 if (newCell.className && /\S/.test(newCell.className)) {
1552 var allowedClasses = this.tags[nodeName].allowedClasses;
1553 var classNames = newCell.className.trim().split(" ");
1554 for (var i = classNames.length; --i >= 0;) {
1555 if (!allowedClasses.test(classNames[i])) {
1556 Dom.removeClass(newCell, classNames[i]);
1557 }
1558 }
1559 }
1560 }
1561 return newCell;
1562 },
1563
1564 remapRowCells: function (row, toType) {
1565 var cells = row.cells;
1566 if (toType === "th") {
1567 for (var i = cells.length; --i >= 0;) {
1568 if (cells[i].nodeName.toLowerCase() != "th") {
1569 var th = this.remapCell(cells[i], "th");
1570 th.scope = "col";
1571 }
1572 }
1573 } else {
1574 for (var i = cells.length; --i >= 0;) {
1575 if (cells[i].nodeName.toLowerCase() != "td") {
1576 var td = this.remapCell(cells[i], "td");
1577 td.scope = "";
1578 }
1579 }
1580 }
1581 },
1582
1583 /**
1584 * This function applies the style properties found in params to the given element
1585 *
1586 * @param {Object} element The element
1587 * @param {Object} params The properties
1588 */
1589 processStyle: function (element, params) {
1590 var style = element.style;
1591 style.cssFloat = '';
1592 style.textAlign = "";
1593 for (var i in params) {
1594 if (params.hasOwnProperty(i)) {
1595 var val = params[i];
1596 switch (i) {
1597 case "f_st_backgroundColor":
1598 if (/\S/.test(val)) {
1599 style.backgroundColor = ((val.charAt(0) === '#') ? '' : '#') + val;
1600 } else {
1601 style.backgroundColor = '';
1602 }
1603 break;
1604 case "f_st_color":
1605 if (/\S/.test(val)) {
1606 style.color = ((val.charAt(0) === '#') ? '' : '#') + val;
1607 } else {
1608 style.color = '';
1609 }
1610 break;
1611 case "f_st_backgroundImage":
1612 if (/\S/.test(val)) {
1613 style.backgroundImage = "url(" + val + ")";
1614 } else {
1615 style.backgroundImage = "";
1616 }
1617 break;
1618 case "f_st_borderWidth":
1619 if (/\S/.test(val)) {
1620 style.borderWidth = val + "px";
1621 } else {
1622 style.borderWidth = "";
1623 }
1624 if (params.f_st_borderStyle === "none") {
1625 style.borderWidth = "0px";
1626 }
1627 if (params.f_st_borderStyle === "not set") {
1628 style.borderWidth = "";
1629 }
1630 break;
1631 case "f_st_borderStyle":
1632 style.borderStyle = (val != "not set") ? val : "";
1633 break;
1634 case "f_st_borderColor":
1635 if (/\S/.test(val)) {
1636 style.borderColor = ((val.charAt(0) === '#') ? '' : '#') + val;
1637 } else {
1638 style.borderColor = '';
1639 }
1640 if (params.f_st_borderStyle === 'none') {
1641 style.borderColor = '';
1642 }
1643 break;
1644 case "f_st_borderCollapse":
1645 style.borderCollapse = (val !== 'not set') ? val : '';
1646 if (params.f_st_borderStyle === 'none') {
1647 style.borderCollapse = '';
1648 }
1649 break;
1650 case "f_st_width":
1651 if (/\S/.test(val)) {
1652 style.width = val + params.f_st_widthUnit;
1653 } else {
1654 style.width = "";
1655 }
1656 break;
1657 case "f_st_height":
1658 if (/\S/.test(val)) {
1659 style.height = val + params.f_st_heightUnit;
1660 } else {
1661 style.height = "";
1662 }
1663 break;
1664 case "f_st_textAlign":
1665 style.textAlign = (val !== "not set") ? val : "";
1666 break;
1667 case "f_st_vertAlign":
1668 style.verticalAlign = (val !== "not set") ? val : "";
1669 break;
1670 }
1671 }
1672 }
1673 },
1674 /**
1675 * This function builds the configuration object for the table Description fieldset
1676 *
1677 * @param {Object} table The table being edited, if any
1678 * @return {Object} The fieldset configuration object
1679 */
1680 buildDescriptionFieldsetConfig: function (table) {
1681 var $fieldset = $('<fieldset />'),
1682 caption = null;
1683
1684 if (typeof table === 'object' && table !== null) {
1685 caption = table.getElementsByTagName('caption')[0];
1686 }
1687
1688 $fieldset.append(
1689 $('<h4 />', {'class': 'form-section-headline'}).text(this.localize('Description')),
1690 $('<div />', {'class': 'form-group'}).append(
1691 $('<label />', {'class': 'col-sm-2'}).html(this.getHelpTip('caption', 'Caption:')),
1692 $('<div />', {'class': 'col-sm-10'}).append(
1693 $('<input />', {name: 'f_caption', 'class': 'form-control', value: (caption !== null ? caption.innerHTML : '')})
1694 )
1695 ),
1696 $('<div />', {'class': 'form-group'}).append(
1697 $('<label />', {'class': 'col-sm-2'}).html(this.getHelpTip('summary', 'Summary:')),
1698 $('<div />', {'class': 'col-sm-10'}).append(
1699 $('<input />', {name: 'f_summary', 'class': 'form-control', value: (typeof table === 'object' && table !== null ? table.summary : '')})
1700 )
1701 )
1702 );
1703
1704 return $fieldset;
1705 },
1706 /**
1707 * This function builds the configuration object for the table Size and Headers fieldset
1708 *
1709 * @param {Object} table The table being edited, if any
1710 * @return {Object} The fieldset configuration object
1711 */
1712 buildSizeAndHeadersFieldsetConfig: function (table) {
1713 var $fieldset = $('<fieldset />'),
1714 isUndefinedTable = typeof table !== 'object' || table === null;
1715
1716 $fieldset.append(
1717 $('<h4 />', {'class': 'form-section-headline'}).text(this.localize(isUndefinedTable ? 'Size and Headers' : 'Headers'))
1718 );
1719
1720 if (isUndefinedTable) {
1721 $fieldset.append(
1722 $('<div />', {'class': 'form-group'}).append(
1723 $('<label />', {'class': 'col-sm-2'}).html(this.getHelpTip('numberOfRows', 'Number of rows')),
1724 $('<div />', {'class': 'col-sm-10'}).append(
1725 $('<input />', {name: 'f_rows', type: 'number', min: 1, 'class': 'form-control', value: (this.properties.numberOfRows && this.properties.numberOfRows.defaultValue ? this.properties.numberOfRows.defaultValue : '2')})
1726 )
1727 ),
1728 $('<div />', {'class': 'form-group'}).append(
1729 $('<label />', {'class': 'col-sm-2'}).html(this.getHelpTip('numberOfColumns', 'Number of columns')),
1730 $('<div />', {'class': 'col-sm-10'}).append(
1731 $('<input />', {name: 'f_cols', type: 'number', min: 1, 'class': 'form-control', value: (this.properties.numberOfColumns && this.properties.numberOfColumns.defaultValue ? this.properties.numberOfColumns.defaultValue : '4')})
1732 )
1733 )
1734 );
1735 }
1736
1737 if (this.removedProperties.indexOf('headers') === -1) {
1738 var selectedValue;
1739
1740 if (isUndefinedTable) {
1741 selectedValue = (this.properties.headers && this.properties.headers.defaultValue) ? this.properties.headers.defaultValue : 'top';
1742 } else {
1743 selectedValue = 'none';
1744 var thead = table.getElementsByTagName('thead');
1745 var tbody = table.getElementsByTagName('tbody');
1746 if (thead.length && thead[0].rows.length) {
1747 selectedValue = 'top';
1748 } else if (tbody.length && tbody[0].rows.length) {
1749 if (Dom.hasClass(tbody[0].rows[0], this.useHeaderClass)) {
1750 selectedValue = 'both';
1751 } else if (tbody[0].rows[0].cells.length && tbody[0].rows[0].cells[0].nodeName.toLowerCase() == 'th') {
1752 selectedValue = 'left';
1753 }
1754 }
1755 }
1756
1757 $fieldset = this.attachSelectMarkup(
1758 $fieldset,
1759 this.getHelpTip('tableHeaders', 'Headers:'),
1760 'typeof',
1761 [
1762 [this.localize('No header cells'), 'none'],
1763 [this.localize('Header cells on top'), 'top'],
1764 [this.localize('Header cells on left'), 'left'],
1765 [this.localize('Header cells on top and left'), 'both']
1766 ],
1767 selectedValue
1768 );
1769 }
1770
1771 return $fieldset;
1772 },
1773 /**
1774 * This function builds the configuration object for the Style fieldset
1775 *
1776 * @param {Object} element The element being edited, if any
1777 * @param {String} buttonId The id of the button that was pressed
1778 * @return {Object} The fieldset configuration object
1779 */
1780 buildStylingFieldsetConfig: function (element, buttonId) {
1781 var $fieldset = $('<fieldset />'),
1782 nodeName = element ? element.nodeName.toLowerCase() : 'table',
1783 isTable = (nodeName === 'table');
1784
1785 var cssStyleSelect = new Select(this.buildStylingFieldConfig('f_class', (isTable ? 'Table class:' : 'Class:'), (isTable ? 'Table class selector' : 'Class selector')));
1786 $fieldset.append(
1787 $('<h4 />', {'class': 'form-section-headline'}).html(isTable ? this.getHelpTip('tableStyle', 'CSS Style') : this.localize('CSS Style'))
1788 );
1789 cssStyleSelect.render($fieldset[0]);
1790 this.setStyleOptions(cssStyleSelect, element, nodeName, (buttonId === 'InsertTable') ? this.defaultClass : null);
1791
1792 if (element && isTable) {
1793 var tbody = element.getElementsByTagName('tbody')[0];
1794 if (tbody) {
1795 var tableBodySelect = new Select(this.buildStylingFieldConfig('f_class_tbody', 'Table body class:', 'Table body class selector'));
1796 tableBodySelect.render($fieldset[0]);
1797 this.setStyleOptions(tableBodySelect, tbody, 'tbody');
1798 }
1799 var thead = element.getElementsByTagName('thead')[0];
1800 if (thead) {
1801 var theadStyleSelect = new Select(this.buildStylingFieldConfig('f_class_thead', 'Table header class:', 'Table header class selector'));
1802 theadStyleSelect.render($fieldset[0]);
1803 this.setStyleOptions(theadStyleSelect, thead, 'thead');
1804 }
1805 var tfoot = element.getElementsByTagName('tfoot')[0];
1806 if (tfoot) {
1807 var tfootStyleSelect = this.buildStylingFieldConfig('f_class_tfoot', 'Table footer class:', 'Table footer class selector');
1808 tfootStyleSelect.render($fieldset[0]);
1809 this.setStyleOptions(tfootStyleSelect, tfoot, 'tfoot');
1810 }
1811 }
1812 return $fieldset;
1813 },
1814 /** This function builds a style selection field
1815 *
1816 * @param {String} fieldName The name of the field
1817 * @param {String} fieldLabel The label for the field
1818 * @param {String} fieldTitle The title for the field tooltip
1819 * @return {Object} The style selection field object
1820 */
1821 buildStylingFieldConfig: function(fieldName, fieldLabel, fieldTitle) {
1822 // This is a nasty hack to fake ExtJS object configuration
1823 return Util.apply(
1824 {
1825 xtype: 'htmlareaselect',
1826 itemId: fieldName,
1827 fieldLabel: this.getHelpTip(fieldTitle, fieldLabel),
1828 helpTitle: typeof TYPO3.ContextHelp !== 'undefined' ? '' : this.localize(fieldTitle),
1829 width: (this.properties['style'] && this.properties['style'].width) ? this.properties['style'].width : 300
1830 },
1831 this.configDefaults['combo']
1832 );
1833 },
1834 /**
1835 * This function populates the style store and sets the selected option
1836 *
1837 * @param {Object} dropDown The combobox object
1838 * @param {Object} element The element being edited, if any
1839 * @param {String} nodeName The type of element ('table' on table insertion)
1840 * @param {String} defaultClass Default class, if any is configured
1841 * @return {Object} The fieldset configuration object
1842 */
1843 setStyleOptions: function (dropDown, element, nodeName, defaultClass) {
1844 var blockStyle = this.getPluginInstance('BlockStyle');
1845 if (dropDown && blockStyle) {
1846 var classNames;
1847 if (defaultClass) {
1848 classNames = [];
1849 classNames.push(defaultClass);
1850 } else {
1851 classNames = Dom.getClassNames(element);
1852 }
1853 blockStyle.buildDropDownOptions(dropDown, nodeName);
1854 blockStyle.setSelectedOption(dropDown, classNames, false, defaultClass);
1855 }
1856 },
1857 /**
1858 * This function builds the configuration object for the Language fieldset
1859 *
1860 * @param {Object} element The element being edited, if any
1861 * @return {Object} The fieldset configuration object
1862 */
1863 buildLanguageFieldsetConfig: function (element) {
1864 var self = this,
1865 $fieldset = $('<fieldset />', {id: 'languageFieldset'}),
1866 languageObject = this.getPluginInstance('Language');
1867
1868 if (this.removedProperties.indexOf('language') === -1 && this.getButton('Language')) {
1869 var selectedLanguage = typeof element === 'object' && element !== null ? languageObject.getLanguageAttribute(element) : 'none';
1870 }
1871
1872 $fieldset.append(
1873 $('<h4 />', {'class': 'form-section-headline'}).text(self.localize('Language')),
1874 $('<div />', {'class': 'form-group'}).append(
1875 $('<label />', {'class': 'col-sm-2'}).html(self.getHelpTip('languageCombo', 'Language', 'Language')),
1876 $('<div />', {'class': 'col-sm-10'}).append(
1877 $('<select />', {name: 'f_lang', 'class': 'form-control'})
1878 )
1879 )
1880 );
1881
1882 $.ajax({
1883 url: this.getDropDownConfiguration('Language').dataUrl,
1884 dataType: 'json',
1885 success: function(response) {
1886 var $select = $fieldset.find('select[name="f_lang"]');
1887
1888 for (var language in response.options) {
1889 if (response.options.hasOwnProperty(language)) {
1890 if (language === 0 && selectedLanguage !== 'none') {
1891 response.options[language].value = 'none';
1892 response.options[language].text = languageObject.localize('Remove language mark');
1893 }
1894 var attributeConfiguration = {value: response.options[language].value};
1895 if (selectedLanguage === response.options[language].value) {
1896 attributeConfiguration.selected = 'selected';
1897 }
1898 $select.append(
1899 $('<option />', attributeConfiguration).text(response.options[language].text)
1900 );
1901 }
1902 }
1903 }
1904 });
1905
1906 if (this.removedProperties.indexOf('direction') === -1 && (this.getButton('LeftToRight') || this.getButton('RightToLeft'))) {
1907 $fieldset = this.attachSelectMarkup(
1908 $fieldset,
1909 self.getHelpTip('directionCombo', 'Text direction', 'Language'),
1910 'f_dir',
1911 [
1912 [this.localize('Not set'), 'not set'],
1913 [this.localize('RightToLeft'), 'rtl'],
1914 [this.localize('LeftToRight'), 'ltr']
1915 ],
1916 selectedLanguage
1917 );
1918 }
1919
1920 return $fieldset;
1921 },
1922 /**
1923 * This function builds the configuration object for the spacing fieldset
1924 *
1925 * @param {Object} table The table being edited, if any
1926 * @return {Object} The fieldset configuration object
1927 */
1928 buildSpacingFieldsetConfig: function (table) {
1929 var $fieldset = $('<fieldset />'),
1930 isTable = typeof table === 'object' && table !== null;
1931
1932 $fieldset.append(
1933 $('<h4 />', {'class': 'form-section-headline'}).text(this.localize('Spacing and padding')),
1934 $('<div />', {'class': 'form-group'}).append(
1935 $('<label />', {'class': 'col-sm-2'}).html(this.getHelpTip('cellSpacing', 'Cell spacing:')),
1936 $('<div />', {'class': 'col-sm-10'}).append(
1937 $('<input />', {name: 'f_spacing', 'class': 'form-control', type: 'number', min: 0, value: isTable ? table.cellSpacing : ''})
1938 )
1939 ),
1940 $('<div />', {'class': 'form-group'}).append(
1941 $('<label />', {'class': 'col-sm-2'}).html(this.getHelpTip('cellPadding', 'Cell padding:')),
1942 $('<div />', {'class': 'col-sm-10'}).append(
1943 $('<input />', {name: 'f_padding', 'class': 'form-control', type: 'number', min: 0, value: isTable ? table.cellPadding : ''})
1944 )
1945 )
1946 );
1947
1948 return $fieldset;
1949 },
1950
1951 /**
1952 * This function builds the configuration object for the Layout fieldset
1953 *
1954 * @param {Object} element The element being edited, if any
1955 * @return {Object} The fieldset configuration object
1956 */
1957 buildLayoutFieldsetConfig: function(element) {
1958 var $fieldset = $('<fieldset />'),
1959 nodeName = element ? element.nodeName.toLowerCase() : 'table',
1960 widthTitle = '',
1961 heightTitle = '',
1962 availableUnitOptions = [
1963 [this.localize('percent'), '%'],
1964 [this.localize('pixels'), 'px'],
1965 [this.localize('em'), 'em']
1966 ];
1967
1968 switch(nodeName) {
1969 case 'table' :
1970 widthTitle = 'Table width';
1971 heightTitle = 'Table height';
1972 break;
1973 case 'tr' :
1974 widthTitle = 'Row width';
1975 heightTitle = 'Row height';
1976 break;
1977 case 'td' :
1978 case 'th' :
1979 widthTitle = 'Cell width';
1980 heightTitle = 'Cell height';
1981 }
1982 $fieldset.append(
1983 $('<h4 />', {'class': 'form-section-headline'}).text(this.localize('Layout'))
1984 );
1985
1986 if (this.removedProperties.indexOf('width') === -1) {
1987 $fieldset.append(
1988 $('<div />', {'class': 'form-group'}).append(
1989 $('<label />', {'class': 'col-sm-2'}).html(this.getHelpTip(widthTitle, 'Width:')),
1990 $('<div />', {'class': 'col-sm-10'}).append(
1991 $('<input />', {type: 'number', name: 'f_st_width', 'class': 'form-control'}).val(element ? this.getLength(element.style.width) : ((this.properties.width && this.properties.width.defaultValue) ? this.properties.width.defaultValue : ''))
1992 )
1993 )
1994 );
1995
1996 $fieldset = this.attachSelectMarkup(
1997 $fieldset,
1998 this.getHelpTip('Width unit', 'Width unit:'),
1999 'f_st_widthUnit',
2000 availableUnitOptions,
2001 element ? (/%/.test(element.style.width) ? '%' : (/px/.test(element.style.width) ? 'px' : 'em')) : ((this.properties.widthUnit && this.properties.widthUnit.defaultValue) ? this.properties.widthUnit.defaultValue : '%')
2002 );
2003 }
2004 if (this.removedProperties.indexOf('height') === -1) {
2005 $fieldset.append(
2006 $('<div />', {'class': 'form-group'}).append(
2007 $('<label />', {'class': 'col-sm-2'}).html(this.getHelpTip(heightTitle, 'Height:')),
2008 $('<div />', {'class': 'col-sm-10'}).append(
2009 $('<input />', {type: 'number', name: 'f_st_height', 'class': 'form-control'}).val(element ? (/%/.test(element.style.height) ? '%' : (/px/.test(element.style.height) ? 'px' : 'em')) : ((this.properties.heightUnit && this.properties.heightUnit.defaultValue) ? this.properties.heightUnit.defaultValue : '%'))
2010 )
2011 )
2012 );
2013
2014 $fieldset = this.attachSelectMarkup(
2015 $fieldset,
2016 this.getHelpTip('Height unit', 'Height unit:'),
2017 'f_st_heightUnit',
2018 availableUnitOptions,
2019 element ? (/%/.test(element.style.height) ? '%' : (/px/.test(element.style.height) ? 'px' : 'em')) : ((this.properties.heightUnit && this.properties.heightUnit.defaultValue) ? this.properties.heightUnit.defaultValue : '%')
2020 );
2021 }
2022
2023 if (nodeName === 'table' && this.removedProperties.indexOf('float') === -1) {
2024 $fieldset = this.attachSelectMarkup(
2025 $fieldset,
2026 this.getHelpTip('tableFloat', 'Float:'),
2027 'f_st_float',
2028 [
2029 [this.localize('Not set'), 'not set'],
2030 [this.localize('Left'), 'left'],
2031 [this.localize('Right'), 'right']
2032 ],
2033 element ? (Dom.hasClass(element, this.floatLeft) ? 'left' : (Dom.hasClass(element, this.floatRight) ? 'right' : 'not set')) : this.floatDefault
2034 );
2035 }
2036
2037 return $fieldset;
2038 },
2039
2040 /**
2041 * This function builds the configuration object for the Layout fieldset
2042 *
2043 * @param {Object} element The element being edited, if any
2044 * @return {Object} The fieldset configuration object
2045 */
2046 buildAlignmentFieldsetConfig: function (element) {
2047 var $fieldset = $('<fieldset />'),
2048 selectedTextAlign = 'not set',
2049 blockElements = this.getPluginInstance('BlockElements');
2050
2051 // Text alignment
2052 if (element && blockElements) {
2053 for (var value in this.convertAlignment) {
2054 if (this.convertAlignment.hasOwnProperty(value) && Dom.hasClass(element, blockElements.useClass[this.convertAlignment[value]])) {
2055 selectedTextAlign = value;
2056 break;
2057 }
2058 }
2059 } else {
2060 selectedTextAlign = (element && element.style.textAlign) ? element.style.textAlign : 'not set';
2061 }
2062
2063 $fieldset.append(
2064 $('<h4 />', {'class': 'form-section-headline'}).text(this.localize('Alignment'))
2065 );
2066
2067 $fieldset = this.attachSelectMarkup(
2068 $fieldset,
2069 this.getHelpTip('textAlignment', 'Text alignment:'),
2070 'f_st_textAlign',
2071 [
2072 [this.localize('Not set'), 'not set'],
2073 [this.localize('Left'), 'left'],
2074 [this.localize('Center'), 'center'],
2075 [this.localize('Right'), 'right'],
2076 [this.localize('Justify'), 'justify']
2077 ],
2078 selectedTextAlign
2079 );
2080
2081 // Vertical alignment
2082 $fieldset = this.attachSelectMarkup(
2083 $fieldset,
2084 this.getHelpTip('verticalAlignment', 'Vertical alignment:'),
2085 'f_st_vertAlign',
2086 [
2087 [this.localize('Not set'), 'not set'],
2088 [this.localize('Top'), 'top'],
2089 [this.localize('Middle'), 'middle'],
2090 [this.localize('Bottom'), 'bottom'],
2091 [this.localize('Baseline'), 'baseline']
2092 ],
2093 (element && element.style.verticalAlign) ? element.style.verticalAlign : 'not set'
2094 );
2095
2096 return $fieldset;
2097 },
2098 /**
2099 * This function builds the configuration object for the Borders fieldset
2100 *
2101 * @param {Object} element The element being edited, if any
2102 * @return {Object} The fieldset configuration object
2103 */
2104 buildBordersFieldsetConfig: function (element) {
2105 var $fieldset = $('<fieldset />'),
2106 nodeName = element ? element.nodeName.toLowerCase() : 'table',
2107 selectedBorderStyle = element && element.style.borderStyle ? element.style.borderStyle : ((this.properties.borderWidth) ? ((this.properties.borderStyle && this.properties.borderStyle.defaultValue) ? this.properties.borderStyle.defaultValue : 'solid') : 'not set');
2108
2109 $fieldset.append(
2110 $('<h4 />', {'class': 'form-section-headline'}).text(this.localize('Frame and borders'))
2111 );
2112
2113 $fieldset = this.attachSelectMarkup(
2114 $fieldset,
2115 this.getHelpTip('borderStyle', 'Border style:'),
2116 'f_st_borderStyle',
2117 [
2118 [this.localize('Not set'), 'not set'],
2119 [this.localize('No border'), 'none'],
2120 [this.localize('Dotted'), 'dotted'],
2121 [this.localize('Dashed'), 'dashed'],
2122 [this.localize('Solid'), 'solid'],
2123 [this.localize('Double'), 'double'],
2124 [this.localize('Groove'), 'groove'],
2125 [this.localize('Ridge'), 'ridge'],
2126 [this.localize('Inset'), 'inset'],
2127 [this.localize('Outset'), 'outset']
2128 ],
2129 selectedBorderStyle,
2130 this.setBorderFieldsDisabled
2131 );
2132
2133 $fieldset.append(
2134 // Border width
2135 $('<div />', {'class': 'form-group'}).append(
2136 $('<label />', {'class': 'col-sm-2'}).html(this.getHelpTip('borderWidth', 'Border width:')),
2137 $('<div />', {'class': 'col-sm-10'}).append(
2138 $('<input />', {name: 'f_st_borderWidth', type: 'number', min: 0, 'class': 'form-control', value: element ? this.getLength(element.style.borderWidth) : ((this.properties.borderWidth && this.properties.borderWidth.defaultValue) ? this.properties.borderWidth.defaultValue : '')})
2139 .prop('disabled', selectedBorderStyle === 'none')
2140 )
2141 )
2142 );
2143 // Border color
2144 var $input = $('<input />', {name: 'f_st_borderColor', 'class': 'form-control t3js-color-input', value: Color.colorToHex(element && element.style.borderColor ? element.style.borderColor : ((this.properties.borderColor && this.properties.borderColor.defaultValue) ? this.properties.borderColor.defaultValue : ''))})
2145 .prop('disabled', selectedBorderStyle === 'none');
2146
2147 require(['TYPO3/CMS/Core/Contrib/jquery.minicolors'], function () {
2148 $input.minicolors({
2149 theme: 'bootstrap',
2150 format: 'hex',
2151 position: 'bottom left'
2152 });
2153 });
2154 $fieldset.append(
2155 $('<div />', {'class': 'form-group'}).append(
2156 $('<label />', {'class': 'col-sm-2'}).html(this.getHelpTip('borderColor', 'Color:')),
2157 $('<div />', {'class': 'col-sm-10'}).append(
2158 $input
2159 )
2160 )
2161 );
2162
2163 if (nodeName === 'table') {
2164 // Collapsed borders
2165 $fieldset = this.attachSelectMarkup(
2166 $fieldset,
2167 this.getHelpTip('collapsedBorders', 'Collapsed borders:'),
2168 'f_st_borderCollapse',
2169 [
2170 [this.localize('Not set'), 'not set'],
2171 [this.localize('Collapsed borders'), 'collapse'],
2172 [this.localize('Detached borders'), 'separate']
2173 ],
2174 element && element.style.borderCollapse ? element.style.borderCollapse : 'not set',
2175 null,
2176 selectedBorderStyle === 'none'
2177 );
2178
2179 // Frame
2180 $fieldset = this.attachSelectMarkup(
2181 $fieldset,
2182 this.getHelpTip('frames', 'Frames:'),
2183 'f_frames',
2184 [
2185 [this.localize('Not set'), 'not set'],
2186 [this.localize('No sides'), 'void'],
2187 [this.localize('The top side only'), 'above'],
2188 [this.localize('The bottom side only'), 'below'],
2189 [this.localize('The top and bottom sides only'), 'hsides'],
2190 [this.localize('The right and left sides only'), 'vsides'],
2191 [this.localize('The left-hand side only'), 'lhs'],
2192 [this.localize('The right-hand side only'), 'rhs'],
2193 [this.localize('All four sides'), 'box']
2194 ],
2195 element && element.frame ? element.frame : 'not set',
2196 null,
2197 selectedBorderStyle === 'none'
2198 );
2199
2200 // Fules
2201 $fieldset = this.attachSelectMarkup(
2202 $fieldset,
2203 this.getHelpTip('rules', 'Rules:'),
2204 'f_rules',
2205 [
2206 [this.localize('Not set'), 'not set'],
2207 [this.localize('No rules'), 'none'],
2208 [this.localize('Rules will appear between rows only'), 'rows'],
2209 [this.localize('Rules will appear between columns only'), 'cols'],
2210 [this.localize('Rules will appear between all rows and columns'), 'all']
2211 ],
2212 element && element.rules ? element.rules : 'not set'
2213 );
2214 }
2215
2216 return $fieldset;
2217 },
2218 /**
2219 * onChange handler: enable/disable other fields of the same fieldset
2220 *
2221 * @param {Event} e
2222 */
2223 setBorderFieldsDisabled: function (e) {
2224 var $me = $(e.currentTarget),
2225 value = $me.val(),
2226 $parent = $me.closest('fieldset');
2227
2228 $parent.find(':input').each(function() {
2229 var $field = $(this),
2230 name = $field.attr('name');
2231
2232 if (name === 'f_st_borderStyle' || name === 'f_rules') {
2233 return true;
2234 } else {
2235 if (value === 'none') {
2236 if ($field.attr('type') === 'number') {
2237 $field.val('0');
2238 } else if ($field.hasClass('t3js-color-input')) {
2239 $field.val('');
2240 } else {
2241 $field.val('not set');
2242 }
2243
2244 $field.prop('disabled', true);
2245 } else {
2246 $field.prop('disabled', false);
2247 }
2248 }
2249 });
2250 },
2251 /**
2252 * This function builds the configuration object for the Colors fieldset
2253 *
2254 * @param {Object} element The element being edited, if any
2255 * @return {Object} The fieldset configuration object
2256 */
2257 buildColorsFieldsetConfig: function (element) {
2258 var $fieldset = $('<fieldset />'),
2259 $textColorInput = $('<input />', {name: 'f_st_color', 'class': 'form-control t3js-color-input', value: Color.colorToHex(element && element.style.color ? element.style.color : ((this.properties.color && this.properties.color.defaultValue) ? this.properties.color.defaultValue : ''))}),
2260 $backgroundColorInput = $('<input />', {name: 'f_st_backgroundColor', 'class': 'form-control t3js-color-input', value: Color.colorToHex(element && element.style.backgroundColor ? element.style.backgroundColor : ((this.properties.color && this.properties.backgroundColor.defaultValue) ? this.properties.backgroundColor.defaultValue : ''))});
2261
2262 $fieldset.append(
2263 $('<h4 />', {'class': 'form-section-headline'}).text(this.localize('Background and colors'))
2264 );
2265
2266 require(['TYPO3/CMS/Core/Contrib/jquery.minicolors'], function () {
2267 $textColorInput.minicolors({
2268 theme: 'bootstrap',
2269 format: 'hex',
2270 position: 'bottom left'
2271 });
2272 $backgroundColorInput.minicolors({
2273 theme: 'bootstrap',
2274 format: 'hex',
2275 position: 'bottom left'
2276 });
2277 });
2278 $fieldset.append(
2279 $('<div />', {'class': 'form-group'}).append(
2280 $('<label />', {'class': 'col-sm-2'}).html(this.getHelpTip('textColor', 'FG Color:')),
2281 $('<div />', {'class': 'col-sm-10'}).append($textColorInput)
2282 ),
2283 $('<div />', {'class': 'form-group'}).append(
2284 $('<label />', {'class': 'col-sm-2'}).html(this.getHelpTip('backgroundColor', 'Background:')),
2285 $('<div />', {'class': 'col-sm-10'}).append($backgroundColorInput)
2286 ),
2287 $('<div />', {'class': 'form-group'}).append(
2288 $('<label />', {'class': 'col-sm-2'}).html(this.getHelpTip('backgroundImage', 'Image URL:')),
2289 $('<div />', {'class': 'col-sm-10'}).append(
2290 $('<input />', {name: 'f_st_backgroundImage', 'class': 'form-control', value: element && element.style.backgroundImage.match(/url\(\s*(.*?)\s*\)/) ? RegExp.$1 : ''})
2291 )
2292 )
2293 );
2294
2295 return $fieldset;
2296 },
2297 /**
2298 * This function builds the configuration object for the Cell Type fieldset
2299 *
2300 * @param {Object} element: the element being edited, if any
2301 * @param {Boolean} column: true if the element is a column, false if the element is a cell
2302 * @return {Object} the fieldset configuration object
2303 */
2304 buildCellTypeFieldsetConfig: function (element, column) {
2305 var $fieldset = $('<fieldset />'),
2306 self = this,
2307 selected = element.nodeName.toLowerCase() + element.scope.toLowerCase(),
2308 data;
2309 if (column) {
2310 data = [
2311 [this.localize('Data cells'), 'td'],
2312 [this.localize('Headers for rows'), 'throw'],
2313 [this.localize('Headers for row groups'), 'throwgroup']
2314 ];
2315 } else {
2316 data = [
2317 [this.localize('Normal'), 'td'],
2318 [this.localize('Header for column'), 'thcol'],
2319 [this.localize('Header for row'), 'throw'],
2320 [this.localize('Header for row group'), 'throwgroup']
2321 ];
2322 }
2323
2324 /**
2325 * onChange handler: reset the CSS class dropdown and show/hide abbr field when the cell type changes
2326 *
2327 * @param {Event} e
2328 * @param {Object} record The selected record
2329 */
2330 function cellTypeChange(e) {
2331 var $me = $(e.currentTarget),
2332 value = $me.val();
2333
2334 var styleCombo = self.dialog.find('[name="f_class"]')[0];
2335 if (styleCombo) {
2336 self.setStyleOptions(styleCombo, element, value.substring(0,2));
2337 }
2338 // abbr field present only for single cell, not for column
2339 var abbrField = self.dialog.find('[name="f_cell_abbr"]');
2340 if (abbrField) {
2341 abbrField.closest('.form-group').toggle(value !== 'td');
2342 }
2343 }
2344
2345 $fieldset.append(
2346 $('<h4 />', {'class': 'form-section-headline'}).text(this.localize(column ? 'Type of cells' : 'Cell Type and Scope'))
2347 );
2348
2349 $fieldset = this.attachSelectMarkup(
2350 $fieldset,
2351 column ? this.getHelpTip('columnCellsType', 'Type of cells of the column') : this.getHelpTip('cellType', 'Type of cell'),
2352 'f_cell_type',
2353 data,
2354 (column && selected == 'thcol') ? 'td' : selected,
2355 $.proxy(cellTypeChange, this)
2356 );
2357
2358 if (!column) {
2359 $fieldset.append(
2360 $('<div />', {'class': 'form-group'}).append(
2361 $('<label />', {'class': 'col-sm-2'}).html(this.getHelpTip('cellAbbreviation', 'Abbreviation')),
2362 $('<div />', {'class': 'col-sm-10'}).append(
2363 $('<input />', {name: 'f_cell_abbr', 'class': 'form-control', value: element.abbr})
2364 )
2365 ).toggle(selected !== 'td')
2366 );
2367 }
2368
2369 return $fieldset;
2370 },
2371 /**
2372 * This function builds the configuration object for the Row Group fieldset
2373 *
2374 * @param {Object} element: the row being edited, if any
2375 * @return {Object} the fieldset configuration object
2376 */
2377 buildRowGroupFieldsetConfig: function (element) {
2378 var $fieldset = $('<fieldset />'),
2379 current = element.parentNode.nodeName.toLowerCase();
2380
2381 $fieldset.append(
2382 $('<h4 />', {'class': 'form-section-headline'}).text(this.localize('Row group'))
2383 );
2384
2385 /**
2386 * onChange handler: show/hide cell conversion checkbox with appropriate label
2387 *
2388 * @param {Event} e
2389 */
2390 function displayCheckbox(e) {
2391 var $field = $(e.currentTarget),
2392 $checkbox = $field.closest('fieldset').find('[name="f_convertCells"]'),
2393 $group = $checkbox.closest('.form-group'),
2394 value = $field.val();
2395
2396 if (current !== value && (current === 'thead' || value === 'thead')) {
2397 $checkbox.closest('label').find('span').text(
2398 value === 'thead' ? this.localize('Make cells header cells') : this.localize('Make cells data cells')
2399 );
2400 $group.show();
2401 $checkbox.prop('checked', true);
2402 } else {
2403 $group.hide();
2404 $checkbox.prop('checked', false);
2405 }
2406 }
2407
2408 $fieldset = this.attachSelectMarkup(
2409 $fieldset,
2410 this.getHelpTip('rowGroup', 'Row group:'),
2411 'f_rowgroup',
2412 [
2413 [this.localize('Table body'), 'tbody'],
2414 [this.localize('Table header'), 'thead'],
2415 [this.localize('Table footer'), 'tfoot']
2416 ],
2417 current,
2418 $.proxy(displayCheckbox, this)
2419 );
2420
2421 // Cell conversion checkbox
2422 $fieldset.append(
2423 $('<div />', {'class': 'form-group col-sm-12'}).append(
2424 $('<div />', {'class': 'checkbox'}).append(
2425 $('<label />').append(
2426 $('<span />').text(this.localize('Make cells header cells'))
2427 ).prepend(
2428 $('<input />', {type: 'checkbox', name: 'f_convertCells'})
2429 )
2430 )
2431 ).toggle(false)
2432 );
2433
2434 return $fieldset;
2435 },
2436
2437 /**
2438 * This function gets called by the editor key map when a key was pressed.
2439 * It will process the enter key for IE and Opera when buttons.table.disableEnterParagraphs is set in the editor configuration
2440 *
2441 * @param {Event} event The jQuery event object (keydown)
2442 * @return {Boolean} False, if the event was taken care of
2443 */
2444 onKey: function (event) {
2445 this.editor.getSelection().createRange();
2446 var parentElement = this.editor.getSelection().getParentElement();
2447 while (parentElement && !Dom.isBlockElement(parentElement)) {
2448 parentElement = parentElement.parentNode;
2449 }
2450 if (/^(td|th)$/i.test(parentElement.nodeName)) {
2451 var brNode = this.editor.document.createElement('br');
2452 this.editor.getSelection().insertNode(brNode);
2453 if (brNode.nextSibling) {
2454 this.editor.getSelection().selectNodeContents(brNode.nextSibling, true);
2455 } else {
2456 this.editor.getSelection().selectNodeContents(brNode, false);
2457 }
2458 Event.stopEvent(event);
2459 return false;
2460 }
2461 return true;
2462 }
2463 });
2464
2465 return TableOperations;
2466 });