Show: Inherited Protected Private Deprecated

# File `src/core/table.js`

                                (function() {
                                    'use strict';

                                    var IE_NON_DIRECTLY_EDITABLE_ELEMENT = {
                                        'table': 1,
                                        'col': 1,
                                        'colgroup': 1,
                                        'tbody': 1,
                                        'td': 1,
                                        'tfoot': 1,
                                        'th': 1,
                                        'thead': 1,
                                        'tr': 1
                                    };

                                    /**
                                     * Table class utility. Provides methods for create, delete and update tables.
                                     *
                                     * @class CKEDITOR.Table
                                     * @constructor
                                     * @param {Object} editor The CKEditor instance.
                                     */

                                    function Table(editor) {
                                        this._editor = editor;
                                    }

                                    Table.HEADING_BOTH = 'Both';
                                    Table.HEADING_COL = 'Column';
                                    Table.HEADING_NONE = 'None';
                                    Table.HEADING_ROW = 'Row';

                                    Table.prototype = {
                                        constructor: Table,

                                        /**
                                         * Creates a table.
                                         *
                                         * @method create
                                         * @param {Object} config Table configuration object
                                         * @return {Object} The created table
                                         */
                                        create: function(config) {
                                            var editor = this._editor;
                                            var table = this._createElement('table');

                                            config = config || {};

                                            // Generate the rows and cols.
                                            var tbody = table.append(this._createElement('tbody'));
                                            var rows = config.rows || 1;
                                            var cols = config.cols || 1;

                                            for (var i = 0; i < rows; i++) {
                                                var row = tbody.append(this._createElement('tr'));
                                                for (var j = 0; j < cols; j++) {
                                                    var cell = row.append(this._createElement('td'));

                                                    cell.appendBogus();
                                                }
                                            }

                                            this.setAttributes(table, config.attrs);
                                            this.setHeading(table, config.heading);

                                            // Insert the table element if we're creating one.
                                            editor.insertElement(table);

                                            var firstCell = new CKEDITOR.dom.element(table.$.rows[0].cells[0]);
                                            var range = editor.createRange();
                                            range.moveToPosition(firstCell, CKEDITOR.POSITION_AFTER_START);
                                            range.select();

                                            return table;
                                        },

                                        /**
                                         * Retrieves a table from the current selection.
                                         *
                                         * @method getFromSelection
                                         * @return {CKEDITOR.dom.element} The retrieved table or null if not found.
                                         */
                                        getFromSelection: function() {
                                            var table;
                                            var selection = this._editor.getSelection();
                                            var selected = selection.getSelectedElement();

                                            if (selected && selected.is('table')) {
                                                table = selected;
                                            } else {
                                                var ranges = selection.getRanges();

                                                if (ranges.length > 0) {
                                                    // Webkit could report the following range on cell selection (#4948):
                                                    // <table><tr><td>[&nbsp;</td></tr></table>]

                                                    /* istanbul ignore else */
                                                    if (CKEDITOR.env.webkit) {
                                                        ranges[0].shrink(CKEDITOR.NODE_ELEMENT);
                                                    }

                                                    table = this._editor.elementPath(ranges[0].getCommonAncestor(true)).contains('table', 1);
                                                }
                                            }

                                            return table;
                                        },

                                        /**
                                         * Checks if a given table can be considered as editable. This method
                                         * workarounds a limitation of IE where for some elements (like table),
                                         * `isContentEditable` returns always false. This is because IE does not support
                                         * `contenteditable` on such elements. However, despite such elements
                                         * cannot be set as content editable directly, a content editable SPAN,
                                         * or DIV element can be placed inside the individual table cells.
                                         * See https://msdn.microsoft.com/en-us/library/ms537837%28v=VS.85%29.aspx
                                         *
                                         * @method isEditable
                                         * @param {CKEDITOR.dom.element} el The table element to test if editable
                                         * @return {Boolean}
                                         */
                                        isEditable: function (el) {
                                            if (!CKEDITOR.env.ie || !el.is(IE_NON_DIRECTLY_EDITABLE_ELEMENT)) {
                                                return !el.isReadOnly();
                                            }

                                            if (el.hasAttribute('contenteditable')) {
                                                return (el.getAttribute('contenteditable') !== 'false');
                                            }

                                            return this.isEditable(el.getParent());
                                        },

                                        /**
                                         * Returns which heading style is set for the given table.
                                         *
                                         * @method getHeading
                                         * @param {CKEDITOR.dom.element} table The table to gather the heading from. If null, it will be retrieved from the current selection.
                                         * @return {String} The heading of the table. Expected values are `CKEDITOR.Table.NONE`, `CKEDITOR.Table.ROW`, `CKEDITOR.Table.COL` and `CKEDITOR.Table.BOTH`.
                                         */
                                        getHeading: function(table) {
                                            table = table || this.getFromSelection();

                                            if (!table) {
                                                return null;
                                            }

                                            var rowHeadingSettings = table.$.tHead !== null;

                                            var colHeadingSettings = true;

                                            // Check if all of the first cells in every row are TH
                                            for (var row = 0; row < table.$.rows.length; row++) {
                                                // If just one cell isn't a TH then it isn't a header column
                                                var cell = table.$.rows[row].cells[0];

                                                if (cell && cell.nodeName.toLowerCase() !== 'th') {
                                                    colHeadingSettings = false;
                                                    break;
                                                }
                                            }

                                            var headingSettings = Table.HEADING_NONE;

                                            if (rowHeadingSettings) {
                                                headingSettings = Table.HEADING_ROW;
                                            }

                                            if (colHeadingSettings) {
                                                headingSettings = (headingSettings === Table.HEADING_ROW ? Table.HEADING_BOTH : Table.HEADING_COL);
                                            }

                                            return headingSettings;
                                        },

                                        /**
                                         * Removes a table from the editor.
                                         *
                                         * @method remove
                                         * @param {CKEDITOR.dom.element} table The table element which table style should be removed.
                                         */
                                        remove: function(table) {
                                            var editor = this._editor;

                                            if (table) {
                                                table.remove();
                                            } else {
                                                table = editor.elementPath().contains('table', 1);

                                                if (table) {
                                                    // If the table's parent has only one child remove it as well (unless it's a table cell, or the editable element) (#5416, #6289, #12110)
                                                    var parent = table.getParent();
                                                    var editable = editor.editable();

                                                    if (parent.getChildCount() === 1 && !parent.is('td', 'th') && !parent.equals(editable)) {
                                                        table = parent;
                                                    }

                                                    var range = editor.createRange();
                                                    range.moveToPosition(table, CKEDITOR.POSITION_BEFORE_START);
                                                    table.remove();
                                                    range.select();
                                                }
                                            }
                                        },

                                        /**
                                         * Assigns provided attributes to a table.
                                         *
                                         * @method setAttributes
                                         * @param {Object} table The table to which the attributes should be assigned
                                         * @param {Object} attrs The attributes which have to be assigned to the table
                                         */
                                        setAttributes: function(table, attrs) {
                                            if (attrs) {
                                                Object.keys(attrs).forEach(function(attr) {
                                                    table.setAttribute(attr, attrs[attr]);
                                                });
                                            }
                                        },

                                        /**
                                         * Sets the appropriate table heading style to a table.
                                         *
                                         * @method setHeading
                                         * @param {CKEDITOR.dom.element} table The table element to which the heading should be set. If null, it will be retrieved from the current selection.
                                         * @param {String} heading The table heading to be set. Accepted values are: `CKEDITOR.Table.NONE`, `CKEDITOR.Table.ROW`, `CKEDITOR.Table.COL` and `CKEDITOR.Table.BOTH`.
                                         */
                                        setHeading: function(table, heading) {
                                            table = table || this.getFromSelection();

                                            var i, newCell;
                                            var tableHead;
                                            var tableBody = table.getElementsByTag('tbody').getItem(0);

                                            var tableHeading = this.getHeading(table);
                                            var hadColHeading = (tableHeading === Table.HEADING_COL || tableHeading === Table.HEADING_BOTH);

                                            var needColHeading = heading === Table.HEADING_COL || heading === Table.HEADING_BOTH;
                                            var needRowHeading = heading === Table.HEADING_ROW || heading === Table.HEADING_BOTH;

                                            // If we need row heading and don't have a <thead> element yet, move the
                                            // first row of the table to the head and convert the nodes to <th> ones.
                                            if (!table.$.tHead && needRowHeading) {
                                                var tableFirstRow = tableBody.getElementsByTag('tr').getItem(0);
                                                var tableFirstRowChildCount = tableFirstRow.getChildCount();

                                                // Change TD to TH:
                                                for (i = 0; i < tableFirstRowChildCount; i++) {
                                                    var cell = tableFirstRow.getChild(i);

                                                    // Skip bookmark nodes. (#6155)
                                                    if (cell.type === CKEDITOR.NODE_ELEMENT && !cell.data('cke-bookmark')) {
                                                        cell.renameNode('th');
                                                        cell.setAttribute('scope', 'col');
                                                    }
                                                }

                                                tableHead = this._createElement(table.$.createTHead());
                                                tableHead.append(tableFirstRow.remove());
                                            }

                                            // If we don't need row heading and we have a <thead> element, move the
                                            // row out of there and into the <tbody> element.
                                            if (table.$.tHead !== null && !needRowHeading) {
                                                // Move the row out of the THead and put it in the TBody:
                                                tableHead = this._createElement(table.$.tHead);

                                                var previousFirstRow = tableBody.getFirst();

                                                while (tableHead.getChildCount() > 0) {
                                                    var newFirstRow = tableHead.getFirst();
                                                    var newFirstRowChildCount = newFirstRow.getChildCount();

                                                    for (i = 0; i < newFirstRowChildCount; i++) {
                                                        newCell = newFirstRow.getChild(i);

                                                        if (newCell.type === CKEDITOR.NODE_ELEMENT) {
                                                            newCell.renameNode('td');
                                                            newCell.removeAttribute('scope');
                                                        }
                                                    }

                                                    newFirstRow.insertBefore(previousFirstRow);
                                                }

                                                tableHead.remove();
                                            }

                                            tableHeading = this.getHeading(table);
                                            var hasColHeading = (tableHeading === Table.HEADING_COL || tableHeading === Table.HEADING_BOTH);

                                            // If we need column heading and the table doesn't have it, convert every first cell in
                                            // every row into a `<th scope="row">` element.
                                            if (!hasColHeading && needColHeading) {
                                                for (i = 0; i < table.$.rows.length; i++) {
                                                    if (table.$.rows[i].cells[0].nodeName.toLowerCase() !== 'th') {
                                                        newCell = new CKEDITOR.dom.element(table.$.rows[i].cells[0]);
                                                        newCell.renameNode('th');
                                                        newCell.setAttribute('scope', 'row');
                                                    }
                                                }
                                            }

                                            // If we don't need column heading but the table has it, convert every first cell in every
                                            // row back into a `<td>` element.
                                            if (hadColHeading && !needColHeading) {
                                                for (i = 0; i < table.$.rows.length; i++) {
                                                    var row = new CKEDITOR.dom.element(table.$.rows[i]);

                                                    if (row.getParent().getName() === 'tbody') {
                                                        newCell = new CKEDITOR.dom.element(row.$.cells[0]);
                                                        newCell.renameNode('td');
                                                        newCell.removeAttribute('scope');
                                                    }
                                                }
                                            }
                                        },

                                        /**
                                         * Creates a new CKEDITOR.dom.element using the passed tag name.
                                         *
                                         * @protected
                                         * @method _createElement
                                         * @param {String} name The tag name from which an element should be created
                                         * @return {CKEDITOR.dom.element} Instance of CKEDITOR DOM element class
                                         */
                                        _createElement: function(name) {
                                            return new CKEDITOR.dom.element(name, this._editor.document);
                                        }
                                    };

                                    CKEDITOR.on('instanceReady', function(event) {
                                        var headingCommands = [Table.HEADING_NONE, Table.HEADING_ROW, Table.HEADING_COL, Table.HEADING_BOTH];

                                        var tableUtils = new Table(event.editor);

                                        headingCommands.forEach(function(heading) {
                                            event.editor.addCommand('tableHeading' + heading, {
                                                exec: function(editor) {
                                                    tableUtils.setHeading(null, heading);
                                                }
                                            });
                                        });
                                    });

                                    CKEDITOR.Table = CKEDITOR.Table || Table;
                                }());