/*
* Copyright 2017 Anyware Services
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* This class is a singleton to handle tables in inline editor
* @private
*/
Ext.define('Ametys.plugins.cms.editor.Tables', {
singleton: true,
/**
* Insert a table in the current editor of size determined
* @param {Number} cols The number of cells of the new table
* @param {Number} rows The number of rows of the new table
*/
createTable: function(cols, rows)
{
var html = this._createHTMLForTable(cols, rows, tinyMCE.activeEditor.schema.elements.table ? tinyMCE.activeEditor.schema.elements.table.attributes.class.defaultValue : "");
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.execCommand('mceBeginUndoLevel');
tinyMCE.insertHTMLAtRoot(html);
tinymce.each(tinyMCE.activeEditor.dom.select('table[_mce_new]'), function(node) {
var td = tinyMCE.activeEditor.dom.select('td', node);
tinyMCE.activeEditor.selection.select(td[0], true);
tinyMCE.activeEditor.selection.collapse();
tinyMCE.activeEditor.dom.setAttrib(node, '_mce_new', '');
tinyMCE.activeEditor.dom.setAttrib(node, '_mce_ribbon_select', '1');
});
tinyMCE.activeEditor.addVisual();
tinyMCE.activeEditor.execCommand('mceEndUndoLevel');
tinyMCE.activeEditor.focus();
},
/**
* Create the HTML markup for a table.
* @param {Number} cols The number of columns
* @param {Number} rows The number of rows
* @param {String} classname class attribute value
* @param {String} style The style attribute value. Can be null.
* @param {Boolean} isMarker a marker attribute is added if true.
* @return {String} The corresponding HTML markup
* @private
*/
_createHTMLForTable: function(cols, rows, classname, style, isMarker)
{
isMarker = isMarker == true;
if (style == null)
{
style = "width:100%";
}
var html = '<table';
html += ' class="' + classname + '"';
if (style)
{
html += ' style="' + style + '"';
}
if (isMarker)
{
html += ' marker="marker"';
}
html += ' _mce_new="1"';
html += '>';
for (var i = 0; i < rows; i++)
{
html += "<tr>";
for (var j = 0; j < cols; j++)
{
html += '<td>' + this._createInnerHTML() + '</td>';
}
html += "</tr>";
}
html += "</table>";
return html;
},
/**
* Create the HTML markup to put inside a cell.
* @param {String} s the text (or html) to put in the cell.
* @return {String} The corresponding HTML markup
*/
_createInnerHTML: function(s)
{
s = s || ' ';
if (!tinymce.isIE)
{
return '<p>' + s + '<br _mce_bogus="1"/></p>';
}
else
{
return '<p>' + s + '</p>';
}
},
// ---------------------------------------- //
// CAPTION //
// ---------------------------------------- //
/**
* Set 'caption' element to current <table> node when pressing ENTER or ESC key.
* @param {Ext.form.field.Text} input The input text
* @param {Ext.event.Event} e The event object
*/
setCaptionOnSpecialKey: function (input, e)
{
if (e.getKey() == e.ENTER)
{
e.preventDefault();
e.stopPropagation();
this.setCaption(input.getValue());
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
}
else if (e.getKey() == e.ESC)
{
e.preventDefault();
e.stopPropagation();
input.setValue(this._getCaption());
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
}
},
/**
* Set table caption when the input loses the focus
* @param {Ext.form.field.Text} input The input text
*/
setCaptionOnBlur: function (input)
{
if (input.getValue() != this._getCaption(this._currentTable))
{
this.setCaption(input.getValue());
}
},
/**
* Set 'caption' element to current <table> node
* @param {String} value The value
* @return {Boolean} Return the success of operation
*/
setCaption: function(value)
{
var elt = this._currentTable;
if (elt != null)
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
var capEl = tinyMCE.activeEditor.dom.select('caption', elt)[0];
if (value == "")
{
if (capEl)
{
capEl.parentNode.removeChild(capEl);
}
}
else
{
var escapedValue = value.replace(/</g, "<").replace(/>/g, ">");
if (!capEl)
{
capEl = elt.ownerDocument.createElement('caption');
capEl.className = "mceNonEditable";
capEl.setAttribute("data-mce-contenteditable", "false");
elt.insertBefore(capEl, elt.firstChild);
}
capEl.innerHTML = escapedValue;
}
tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
return true;
}
return false;
},
/**
* Get the caption of current table
* @param {HTMLElement} node The <table> node. Can be null to retrieve <table> node from cursor current position
* @return {String} The caption text
* @param {tinymce.Editor} [editor] The wrapper tinymce editor object
* @private
*/
_getCaption: function(node, editor)
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
editor = editor || tinyMCE.activeEditor;
var capEl = editor.dom.select('caption', node)[0];
if (capEl)
{
return capEl.innerHTML.replace(/</g, "<").replace(/>/g, ">");
}
else
{
return "";
}
},
/**
* Enable/disable controller and set input value according the selected table
* @param {Ametys.ribbon.element.ui.FieldController} controller The controller
* @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
* @param {HTMLElement} node The current selected node. Can be null.
*/
captionListener: function (controller, field, node)
{
this._currentTable = node;
if (field == null || node == null || field.getEditor() == null || field.getEditor().dom == null || field.getEditor().selection == null || field.getEditor().selection.getStart() == null)
{
controller.setValue('');
}
else
{
var caption = this._getCaption(node, field.getEditor());
controller.setValue(caption);
}
},
// ---------------------------------------- //
// SUMMARY //
// ---------------------------------------- //
/**
* Set 'summary' attribute to current <table> node when pressing ENTER or ESC key.
* @param {Ext.form.field.Text} input The input text
* @param {Ext.event.Event} e The event object
*/
setSummaryOnSpecialKey: function (input, e)
{
if (e.getKey() == e.ENTER)
{
e.preventDefault();
e.stopPropagation();
this.setSummary(input.getValue());
tinyMCE.activeEditor.focus();
}
else if (e.getKey() == e.ESC)
{
e.preventDefault();
e.stopPropagation();
input.setValue(this._getSummary());
tinyMCE.activeEditor.focus();
}
},
/**
* Set table caption when the input loses the focus
* @param {Ext.form.field.Text} input The input text
*/
setSummaryOnBlur: function (input)
{
if (input.getValue() != this._getSummary(this._currentTable))
{
this.setSummary(input.getValue());
}
},
/**
* Set 'summary' attribute to current <table> node
* @param {String} value The value
*/
setSummary: function (value)
{
if (this._currentTable != null)
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.dom.setAttrib(this._currentTable, 'summary', value);
tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
}
},
/**
* Get the summary of current table
* @param {HTMLElement} node The <table> node. Can be null to retrieve <table> node from cursor current position
* @return {String} The summary text
* @private
*/
_getSummary: function(node)
{
return node.summary || "";
},
/**
* Enable/disable controller and set input value according the selected table
* @param {Ametys.ribbon.element.ui.FieldController} controller The controller
* @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
* @param {HTMLElement} node The current selected node. Can be null.
*/
summaryListener: function (controller, field, node)
{
this._currentTable = node;
if (node)
{
var summary = this._getSummary(node);
controller.setValue(summary);
}
else
{
controller.setValue('');
}
},
// ---------------------------------------- //
// ALIGN //
// ---------------------------------------- //
/**
* Set the cells alignment
* @param {String} align the horizontal align
* @param {String} valign the vertical align
*/
alignTable: function(align, valign)
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
var cells = [];
// First we look for every data-mce-selected table cell
var table = tinyMCE.activeEditor.dom.getParent(tinyMCE.activeEditor.selection.getNode(), function (node) { return /^table$/i.test(node.nodeName); });
var tdSelected = tinyMCE.activeEditor.dom.select("td[data-mce-selected=\"1\"]", table);
for (var i = 0; i < tdSelected.length; i++)
{
cells.push(tdSelected[i]);
}
var thSelected = tinyMCE.activeEditor.dom.select("th[data-mce-selected=\"1\"]", table);
for (var i = 0; i < thSelected.length; i++)
{
cells.push(thSelected[i]);
}
// If we do not find any, that's because the carret is only inside a cell
if (cells.length == 0)
{
var c = tinyMCE.activeEditor.dom.getParent(tinyMCE.activeEditor.selection.getNode(), function (node) { return /^td|th$/i.test(node.nodeName); });
if (c != null)
{
cells.push(c);
}
}
if (cells.length > 0)
{
tinyMCE.activeEditor.execCommand('mceBeginUndoLevel');
for (var i = 0; i < cells.length; i++)
{
var cell = cells[i];
if (align != null)
{
cell.style.textAlign = align;
cell.removeAttribute("data-mce-style");
// moreover we want to remove text align on chil nodes
for (var j = 0; j < cell.childNodes.length; j++)
{
var elt = cell.childNodes[j];
if (elt.style)
{
elt.style.textAlign = "";
}
}
}
if (valign != null)
{
cell.style.verticalAlign = valign;
cell.removeAttribute("data-mce-style");
}
}
tinyMCE.activeEditor.execCommand('mceEndUndoLevel');
}
},
/**
* Enable/disable controller and set input value according the selected table
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
* @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
* @param {HTMLElement} node The current selected node. Can be null.
*/
alignControllerListener: function(controller, field, node)
{
var align = controller.getInitialConfig ('align');
var valign = controller.getInitialConfig ('valign');
if (node)
{
var toggleFunction = Ext.bind(controller.toggle, controller);
var editor = field.getEditor();
this.alignListener(node, editor, toggleFunction, align, valign);
}
},
/**
* Enable/disable controller and set input value according the selected table
* @param {HTMLElement} node The current selected node.
* @param {Object} editor tinyMCE editor
* @param {Function} toggleFunction function that will be called with true/false to select/unselect button
* @param {String} align horizontal alignment
* @param {String} valign vertical alignment
*/
alignListener: function(node, editor, toggleFunction, align, valign)
{
this._currentCell = node;
if (node)
{
var onVAlignValue = valign != null && editor.dom.getStyle(node, "vertical-align", true) == valign;
var onAlignValue = align != null && editor.dom.getStyle(node, "text-align", true) == align;
toggleFunction(onVAlignValue && onAlignValue);
}
else
{
toggleFunction(false);
}
},
// ---------------------------------------- //
// FLOAT //
// ---------------------------------------- //
/**
* Enable/disable and toggle/untoggle controller according the table alignment
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
* @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
* @param {HTMLElement} node The current selected node. Can be null.
*/
floatListener: function (controller, field, node)
{
this._currentNode = node;
if (node)
{
if (node.style.width == '100%')
{
controller.setDisabled(true);
}
controller.toggle(field.getEditor().dom.hasClass(node, controller.getInitialConfig('css-class')));
}
else
{
controller.toggle(false);
}
},
/**
* Align table on the text right
* @param {Boolean} always true in order to take the table marker into account, false otherwise
*/
applyFloatRight: function(always)
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
var node = this.getTable(this._currentNode, always);
if (node != null)
{
tinyMCE.activeEditor.execCommand('mceBeginUndoLevel');
this._applyFloatRight(node);
tinyMCE.activeEditor.execCommand('mceEndUndoLevel');
}
},
/**
* Align table on the text right
* @param {HTMLElement} node The table node
* @private
*/
_applyFloatRight: function(node)
{
// tinyMCE issue : mceItemTable has to be the last class
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
var hasMceItemTableClass = tinyMCE.activeEditor.dom.hasClass(node, 'mceItemTable');
if (hasMceItemTableClass)
{
tinyMCE.activeEditor.dom.removeClass(node, 'mceItemTable');
}
tinyMCE.activeEditor.dom.addClass(node, 'floatright');
tinyMCE.activeEditor.dom.removeClass(node, 'floatleft');
if (hasMceItemTableClass)
{
tinyMCE.activeEditor.dom.addClass(node, 'mceItemTable');
}
},
/**
* Align table on the text left
* @param {Boolean} always true in order to take the table marker into account, false otherwise
*/
applyFloatLeft: function (always)
{
tinyMCE.activeEditor.focus();
var node = this.getTable(this._currentNode, always);
if (node != null)
{
tinyMCE.activeEditor.execCommand('mceBeginUndoLevel');
this._applyFloatLeft(node);
tinyMCE.activeEditor.execCommand('mceEndUndoLevel');
}
},
/**
* Align table on the text left
* @param {HTMLElement} node The table node
* @private
*/
_applyFloatLeft: function(node)
{
// tinyMCE issue : mceItemTable has to be the last class
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
var hasMceItemTableClass = tinyMCE.activeEditor.dom.hasClass(node, 'mceItemTable');
if (hasMceItemTableClass)
{
tinyMCE.activeEditor.dom.removeClass(node, 'mceItemTable');
}
tinyMCE.activeEditor.dom.addClass(node, 'floatleft');
tinyMCE.activeEditor.dom.removeClass(node, 'floatright');
if (hasMceItemTableClass)
{
tinyMCE.activeEditor.dom.addClass(node, 'mceItemTable');
}
},
/**
* Align table with the text
* @param {Boolean} always true in order to take the table marker into account, false otherwise
*/
applyNoFloat: function (always)
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
var node = this.getTable(this._currentNode, always);
if (node != null)
{
tinyMCE.activeEditor.execCommand('mceBeginUndoLevel');
this._applyNoFloat(node);
tinyMCE.activeEditor.execCommand('mceEndUndoLevel');
}
},
/**
* @private
* Align table with the text
* @param {HTMLElement} node The table node
*/
_applyNoFloat: function (node)
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.dom.removeClass(node, 'floatleft');
tinyMCE.activeEditor.dom.removeClass(node, 'floatright');
},
/**
* Enable/disable and toggle/untoggle controller according the table alignment
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
* @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
* @param {HTMLElement} node The current selected node. Can be null.
*/
noFloatListener: function (controller, field, node)
{
if (node)
{
if (node.style.width == '100%')
{
controller.setDisabled(true);
}
controller.toggle(!field.getEditor().dom.hasClass(node, 'floatleft') && !field.getEditor().dom.hasClass(node, 'floatright'));
}
else
{
controller.toggle(false);
}
},
// ---------------------------------------- //
// ROW HEADER //
// ---------------------------------------- //
/**
* This method toggle row header of the currently selected table. <br/>
* If there is row headers they will be removed, else 1 row header will be set (or 2 rows if 1 is not possible due to merge, or 3 rows...)
*/
toggleRowHeader: function ()
{
var me = this;
function getMinimalHeaderSize(node)
{
// check if merge is authorized
var i = 1;
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
var cellsSelected = tinyMCE.activeEditor.dom.select("[data-mce-selected=\"1\"]", node);
if (cellsSelected.length > 0)
{
i = cellsSelected[cellsSelected.length - 1].parentNode.rowIndex + 1;
}
else
{
var tr = tinyMCE.activeEditor.dom.getParent(tinyMCE.activeEditor.selection.getNode(), 'tr');
if (tr != null)
{
i = tr.rowIndex + 1;
}
}
for (; i < node.rows.length; i++)
{
if (me._canHaveRowHeader(node, i))
{
return i;
}
}
return node.rows.length;
}
tinyMCE.activeEditor.focus();
// Find table
var node = this.getTable(tinyMCE.activeEditor.selection.getNode());
if (node != null)
{
var rowHeaders = this._countRowHeader(node);
this._setHeaderRows(rowHeaders > 0 ? 0 : getMinimalHeaderSize(node));
}
},
/**
* This method will change the count of header rows of the currently selected table.
* @param {Number} n The number of rows to convert
* @private
*/
_setHeaderRows: function(n)
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
// Find table
var node = this.getTable(tinyMCE.activeEditor.selection.getNode());
if (node != null)
{
var bm = tinyMCE.activeEditor.selection.getBookmark();
tinyMCE.activeEditor.execCommand('mceBeginUndoLevel');
this._removeHeaderRows(node);
var grid = this._getTableGrid(node);
for (var i = 0; i < n; i++)
{
for (var j = 0; j < grid[i].length; j++)
{
var cell = grid[i][j];
if (/^td$/i.test(cell.nodeName) && cell.parentNode != null)
{
this._replaceCellWith(cell, 'th');
}
}
}
tinyMCE.activeEditor.execCommand('mceEndUndoLevel');
tinyMCE.activeEditor.selection.moveToBookmark(bm);
}
},
/**
* This method remove all header rows.
* If there are columns headers they will be preserved.
* @param {HTMLElement} node The table node
* @private
*/
_removeHeaderRows: function(node)
{
// Find columns of the first row
var rowHeaderSize = this._countRowHeader(node);
var colHeaderSize = this._countColHeader(node);
this._removeHeader(node);
if (colHeaderSize > 0 && node.rows.length > rowHeaderSize)
{
this._setHeaderCols(colHeaderSize);
}
},
/**
* This method will replace all existing header cells by normal cells
* @param {HTMLElement} elt The table node
* @private
*/
_removeHeader: function(elt)
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
var cells = tinyMCE.activeEditor.dom.select("tr th", elt);
Ext.Array.each (cells,
function(cell)
{
this._replaceCellWith(cell, 'td');
},
this
);
},
/**
* This method determine if the given table can have a certain amount of header rows
* @param {HTMLElement} elt The table to check
* @param {Number} size The number of rows to test
* @return {boolean} true if the 'table' can have 'size' header rows
* @private
*/
_canHaveRowHeader: function (elt, size)
{
if (size == 0 || size == elt.rows.length)
{
return true;
}
else if (size > elt.rows.length)
{
return false;
}
else
{
var grid = this._getTableGrid(elt);
for (var i = 0; i < grid[0].length; i++)
{
if (grid[size - 1][i] == grid[size][i])
{
return false;
}
}
return true;
}
},
/**
* This method can be used as a button listener to handle its state depending on the header row count
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
* @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
* @param {HTMLElement} node The current selected node. Can be null.
*/
rowHeaderListener : function (controller, field, node)
{
if (field != null && node != null && field.getEditor() != null)
{
this._rowHeaderListener(controller, 0, false, field.getEditor());
}
},
/**
* This listener can be user as a gallery button lister to handle its state when there are 0 header rows
* @param {Ametys.ribbon.element.ui.ButtonController} controller The gallery button to handler
*/
row0HeaderListener: function (controller)
{
this._rowHeaderListener(controller, 0, true);
},
/**
* This listener can be user as a gallery button lister to handle its state when there are 1 header rows
* @param {Ametys.ribbon.element.ui.ButtonController} controller The gallery button to handler
*/
row1HeaderListener : function (controller)
{
this._rowHeaderListener(controller, 1, true);
},
/**
* This listener can be user as a gallery button lister to handle its state when there are 2 header rows
* @param {Ametys.ribbon.element.ui.ButtonController} controller The gallery button to handler
*/
row2HeaderListener : function (controller)
{
this._rowHeaderListener(controller, 2, true);
},
/**
* This listener can be user as a gallery button lister to handle its state when there are 3 header rows
* @param {Ametys.ribbon.element.ui.ButtonController} controller The gallery button to handler
*/
row3HeaderListener : function (controller)
{
this._rowHeaderListener(controller, 3, true);
},
/**
* This listener can be user as a gallery button lister to handle its state when there are 4 header rows or more
* @param {Ametys.ribbon.element.ui.ButtonController} controller The gallery button to handler
*/
rowXHeaderListener : function (controller)
{
this._rowHeaderListener(controller, 3, false);
},
/**
* This method will enable/disable/toggle the given button (or group of buttons) depending if the currenlty selected table count of header rows
* @param {Ext.button.Button/Ametys.ribbon.element.RibbonUIController} controller The button to change
* @param {Number} count The number of rows to check
* @param {Boolean} isCount If true, the count parameter will be considered as an exact amount. If false, the button will be toggled event if the number of rows is more than count.
* @param {tinymce.Editor} [editor] The wrapper tinymce editor object
* @private
*/
_rowHeaderListener: function (controller, count, isCount, editor)
{
var canhave = false;
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
editor = editor || tinyMCE.activeEditor;
var tableNode = this.getTable(editor.selection.getNode());
var toggleState = false;
if (tableNode != null)
{
toggleState = isCount ? this._countRowHeader(tableNode) == count : this._countRowHeader(tableNode) > count;
canhave = isCount ? this._canHaveRowHeader(tableNode, count) : tableNode.rows.length > count;
}
controller.setDisabled(!canhave);
controller.toggle(toggleState);
},
// ---------------------------------------- //
// COL HEADER //
// ---------------------------------------- //
/**
* This method toggle row header of the currently selected table. <br/>
* If there is row headers they will be removed, else 1 row header will be set (or 2 rows if 1 is not possible due to merge, or 3 rows...)
*/
toggleColHeader: function ()
{
var me = this;
function getMinimalHeaderSize(node)
{
var i = 1;
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
var cellsSelected = tinyMCE.activeEditor.dom.select("[data-mce-selected=\"1\"]", node);
if (cellsSelected.length > 0)
{
i = me._getCellIndex(cellsSelected[cellsSelected.length - 1]) + 1;
}
else
{
var cell = tinyMCE.activeEditor.dom.getParent(tinyMCE.activeEditor.selection.getNode(), function (s) { return /^td|th$/i.test(s.nodeName); } );
if (cell != null)
{
i = me._getCellIndex(cell) + 1;
}
}
var max = me._countCells(node);
for (; i < max; i++)
{
if (me._canHaveColHeader(node, i))
{
return i;
}
}
return max;
}
tinyMCE.activeEditor.focus();
// Find table
var node = this.getTable(tinyMCE.activeEditor.selection.getNode());
if (node != null)
{
var colHeaders = this._countColHeader(node);
this._setHeaderCols(colHeaders > 0 ? 0 : getMinimalHeaderSize(node));
}
},
/**
* This method will change the count of header columns of the currently selected table.
* @param {Number} n The number of columns to convert
* @private
*/
_setHeaderCols: function(n)
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
// Find table
var node = this.getTable(tinyMCE.activeEditor.selection.getNode());
if (node != null)
{
var bm = tinyMCE.activeEditor.selection.getBookmark();
tinyMCE.activeEditor.execCommand('mceBeginUndoLevel');
this._removeHeaderCols(node);
var grid = this._getTableGrid(node);
for (var i = 0; i < grid.length; i++)
{
for (var j = 0; j < n; j++)
{
var cell = grid[i][j];
if (/^td$/i.test(cell.nodeName) && cell.parentNode != null)
{
this._replaceCellWith(cell, 'th');
}
}
}
tinyMCE.activeEditor.execCommand('mceEndUndoLevel');
tinyMCE.activeEditor.selection.moveToBookmark(bm);
}
},
/**
* This method remove all header cols.
* If there are columns headers they will be preserved.
* @param {HTMLElement} elt The table node
* @private
*/
_removeHeaderCols: function (elt)
{
// Find columns of the first col
var rowHeaderSize = this._countRowHeader(elt);
var colHeaderSize = this._countColHeader(elt);
var nbcells = this._countCells(elt);
this._removeHeader(elt);
if (rowHeaderSize > 0 && nbcells > colHeaderSize)
{
this._setHeaderRows(rowHeaderSize);
}
},
/**
* This method determine if the given table can have a certain amount of header cols
* @param {HTMLElement} elt The table to check
* @param {Number} size The number of cols to test
* @return {Boolean} true if the 'table' can have 'size' header cols
* @private
*/
_canHaveColHeader: function(elt, size)
{
var nbcells = this._countCells(elt);
if (size == 0 || size == nbcells)
{
return true;
}
else if (size > nbcells)
{
return false;
}
else
{
var grid = this._getTableGrid(elt);
for (var i = 0; i < grid.length; i++)
{
if (grid[i][size - 1] == grid[i][size])
{
return false;
}
}
return true;
}
},
/**
* This method can be used as a button listener to handle its state depending on the header col count
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
* @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
* @param {HTMLElement} node The current selected node. Can be null.
*/
colHeaderListener: function (controller, field, node)
{
if (node)
{
this._colHeaderListener(controller, 0, false);
}
},
/**
* This listener can be user as a gallery button lister to handle its state when there are 0 header cols
* @param {Ametys.ribbon.element.ui.ButtonController} controller The gallery button to handler
*/
col0HeaderListener: function (controller)
{
this._colHeaderListener(controller, 0, true);
},
/**
* This listener can be user as a gallery button lister to handle its state when there are 1 header cols
* @param {Ametys.ribbon.element.ui.ButtonController} controller The gallery button to handler
*/
col1HeaderListener: function (controller)
{
this._colHeaderListener(controller, 1, true);
},
/**
* This listener can be user as a gallery button lister to handle its state when there are 2 header cols
* @param {Ametys.ribbon.element.ui.ButtonController} controller The gallery button to handler
*/
col2HeaderListener: function (controller)
{
this._colHeaderListener(controller, 2, true);
},
/**
* This listener can be user as a gallery button lister to handle its state when there are 3 header cols
* @param {Ametys.ribbon.element.ui.ButtonController} controller The gallery button to handler
*/
col3HeaderListener: function (controller)
{
this._colHeaderListener(controller, 3, true);
},
/**
* This listener can be user as a gallery button lister to handle its state when there are 4 header cols or more
* @param {Ametys.ribbon.element.ui.ButtonController} controller The gallery button to handler
*/
colXHeaderListener: function (controller)
{
this._colHeaderListener(controller, 3, false);
},
/**
* This method will enable/disable/toggle the given button (or group of buttons) depending if the currenlty selected table count of header cols
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
* @param {Number} count The number of cols to check
* @param {Boolean} isCount If true, the count parameter will be considered as an exact amount. If false, the button will be toggled event if the number of cols is more than count.
* @private
*/
_colHeaderListener: function (controller, count, isCount)
{
var canhave = false;
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
var tableNode = this.getTable(tinyMCE.activeEditor.selection.getNode());
var toggleState = false;
if (tableNode != null)
{
var colHeader = this._countColHeader(tableNode);
toggleState = isCount ? colHeader == count : this._countColHeader(tableNode) > count;
canhave = isCount ? this._canHaveColHeader(tableNode, count) : this._countCells(tableNode) > count;
}
controller.setDisabled(!canhave);
controller.toggle(toggleState);
},
// ---------------------------------------- //
// INSERT ROW //
// ---------------------------------------- //
/**
* Insert a new row before the current row
* @param {Boolean} always true in order to take the table marker into account, false otherwise
* @param {HTMLElement} table The table node element. Can be null. If null the table is get from current selected cell.
*/
insertRowUp: function (always, table)
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
// mceTableInsertRowBefore does not take care about th => emulation
var tableNode = table || this.getTable(this._currentCell, always);
if (tableNode != null)
{
tinyMCE.activeEditor.execCommand('mceBeginUndoLevel');
var countColHeader = this._countColHeader(tableNode);
tinyMCE.activeEditor.execCommand('mceTableInsertRowBefore', false, 1);
if (countColHeader > 0)
{
this._setHeaderCols(countColHeader);
}
this._repairTable(tableNode);
tinyMCE.activeEditor.execCommand('mceEndUndoLevel');
}
},
/**
* Enable/disable controller and set input value according the selected table
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
* @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
* @param {HTMLElement} node The current selected node. Can be null.
*/
currentCellListener: function(controller, field, node)
{
this._currentCell = node;
},
/**
* Insert a new row after the current row
* @param {Boolean} always true in order to take the table marker into account, false otherwise
* @param {HTMLElement} table The table node element. Can be null. If null the table is get from current selected cell.
*/
insertRowDown: function (always, table)
{
tinyMCE.activeEditor.focus();
// mceTableInsertRowBefore does not take care about th => emulation
var tableNode = table || this.getTable(this._currentCell, always);
if (tableNode != null)
{
tinyMCE.activeEditor.execCommand('mceBeginUndoLevel');
var countColHeader = this._countColHeader(tableNode);
tinyMCE.activeEditor.execCommand('mceTableInsertRowAfter', false, 1);
if (countColHeader > 0)
{
this._setHeaderCols(countColHeader);
}
this._repairTable(tableNode);
tinyMCE.activeEditor.execCommand('mceEndUndoLevel');
}
},
// ---------------------------------------- //
// INSERT COLUMN //
// ---------------------------------------- //
/**
* Insert a new column before the current column
* @param {Boolean} always true in order to take the table marker into account, false otherwise
*/
insertColumnRight: function (always)
{
tinyMCE.activeEditor.focus();
var tableNode = this.getTable(this._currentCell, always);
tinyMCE.activeEditor.execCommand('mceTableInsertColAfter');
this._repairTable(tableNode);
},
/**
* Insert a new column after the current column
* @param {Boolean} always true in order to take the table marker into account, false otherwise
*/
insertColumnLeft: function (always)
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
var tableNode = this.getTable(this._currentCell, always);
tinyMCE.activeEditor.execCommand('mceTableInsertColBefore', false, 1);
this._repairTable(tableNode);
},
// ---------------------------------------- //
// DELETE //
// ---------------------------------------- //
/**
* Delete the table
*/
deleteTable: function ()
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.execCommand('mceTableDelete', null, null, { skip_focus: true });
tinyMCE.activeEditor.focus(); // We do not want to focus automatically, because it is too soon, and the selection would be the orphean TD/TH tag
},
/**
* Delete the selected row
*/
deleteRow: function ()
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.execCommand('mceTableDeleteRow', null, null, { skip_focus: true });
tinyMCE.activeEditor.focus(); // We do not want to focus automatically, because it is too soon, and the selection would be the orphean TD/TH tag
},
/**
* Delete the selected column
*/
deleteCol: function ()
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.execCommand('mceTableDeleteCol', null, null, { skip_focus: true });
tinyMCE.activeEditor.focus(); // We do not want to focus automatically, because it is too soon, and the selection would be the orphean TD/TH tag
},
// ---------------------------------------- //
// MERGE //
// ---------------------------------------- //
/**
* Merge the selected cell with the right cell
* @param {Boolean} always true in order to take the table marker into account, false otherwise
*/
mergeCells: function (always)
{
var args = [];
args["numcols"] = 1;
args["numrows"] = 1;
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
var parentTable = this.getTable(tinyMCE.activeEditor.selection.getStart(), always);
if (parentTable != null)
{
// check if merge is authorized
var cellsSelected = tinyMCE.activeEditor.dom.select("[data-mce-selected=\"1\"]", parentTable);
var x1 = this._getCellIndex(cellsSelected[0]);
var y1 = cellsSelected[0].parentNode.rowIndex;
var x2 = this._getCellIndex(cellsSelected[cellsSelected.length - 1]);
var y2 = cellsSelected[cellsSelected.length - 1].parentNode.rowIndex;
var rowHeaders = this._countRowHeader(parentTable);
var colHeaders = this._countColHeader(parentTable);
if (x1 < colHeaders && x2 >= colHeaders || y1 < rowHeaders && y2 >= rowHeaders)
{
Ametys.Msg.alert("{{i18n CONTENT_EDITION_TABLE_MERGE_CELL_FORBIDEN_TITLE}}", "{{i18n CONTENT_EDITION_TABLE_MERGE_CELL_FORBIDEN_TEXT}}");
return;
}
if (x1 != x2)
{
cellsSelected[0].style.width = '';
cellsSelected[0].removeAttribute("data-mce-style");
}
if (y1 != y2)
{
cellsSelected[0].style.height = '';
cellsSelected[0].removeAttribute("data-mce-style");
}
// do the merge
tinyMCE.activeEditor.execCommand('mceTableMergeCells', false, args);
}
},
/**
* Enable/disable controller according the selected table
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
* @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
* @param {HTMLElement} node The current selected node. Can be null.
*/
mergeCellsListener: function (controller, field, node)
{
if (field == null || node == null || field.getEditor() == null || field.getEditor().dom == null || field.getEditor().selection == null || field.getEditor().selection.getStart() == null)
{
controller.disable();
return;
}
var on = false;
var parentTable = this.getTable(field.getEditor().selection.getStart(), null, field.getEditor());
if (parentTable != null)
{
var tdSelected = field.getEditor().dom.select("td[data-mce-selected=\"1\"]", parentTable);
var thSelected = field.getEditor().dom.select("th[data-mce-selected=\"1\"]", parentTable);
on = (tdSelected.length > 1 && thSelected.length == 0)
|| (tdSelected.length == 0 && thSelected.length > 1);
}
if (!on)
{
controller.setDisabled(true);
}
},
// ---------------------------------------- //
// SPLIT //
// ---------------------------------------- //
/**
* Slip the selected cell in two cells
* @param {Boolean} always true in order to take the table marker into account, false otherwise
*/
splitCells: function (always)
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
var parentTable = this.getTable(tinyMCE.activeEditor.selection.getStart(), always);
var countColHeader = parentTable != null && this._countColHeader(parentTable);
var countRowHeader = parentTable != null && this._countRowHeader(parentTable);
tinyMCE.activeEditor.execCommand('mceBeginUndoLevel');
tinyMCE.activeEditor.execCommand('mceTableSplitCells');
this._setHeaderCols(countColHeader);
this._setHeaderRows(countRowHeader);
tinyMCE.activeEditor.execCommand('mceEndUndoLevel');
},
/**
* Enable/disable controller according the selected table
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
* @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
* @param {HTMLElement} node The current selected node. Can be null.
*/
splitCellsListener: function (controller, field, node)
{
if (field == null || node == null || node.getAttribute == null || field.getEditor() == null || field.getEditor().dom == null)
{
controller.disable();
return;
}
var on = false;
var cell = field.getEditor().dom.getParent(field.getEditor().selection.getNode(), function (node) { return /^td|th$/i.test(node.nodeName); });
if (cell != null)
{
var rowcolspan = this._getColRowSpan(cell);
var colspan = rowcolspan.colspan;
var rowspan = rowcolspan.rowspan;
on = ((!isNaN(colspan) && colspan != 1) || (!isNaN(rowspan) && rowspan != 1));
}
if (!on)
{
controller.setDisabled(true);
}
},
// ---------------------------------------- //
// COL SIZE //
// ---------------------------------------- //
/**
* Set the width of current column when pressing ENTER or ESC key.
* @param {Ext.form.field.Text} input The input text
* @param {Ext.event.Event} e The event object
*/
setColSizeOnSpecialKey: function (input, e)
{
if (e.getKey() == e.ENTER)
{
e.preventDefault();
e.stopPropagation();
this.setColSize(input.getValue());
tinyMCE.activeEditor.focus();
}
else if (e.getKey() == e.ESC)
{
e.preventDefault();
e.stopPropagation();
input.setValue(this._getCellWidth(this._currentCell));
tinyMCE.activeEditor.focus();
}
},
/**
* Set the width of current column when the input looses the focus
* @param {Ext.form.field.Text} input The input text
*/
setColSizeOnBlur: function(input)
{
var inputValue = input.getValue();
if (this._currentCell && inputValue != null && this._currentCell.style.width != inputValue + 'px')
{
this.setColSize(inputValue);
}
},
/**
* Set the width of current column when the spinner is made to spin up or down
* @param {Ext.form.field.Text} input The input text
* @param {String} direction The direction: 'up' if spinning up, or 'down' if spinning down.
*/
setColSizeOnSpin: function(input, direction)
{
this.setColSize(input.getValue() + (direction == 'up' ? 1 : -1));
},
/**
* Set the width of the current column
* @param {String} value The value
*/
setColSize: function (value)
{
var cell = this._currentCell;
if (cell != null)
{
this._setColWidth(cell, value != '' ? value + "px" : '');
tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
}
},
/**
* Enable/disable controller and set input value according the selected column
* @param {Ametys.ribbon.element.ui.FieldController} controller The controller
* @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
* @param {HTMLElement} node The current selected node. Can be null.
*/
colSizeListener: function (controller, field, node)
{
var cell = (field != null && node != null) ? field.getEditor().dom.getParent(field.getEditor().selection.getNode(), function (node) { return /^td|th$/i.test(node.nodeName); }) : null;
if (cell != null)
{
var cellWidth = this._getCellWantedWidth(cell);
var realWidth = this._getCellWidth(cell);
controller.enable();
controller.setValue(cellWidth);
controller.getUIControls().each(function (input) {
if (typeof input.setReferenceValue == 'function')
{
input.setReferenceValue(realWidth + " px");
}
});
}
else
{
controller.disable();
controller.setValue("");
controller.getUIControls().each(function (input) {
if (typeof input.setReferenceValue == 'function')
{
input.setReferenceValue('');
}
});
}
this._currentCell = cell;
},
// ---------------------------------------- //
// ROW SIZE //
// ---------------------------------------- //
/**
* Set the height of current row when pressing ENTER or ESC key.
* @param {Ext.form.field.Text} input The input text
* @param {Ext.event.Event} e The event object
*/
setRowSizeOnSpecialKey: function (input, e)
{
if (e.getKey() == e.ENTER)
{
e.preventDefault();
e.stopPropagation();
this.setRowSize(input.getValue());
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
}
else if (e.getKey() == e.ESC)
{
e.preventDefault();
e.stopPropagation();
input.setValue(this._getCellHeight(this._currentCell));
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
}
},
/**
* Set the height of current row when the input looses the focus
* @param {Ext.form.field.Text} input The input text
*/
setRowSizeOnBlur: function(input)
{
var inputValue = input.getValue();
if (this._currentCell && inputValue != null && this._currentCell.style.height != inputValue + 'px')
{
this.setRowSize(inputValue);
}
},
/**
* Set the height of current row when the spinner is made to spin up or down
* @param {Ext.form.field.Text} input The input text
* @param {String} direction The direction: 'up' if spinning up, or 'down' if spinning down.
*/
setRowSizeOnSpin: function(input, direction)
{
this.setRowSize(input.getValue() + (direction == 'up' ? 1 : -1));
},
/**
* Set the height of the current row
* @param {String} value The value
*/
setRowSize: function(value)
{
var cell = this._currentCell;
if (cell != null)
{
this._setRowHeight(cell, value != '' ? value + "px" : '');
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
}
},
/**
* Enable/disable controller and set input value according the selected row
* @param {Ametys.ribbon.element.ui.FieldController} controller The controller
* @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
* @param {HTMLElement} node The current selected node. Can be null.
*/
rowSizeListener: function (controller, field, node)
{
var cell = (field != null && node != null) ? field.getEditor().dom.getParent(field.getEditor().selection.getNode(), function (node) { return /^td|th$/i.test(node.nodeName); }) : null;
if (cell != null)
{
var cellHeight = this._getCellWantedHeight(cell);
var realHeight = this._getCellHeight(cell);
controller.setDisabled(false);
controller.setValue(cellHeight);
controller.getUIControls().each(function (input) {
if (typeof input.setReferenceValue == 'function')
{
input.setReferenceValue(realHeight + " px");
}
});
}
else
{
controller.setDisabled(true);
controller.setValue("");
controller.getUIControls().each(function (input) {
if (typeof input.setReferenceValue == 'function')
{
input.setReferenceValue('');
}
});
}
this._currentCell = cell;
},
/**
* Ajust table width to the content
* @param {Boolean} always true in order to take the table marker into account, false otherwise
*/
colSizeAjustToContent: function (always)
{
// Find table
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
var node = this.getTable(tinyMCE.activeEditor.selection.getNode(), always);
if (node != null)
{
tinyMCE.activeEditor.execCommand('mceBeginUndoLevel');
// remove table global size
node.style.width = '';
node.removeAttribute("data-mce-style");
// remove each cell size
var cells = tinyMCE.activeEditor.dom.select("tr *", node);
Ext.Array.each (cells,
function (cell)
{
cell.style.width = "";
cell.removeAttribute("data-mce-style");
}
);
tinyMCE.activeEditor.execCommand('mceEndUndoLevel');
tinyMCE.activeEditor.execCommand('mceRepaint');
}
},
/**
* Ajust table width to the page
* @param {Boolean} always true in order to take the table marker into account, false otherwise
*/
colSizeAjustToWindow: function (always)
{
// Find table
tinyMCE.activeEditor.focus();
var node = this.getTable(tinyMCE.activeEditor.selection.getNode(), always);
if (node != null)
{
node.style.width = '100%';
node.removeAttribute("data-mce-style");
tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
}
},
/**
* Fix table width
* @param {Boolean} always true in order to take the table marker into account, false otherwise
*/
colSizeFixed: function (always)
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.focus();
var node = this.getTable(tinyMCE.activeEditor.selection.getNode(), always);
if (node != null)
{
tinyMCE.activeEditor.execCommand('mceBeginUndoLevel');
// remove each cell size
var me = this;
var cells = tinyMCE.activeEditor.dom.select("tr > *", node);
Ext.Array.each (cells,
function (cell)
{
// need to be done twice since a read after the first modication can render a different result
cell.style.width = me._getCellWidth(cell) + "px";
cell.style.width = me._getCellWidth(cell) + "px";
cell.removeAttribute("data-mce-style");
}
);
// remove table global size
node.style.width = '';
node.removeAttribute("data-mce-style");
tinyMCE.activeEditor.execCommand('mceEndUndoLevel');
}
},
// ---------------------------------------- //
// GUIDELINES //
// ---------------------------------------- //
/**
* Show or hide guidelines
*/
toggleVisualAid: function ()
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
tinyMCE.activeEditor.execCommand('mceToggleVisualAid');
},
/**
* Enable/disable and toggle/untoggle controller according the guidelines status
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
* @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
* @param {HTMLElement} node The current selected node. Can be null.
*/
visualAidListener: function (controller, field, node)
{
var toggleState = field != null && node != null && field.getEditor() != null && field.getEditor().dom != null && field.getEditor().hasVisual;
controller.toggle(toggleState);
},
/**
* Enable/disable and toggle/untoggle controller according to the selected node
* @param {Ametys.ribbon.element.ui.ButtonController} controller The controller
* @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
* @param {HTMLElement} node The current selected node. Can be null.
*/
tableListener: function (controller, field, node)
{
this._currentNode = node;
},
// ---------------------------------------- //
// UTILITARY //
// ---------------------------------------- //
/**
* Get the surrounding table
* @param {HTMLElement} node The node element inside a table
* @param {Boolean} always true in order to take the table marker into account, false otherwise
* @param {tinymce.Editor} [editor] The wrapper tinymce editor object
* @return {HTMLElement} The table node or null if not found
*/
getTable: function(node, always, editor)
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
editor = editor || tinyMCE.activeEditor;
node = editor.dom.getParent(node, 'table');
if (always != true && node != null && node.getAttribute("marker") != null)
{
node = null;
}
return node;
},
/**
* Repair the table
* @param {HTMLElement} table The table
* @private
*/
_repairTable: function(table)
{
if (table == null)
{
return;
}
for (var i = 0; i < table.rows.length; i++)
{
for (var j = 0; j < table.rows[i].cells.length; j++)
{
var cell = table.rows[i].cells[j];
if (cell.innerHTML == '' || cell.innerHTML == ' ' || cell.childNodes.length == 1 && /^br$/i.test(cell.childNodes[0].tagName))
{
cell.innerHTML = this._createInnerHTML();
}
}
}
},
/**
* @private
* Determine the number of row headers in the table
* @param {HTMLElement} tableElt A table html element
* @return {Number} 0 if the table has no row header or the number of header rows in other cases.
*/
_countRowHeader: function (tableElt)
{
var tdEls = tinyMCE.activeEditor.dom.select("td:first-of-type", tableElt);
if (tdEls.length > 0)
{
return tdEls[0].parentNode.rowIndex;
}
else
{
return tableElt.rows.length;
}
},
/**
* @private
* Determine the number of column headers
* @param {HTMLElement} tableElt A table html element
* @return {Number} 0 if the table has no col header or the number of header cols in other cases.
*/
_countColHeader: function (tableElt)
{
var tdEls = tinyMCE.activeEditor.dom.select("td:first-of-type", tableElt);
if (tdEls.length > 0)
{
return this._getCellIndex(tdEls[0]);
}
else
{
return this._countCells(tableElt);
}
},
/**
* @private
* Determine the cell real size
* @param {HTMLElement} cell The cell node
* @return {Number} The cell width
*/
_getCellWidth: function(cell)
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
return tinyMCE.activeEditor.dom.getSize(cell).w;
},
/**
* @private
* Get the desired width. Can be empty.
*/
_getCellWantedWidth: function(cell)
{
if (cell)
{
var w = cell.style.width;
var wi = parseInt(w);
return isNaN(wi) ? '' : '' + wi;
}
else
{
return '';
}
},
/**
* @private
* Determine the size of a column
* @param {HTMLElement} cell The html table cell which size have to be determined
*/
_getColWidth: function (cell)
{
var table = this.getTable(cell);
var grid = this._getTableGrid(table);
var i = this._getCellIndex(cell, grid);
for (var x = 0; x < grid.length; x++)
{
if (this._getColRowSpan(grid[x][i]).colspan == 1)
{
return this._getCellWidth(grid[x][i]);
}
}
return this._getCellWidth(cell);
},
/**
* Get the desired width of column
* @param {Number} index The index of the column
* @param {HTMLElement} grid The table
* @private
*/
_getColWantedWidthByIndex: function(index, grid)
{
var mincolspan = 1000000;
var mincolspanAssociatedX = -1;
for (var x = 0; x < grid.length; x++)
{
var colrowspan = this._getColRowSpan(grid[x][index]);
if (colrowspan.colspan == 1)
{
return this._getCellWantedWidth(grid[x][index]);
}
else if (colrowspan.colspan < mincolspan)
{
mincolspan = colrowspan.colspan;
mincolspanAssociatedX = x;
}
}
return this._getCellWantedWidth(grid[mincolspanAssociatedX][index]) / mincolspan;
},
/**
* @private
* Set the size of a column
*/
_setColWidth: function(cell, size)
{
var table = this.getTable(cell);
var grid = this._getTableGrid(table);
var cellIndex = this._getCellIndex(cell, grid);
this._setColWidthByIndex (cellIndex, size, grid);
},
/**
* Set the column width
* @param {Number} cellIndex The column index
* @param {String} size The desired width
* @param {HTMLElement} grid The table
* @private
*/
_setColWidthByIndex: function(cellIndex, size, grid)
{
for (var x = 0; x < grid.length; x++)
{
var gridColRowSpan = this._getColRowSpan(grid[x][cellIndex]);
if (gridColRowSpan.colspan == 1 && grid[x][cellIndex])
{
grid[x][cellIndex].style.width = size;
grid[x][cellIndex].removeAttribute("data-mce-style");
}
}
},
/**
* @private
* Determine the cell real size
* @param {HTMLElement} cell The cell
*/
_getCellHeight: function(cell)
{
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
return tinyMCE.activeEditor.dom.getSize(cell).h;
},
/**
* @private
* Get the desired height. Can be empty.
* @param {HTMLElement} cell The cell
*/
_getCellWantedHeight: function(cell)
{
var h = cell.style.height;
var hi = parseInt(h);
return isNaN(hi) ? '' : '' + hi;
},
/**
* Get the desired height of a row
* @param {Number} index The row index
* @param {HTMLElement} grid The table
*/
_getRowWantedHeightByIndex: function(index, grid)
{
var minrowspan = 1000000;
var minrowspanAssociatedX = -1;
for (var x = 0; x < grid[index].length; x++)
{
var colrowspan = this._getColRowSpan(grid[index][x]);
if (colrowspan.rowspan == 1)
{
return this._getCellWantedHeight(grid[index][x]);
}
else if (colrowspan.rowspan < minrowspan)
{
minrowspan = colrowspan.rowspan;
minrowspanAssociatedX = x;
}
}
return this._getCellWantedHeight(grid[index][minrowspanAssociatedX]) / minrowspan;
},
/**
* @private
* Set the size of a row
*/
_setRowHeight: function(cell, size)
{
var table = this.getTable(cell);
var grid = this._getTableGrid(table);
var rowIndex = cell.parentNode.rowIndex;
this._setRowHeightByIndex(rowIndex, size, grid);
},
/**
* Set the row height
* @param {Number} rowIndex The row index
* @param {String} size The desired height
* @param {HTMLElement} grid The table grid
* @private
*/
_setRowHeightByIndex: function(rowIndex, size, grid)
{
for (var x = 0; x < grid[rowIndex].length; x++)
{
var gridColRowSpan = this._getColRowSpan(grid[rowIndex][x]);
if (gridColRowSpan.rowspan == 1)
{
grid[rowIndex][x].style.height = size;
grid[rowIndex][x].removeAttribute("data-mce-style");
}
}
},
/**
* @private
* Compute the colspan and the rowspan of a cell
* @param {HTMLElement} td The cell to compute
* @return {Object} A map with colspan and rowspan
*/
_getColRowSpan: function(td)
{
// UPGRADE (tinyMCE 3.2.5) plugins/table/editor_plugin_src.js _doExecCommand getColRowSpan
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
var colspan = tinyMCE.activeEditor.dom.getAttrib(td, "colspan");
var rowspan = tinyMCE.activeEditor.dom.getAttrib(td, "rowspan");
colspan = colspan == "" ? 1 : parseInt(colspan);
rowspan = rowspan == "" ? 1 : parseInt(rowspan);
return {colspan : colspan, rowspan : rowspan};
},
/**
* @private
* Will return the col index of the cell by taking in account rowspan of preceding lines
* @param {HTMLElement} cell The cell
* @param {HTMLElement[]} grid Optionnaly the grid of the table if already computed
* @return {Number} The real cell index
*/
_getCellIndex: function(cell, grid)
{
var rowIndex = cell.parentNode.rowIndex;
if (rowIndex == 1)
{
return cell.cellIndex;
}
else
{
grid = grid || this._getTableGrid(this.getTable(cell, true), rowIndex + 1);
for (var i = 0; i < grid[rowIndex].length; i++)
{
if (grid[rowIndex][i] == cell)
{
return i;
}
}
}
},
/**
* Count cells on each rows for the given table.
* @param {HTMLElement} elt The table.
* @private
*/
_countCells: function(elt)
{
var nbcell = 0;
var k = 0;
while (elt.rows[0].cells[k] != null)
{
var colspan = this._getColRowSpan(elt.rows[0].cells[k]).colspan;
nbcell += colspan;
k++;
}
return nbcell;
},
/**
* @private
* Compute a grid for the table (integrating colspan and rowspan)
* @param {HTMLElement} table The table to compute
* @param {Number} maxrow Optionnaly, you can specify a maxrow where computing will stop for performance issue.
* @return {HTMLElement[][]} An array of cell. Cells with rowspan or colspan are set several times
*/
_getTableGrid: function(table, maxrow)
{
// UPGRADE (tinyMCE 3.2.5) plugins/table/editor_plugin_src.js _doExecCommand getTableGrid
var grid = [], rows = table.rows, x, y, td, sd, xstart, x2, y2;
maxrow = maxrow || rows.length;
for (y=0; y<maxrow; y++) {
for (x=0; x<rows[y].cells.length; x++) {
td = rows[y].cells[x];
sd = this._getColRowSpan(td);
// All ready filled
for (xstart = x; grid[y] && grid[y][xstart]; xstart++) ;
// Fill box
for (y2=y; y2<y+sd['rowspan']; y2++) {
if (!grid[y2])
grid[y2] = [];
for (x2=xstart; x2<xstart+sd['colspan']; x2++)
grid[y2][x2] = td;
}
}
}
return grid;
},
/**
* @private
* Determine if we are in a cell
* @param {Ametys.cms.form.widget.RichText} field The current field
* @param {HTMLElement} node The current node selected
* @return {boolean} true if the node is in a cell
*/
_isInCellOrHeader: function (field, node)
{
var off = field == null || node == null;
if (off)
{
return false;
}
return field.getEditor() != null && field.getEditor().dom != null && field.getEditor().dom.getParent(node, function (s) { return /^td|th$/i.test(s.nodeName); } ) != null;
},
/**
* @private
* Change a cell tag to another tag
* @param {Ext.Element} cell The cell to change
* @param {String} tag The tag name of the element to replace with
* @return {boolean} true if the table has a least a th in its firt row
*/
_replaceCellWith: function (cell, tag)
{
var colspan = cell.getAttribute("colspan");
var rowspan = cell.getAttribute("rowspan");
var style = cell.hasAttribute("style") ? cell.getAttribute("style") : null;
// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
var elt = tinyMCE.activeEditor.dom.doc.createElement(tag);
if (colspan)
{
elt.setAttribute("colspan", colspan);
}
if (rowspan)
{
elt.setAttribute("rowspan", rowspan);
}
if (style)
{
elt.setAttribute("style", style);
}
while (cell.childNodes.length > 0)
{
elt.appendChild(cell.childNodes[0]);
}
cell.parentNode.insertBefore(elt, cell);
cell.parentNode.removeChild(cell);
return elt;
},
/*
* Controller helpers
*/
/**
* Creates an array of squares to choose table dimension
*/
createTabCreator: function ()
{
var col = 10;
var row = 8;
var html = ""
html += "<table class='insert-table'>";
for (var r = 0; r < row; r++)
{
html += "<tr>";
for (var c = 0; c < col; c++)
{
html += "<td> </td>";
}
html += "</tr>";
}
html += "</table>";
html += "<div class='insert-table-hint'>" + "{{i18n CONTENT_EDITION_TABLE_ARRAY_DECRIPTION}}" + "</div>";
return html;
},
/**
* Highlight the selected rows and cols
* @param {HTMLElement} table the table to modify
* @param {Number} rows The number of rows to highlight
* @param {Number} cols The number of columns to highlight
*/
drawMenuTable: function(table, rows, cols)
{
for (var j = 0; j < table.rows.length; j++)
{
for (var i = 0; i < table.rows[j].cells.length; i++)
{
var cell = table.rows[j].cells[i];
if (j <= rows && i <= cols)
{
cell.className = "over";
}
else
{
cell.className = "";
}
}
}
},
/**
* Clear the menu table
* @param {HTMLElement} table table displayed
* @param {Function} setTitleFunction method that will be called to set the title
*/
clearMenuTable: function(table, setTitleFunction)
{
for (var j = 0; j < table.rows.length; j++)
{
for (var i = 0; i < table.rows[j].cells.length; i++)
{
var cell = table.rows[j].cells[i];
cell.className = "";
}
}
setTitleFunction();
},
/**
* When a mouse hover over a cell of the table
* @param {HTMLElement} target object under the mouse
* @param {HTMLElement} table table displayed
* @param {Function} saveSizeFunction method that will be called to save the current selection
* @param {Function} setTitleFunction method that will be called to set the title
*/
onMouseOver: function(target, table, saveSizeFunction, setTitleFunction)
{
if (/^td$/i.test(target.tagName))
{
rows = target.parentNode.rowIndex;
cols = target.cellIndex;
Ametys.plugins.cms.editor.Tables.drawMenuTable(table, rows, cols);
saveSizeFunction(rows, cols);
setTitleFunction(rows, cols);
}
}
});