define( ['jquery', 'table/table-plugin-utils'], function (jQuery, Utils) { /** * Constructs a TableCell. * * @param {DomNode} cell * A td/th which will be represente by this TableCell. * @param {Table} tableObj * The Table which contains the cell. The cell will be * activated/dactivated with the table. */ var TableCell = function (originalTd, tableObj) { if (null == originalTd) { originalTd = ' '; } //original Td must be a DOM node so that the this.obj.context property is available //this transformation will properly handle jQuery objects as well as DOM nodes originalTd = jQuery(originalTd).get(0); this.obj = jQuery(originalTd); this.tableObj = tableObj; tableObj.cells.push(this); }; /** * Reference to the jQuery-representation of the wrapping table * * @see TableCell.table */ TableCell.prototype.tableObj = undefined; /** * Reference to the jQuery td-Object of the cell */ TableCell.prototype.obj = undefined; /** * The jQuery wrapper of the cell */ TableCell.prototype.wrapper = undefined; /** * Flag if the cell has focus */ TableCell.prototype.hasFocus = false; TableCell.prototype.activate = function () { var cell = this; var $elem = cell.obj; // wrap the created div into the contents of the cell $elem.wrapInner('
'); // create the editable wrapper for the cells var $wrapper = $elem.children('div').eq(0); $wrapper.contentEditable(true); $wrapper.addClass('aloha-table-cell-editable'); // attach events to the editable div-object $wrapper.bind('focus', function ($event) { // ugly workaround for ext-js-adapter problem in // ext-jquery-adapter-debug.js:1020 if ($event.currentTarget) { $event.currentTarget.indexOf = function () { return -1; }; } cell._editableFocus($event); }); $wrapper.bind('mousedown', function ($event) { // ugly workaround for ext-js-adapter problem in ext-jquery-adapter-debug.js:1020 if ($event.currentTarget) { $event.currentTarget.indexOf = function () { return -1; }; } cell._editableMouseDown($event); cell.tableObj.selection.baseCellPosition = [cell._virtualY(), cell._virtualX()]; if ($event.shiftKey) { // shift-click to select a coherent cell range // // in IE it's not possible to select multiple cells when you "select+drag" over other cells // click into the first cell and then "shift-click" into the last cell of the coherent cell range you want to select var right = cell.tableObj.selection.lastBaseCellPosition[1]; var bottom = cell.tableObj.selection.lastBaseCellPosition[0]; var topLeft = cell.tableObj.selection.baseCellPosition; var left = topLeft[1]; if (left > right) { left = right; right = topLeft[1]; } var top = topLeft[0]; if (top > bottom) { top = bottom; bottom = topLeft[0]; } var rect = { "top": top, "right": right, "bottom": bottom, "left": left }; var table = cell.tableObj; var $rows = table.obj.children().children('tr'); var grid = Utils.makeGrid($rows); table.selection.selectedCells = []; var selectClass = table.get('classCellSelected'); Utils.walkGrid(grid, function (cellInfo, j, i) { if (Utils.containsDomCell(cellInfo)) { if (i >= rect.top && i <= rect.bottom && j >= rect.left && j <= rect.right) { jQuery(cellInfo.cell).addClass(selectClass); table.selection.selectedCells.push(cellInfo.cell); } else { jQuery(cellInfo.cell).removeClass(selectClass); } } }); table.selection.notifyCellsSelected(); } else { cell.tableObj.selection.lastBaseCellPosition = cell.tableObj.selection.baseCellPosition; cell._editableMouseDown($event); cell._startCellSelection(); } }); $wrapper.bind('blur', function ($event) { cell._editableBlur($event); }); $wrapper.bind('keyup', function ($event) { cell._editableKeyUp($event); }); $wrapper.bind('keydown', function ($event) { cell._editableKeyDown($event); }); $wrapper.bind('mouseover', function ($event) { cell._selectCellRange(); }); // we will treat the wrapper just like an editable $wrapper.contentEditableSelectionChange(function ($event) { Aloha.Selection.onChange($wrapper, $event); return $wrapper; }); $elem.bind('mousedown', function ($event) { window.setTimeout(function () { // Select the entire cell's content. cell.wrapper.trigger('focus'); cell._selectAll($wrapper); }, 1); if (!$event.shiftKey) { cell.tableObj.selection.unselectCells(); cell._startCellSelection(); } $event.stopPropagation(); }); if ($elem.get(0)) { $elem.get(0).onselectstart = function () { return false; }; } // set contenteditable wrapper div this.wrapper = $elem.children(); if (this.wrapper.get(0)) { this.wrapper.get(0).onselectstart = function () { window.event.cancelBubble = true; }; // Disabled the dragging of content, since it makes cell selection // difficult. this.wrapper.get(0).ondragstart = function () { return false }; } return this; }; /** * The deactivate method removes the contenteditable helper div within the * table-data field and wraps the innerHtml to the outerHTML * * @return void */ TableCell.prototype.deactivate = function () { var wrapper = jQuery(this.obj.children('.aloha-table-cell-editable')); if (wrapper.length) { // unwrap cell contents without re-creating dom nodes wrapper.parent().append( wrapper.contents() ); // remove the contenteditable div and its attached events wrapper.remove(); // remove the click event of the this.obj.unbind('click'); this.obj.unbind('mousedown'); if (jQuery.trim(this.obj.attr('class')) == '') { this.obj.removeAttr('class'); } } } /** * Native toString-method * * @return string name of the namespace */ TableCell.prototype.toString = function () { return 'TableCell'; }; /** * Focus method for the contentediable div within a table data-field. The method * requires the event-property Cell as a Cell object. If the * Cell wasn't activated yet it does all relevant actions to activate the cell. * * @param e * the jquery event object * @return void */ TableCell.prototype._editableFocus = function (e) { // only do activation stuff if the cell don't has the focus if (!this.hasFocus) { // set an internal flag to focus the table this.tableObj.focus(); // add an active-class this.obj.addClass('aloha-table-cell_active'); // set the focus flag this.hasFocus = true; // unset the selection type this.tableObj.selection.selectionType = 'cell'; } }; /** * Blur event for the contenteditable div within a table-data field. The method * requires the event-property TableCell as a TableCell object. It * sets the hasFocus flag of the cell to false and removes the "active" * css-class. * * @param jqEvent * the jquery event object * @return void */ TableCell.prototype._editableBlur = function (jqEvent) { // reset the focus of the cell this.hasFocus = false; // remove "active class" this.obj.removeClass('aloha-table-cell_active'); }; /** * Gives the X (column no) for a cell, after adding colspans */ TableCell.prototype._virtualX = function () { var $rows = this.tableObj.obj.children().children('tr'); var rowIdx = this.obj.parent().index(); var colIdx = this.obj.index(); return Utils.cellIndexToGridColumn($rows, rowIdx, colIdx); }; /** * Gives the Y (row no) for a cell, after adding colspans */ TableCell.prototype._virtualY = function () { return this.obj.parent('tr').index(); }; /** * Starts the cell selection mode */ TableCell.prototype._startCellSelection = function () { if(!this.tableObj.selection.cellSelectionMode) { //unselect currently selected cells this.tableObj.selection.unselectCells(); // activate cell selection mode this.tableObj.selection.cellSelectionMode = true; //bind a global mouseup event handler to stop cell selection var that = this; jQuery('body').bind('mouseup.cellselection', function () { that._endCellSelection(); }); this.tableObj.selection.baseCellPosition = [this._virtualY(), this._virtualX()]; } }; /** * Ends the cell selection mode */ TableCell.prototype._endCellSelection = function() { if(this.tableObj.selection.cellSelectionMode) { this.tableObj.selection.cellSelectionMode = false; this.tableObj.selection.baseCellPosition = null; this.tableObj.selection.lastSelectionRange = null; this.tableObj.selection.selectionType = 'cell'; //unbind the global cell selection event jQuery('body').unbind('mouseup.cellselection'); } }; TableCell.prototype._getSelectedRect = function () { var right = this._virtualX(); var bottom = this._virtualY(); var topLeft = this.tableObj.selection.baseCellPosition; var left = topLeft[1]; if (left > right) { left = right; right = topLeft[1]; } var top = topLeft[0]; if (top > bottom) { top = bottom; bottom = topLeft[0]; } return { "top": top, "right": right, "bottom": bottom, "left": left }; }; /** * Toggles selection of cell. * This works only when cell selection mode is active. */ TableCell.prototype._selectCellRange = function() { if(this.tableObj.selection.resizeMode || !this.tableObj.selection.cellSelectionMode) { return; } var rect = this._getSelectedRect(); var table = this.tableObj; var $rows = table.obj.children().children('tr'); var grid = Utils.makeGrid($rows); table.selection.selectedCells = []; var selectClass = table.get('classCellSelected'); Utils.walkGrid(grid, function (cellInfo, j, i) { if (Utils.containsDomCell(cellInfo)) { if (i >= rect.top && i <= rect.bottom && j >= rect.left && j <= rect.right) { jQuery(cellInfo.cell).addClass(selectClass); table.selection.selectedCells.push(cellInfo.cell); } else { jQuery(cellInfo.cell).removeClass(selectClass); } } }); table.selection.notifyCellsSelected(); }; /** * Selects all inner-contens of an contentEditable-object * * @param editableNode dom-representation of the editable node (div-element) * @return void */ TableCell.prototype._selectAll = function (editableNode) { var e = (editableNode.jquery) ? editableNode.get(0) : editableNode; // Not IE if (!jQuery.browser.msie) { var s = window.getSelection(); // WebKit if (s.setBaseAndExtent /*&& e> 0 */ ) { s.setBaseAndExtent(e, 0, e, Math.max(0, e.innerText.length - 1)); } // Firefox and Opera else { // workaround for bug # 42885 if (window.opera && e.innerHTML.substring(e.innerHTML.length - 4) == '
') { e.innerHTML = e.innerHTML + ' '; } var r = document.createRange(); r.selectNodeContents(e); s.removeAllRanges(); s.addRange(r); } } // Some older browsers else if (document.getSelection) { var s = document.getSelection(); var r = document.createRange(); r.selectNodeContents(e); s.removeAllRanges(); s.addRange(r); } // IE else if (document.selection) { var r = document.body.createTextRange(); r.moveToElementText(e); r.select(); } }; /** * The mouse-down event for the editable-div in the thd-field. Unselect all * cells when clicking on the editable-div. * * @param jqEvent * the jquery-event object * @return void */ TableCell.prototype._editableMouseDown = function (jqEvent) { // deselect all highlighted cells registered in the this.tableObj.selection object this.tableObj.selection.unselectCells(); if (this.tableObj.hasFocus) { jqEvent.stopPropagation(); } }; /** * The key-up event for the editable-div in the td-field. Just check if the div * is empty and insert an   * * @param jqEvent * the jquery-event object * @return void */ TableCell.prototype._editableKeyUp = function (jqEvent) { //TODO do we need to check for empty cells and insert a space? //this._checkForEmptyEvent(jqEvent); }; /** * The key-down event for the ediable-div in the td-field. Check if the the div * is empty and insert an  . Furthermore if cells are selected, unselect * them. * * @param jqEvent * the jquery-event object * @return void */ TableCell.prototype._editableKeyDown = function (jqEvent) { var KEYCODE_TAB = 9; this._checkForEmptyEvent(jqEvent); if ( this.obj[0] === this.tableObj.obj.find('tr:last td:last')[0] ) { // only add a row on a single key-press of tab (so check // that alt-, shift- or ctrl-key are NOT pressed) if (KEYCODE_TAB == jqEvent.keyCode && !jqEvent.altKey && !jqEvent.shiftKey && !jqEvent.ctrlKey) { // add a row after the current row this.tableObj.addRow(this.obj.parent().index() + 1); // firefox needs this for the first cell of the new row // to be selected (.focus() doesn't work reliably in // IE7) this.tableObj.cells[this.tableObj.cells.length - 1]._selectAll(this.wrapper.get(0)); jqEvent.stopPropagation(); return; } } }; /** * The custom keyup event for a table-cell Checks if the cell is empty and * inserts a space (\u00a0) * * @param e * the event object which is given by jquery * @return void */ TableCell.prototype._checkForEmptyEvent = function (jqEvent) { var $wrapper = jQuery(this.wrapper), text = $wrapper.text(); if ($wrapper.children().length > 0) { return; } // if empty insert a blank space and blur and focus the wrapper if (text === '') { this.wrapper.text('\u00a0'); this.wrapper.get(0).blur(); this.wrapper.get(0).focus(); } }; /** * Given a cell, will return the container element of the contents * of the cell. The container element may be the given cell itself, * or a wrapper element, in the case of activated cells. * * @param {DomNode} cell * the TH/TD of a TableCell that may or may not be actived. * @return {DomNode} * the element that contains the contents of the given cell. */ TableCell.getContainer = function (cell) { if (jQuery(cell.firstChild).hasClass("aloha-table-cell-editable")) { return cell.firstChild; } else { return cell; } }; return TableCell; });