UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

1,501 lines (1,211 loc) 44.1 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2013 1&1 Internet AG, Germany, http://www.1und1.de License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Romeo Kenfack Tsakem (rkenfack) ************************************************************************ */ /** * This is a widget that enhances an HTML table with some basic features like * Sorting and Filtering. * * EXPERIMENTAL - NOT READY FOR PRODUCTION * * <h2>CSS Classes</h2> * <table> * <thead> * <tr> * <td>Class Name</td> * <td>Applied to</td> * <td>Description</td> * </tr> * </thead> * <tbody> * <tr> * <td><code>qx-table</code></td> * <td>Table element</td> * <td>Identifies the Table widget</td> * </tr> * <tr> * <td><code>qx-table-cell</code></td> * <td>Table cell (<code>td</code>)</td> * <td>Identifies and styles a cell of the widget</td> * </tr> * <tr> * <td><code>qx-table-header</code></td> * <td>Table header (<code>th</code>)</td> * <td>Identifies and styles a header of the table widget</td> * </tr> * <tr> * <td><code>qx-table-row-selection</code></td> * <td>Table cell (<code>td</code>)</td> * <td>Identifies and styles the cells containing the inputs for the row selection</td> * </tr> * <tr> * <td><code>qx-table-selection-input</code></td> * <td><code>input</code></td> * <td>Identifies and styles input element to select a table row</td> * </tr> * <tr> * <td><code>qx-table-input-label</code></td> * <td>Label element (<code>label</code>)</td> * <td>Identifies and styles label contained in the selection cell. This label can be used to create custom inputs</td> * </tr> * <tr> * <td><code>qx-table-row-selected</code></td> * <td>Selected row (<code>tr</code>)</td> * <td>Identifies and styles the selected table rows</td> * </tr> * <tr> * <td><code>qx-table-sort-asc</code></td> * <td>Table header (<code>th</code>)</td> * <td>Identifies and styles the header of the current ascendant sorted column</td> * </tr> * <tr> * <td><code>qx-table-sort-desc</code></td> * <td>Table header (<code>th</code>)</td> * <td>Identifies and styles the header of the current descendant sorted column</td> * </tr> * </tbody> * </table> * * @group (Widget) * */ qx.Bootstrap.define("qx.ui.website.Table", { extend : qx.ui.website.Widget, construct : function(selector, context) { this.base(arguments, selector, context); }, events : { /** Fires at each model change */ "modelChange" : "Array", /** Fires at each selection change */ "selectionChange" : "qxWeb", /** Fires each time a cell of the widget is clicked */ "cellClick" : "Object", /** Fires each time a cell of the widget is hovered */ "cellHover" : "Object", /** Fires each time the mouse leave a cell of the table widget */ "cellOut" : "Object", /** Fires after the model has been applied to the widget */ "modelApplied" : "Array", /** Fires each time the value of a cell is rendered into the cell */ "cellRender" : "Object", /** Fires after the table rows have been sorted */ "sort" : "Object", /** Fires after the table rows have been filtered */ "filter" : "Object" }, statics : { /** * *caseSensitive* * Determines if the string sorting/filtering should be case sensitive or not. Default value : <code>false</code>. * * *rowSelection* * Defines the row selection type. Possible values are : 'none', 'single', 'multiple'. Default value : <code>none</code>. * */ _config : { caseSensitive : false, rowSelection : "none", sortable : false }, /** * *columnDefault* * The Default cell template for all the table columns. Default value : * * <pre> * <td class='qx-table-cell' data-qx-table-cell-key='{{ cellKey }}'> * <div class='qx-table-cell-wrapper'> * <label>{{& value }}</label> * </div> * <td>" * </pre> * * To define a custom template for a specific name use <code>setTemplate('colname', template)</code> or use <br> * <code>setTemplate('columnDefault', template)</code> to set one template for all your table columns. * */ _templates : { "columnDefault" : "<td class='qx-table-cell' data-qx-table-cell-key='{{ cellKey }}'>"+ "<div class='qx-table-cell-wrapper'>"+ "<label>{{& value }}</label>"+ "</div>"+ "<td>" }, /** * Factory method which converts the current collection into a collection of * table widgets. * @param model {Array} The model of the widgets in the collection * @return {qx.ui.website.Table} A new table collection. * @attach {qxWeb} */ table : function(model) { var table = new qx.ui.website.Table(this); table.__model = model; table.init(); return table; }, /** * Checks if a given string is a number * @param n {String} The String to check the type for * @return {Boolean} The result of the check */ __isNumber : function(n) { return (Object.prototype.toString.call(n) === '[object Number]' || Object.prototype.toString.call(n) === '[object String]') && !isNaN(parseFloat(n)) && isFinite(n.toString().replace(/^-/, '')); }, /** * Checks if a given string is a Date * @param val {String} The String to check the type for * @return {Boolean} The result of the check */ __isDate : function(val) { var d = new Date(val); return !isNaN(d.valueOf()); }, /** * Gets the index of an HTMLElement inside of an HTMLCollection * @param htmlCollection {HTMLCollection} The HTMLCollection * @param htmlElement {HTMLElement} The HTMLElement * @return {Integer} The position of the htmlElement or -1 */ __getIndex : function(htmlCollection, htmlElement) { var index = -1; for (var i = 0, l = htmlCollection.length; i < l; i++) { if (htmlCollection.item(i) == htmlElement) { index = i; break; } } return index; }, /** * Generates an unique id * @return {String} The generated id */ __getUID : function() { return ((new Date()).getTime() + "" + Math.floor(Math.random() * 1000000)).substr(0, 18); }, /** */ __selectionTypes : ["single", "multiple", "none"], /** */ __internalCellClass : "qx-table-cell", /** */ __internalHeaderClass : "qx-table-header", /** */ __internalSelectionClass : "qx-table-row-selection", /** */ __internalInputClass : "qx-table-selection-input", /** */ __allColumnSelector : "qx-table-all-columns", /** */ __dataColName : "data-qx-table-col-name", /** */ __dataColType : "data-qx-table-col-type", /** */ __dataSortingKey : "data-qx-table-cell-key", /** */ __modelSortingKey : "cellKey", /** */ __inputLabelClass : "qx-table-input-label", /** */ __selectedRowClass : "qx-table-row-selected", /** */ __ascSortingClass : "qx-table-sort-asc", /** */ __descSortingClass : "qqx-table-sort-desc" }, members : { __model: null, __columnMeta: null, __sortingFunction: null, __filterFunction: null, __filterFunc: null, __filters: null, __inputName: null, __hovered: null, __sortingData: null, // overridden init : function() { if (!this.base(arguments)) { return false; } var model = this.__model; if (qxWeb.getNodeName(this).toUpperCase() !== "TABLE") { throw new Error("collection should contains only table elements !!"); } if(!this[0].tHead){ throw new Error("A Table header element is required for this widget."); } this.find("tbody td").addClass("qx-table-cell"); this.__inputName = "input" + qx.ui.website.Table.__getUID(); this.__getColumnMetaData(model); this.setModel(model); this.setSortingFunction(this.__defaultColumnSort); this.__registerEvents(); this.__hovered = null; return true; }, /** * Sets the given model to the widgets in the collection * * @param model {Array} The model to be set * @return {qx.ui.website.Table} <code>this</code> reference for chaining. */ setModel : function(model) { if (typeof model != "undefined") { if (qx.lang.Type.isArray(model)) { this.__model = model; this.emit("modelChange", model); } else { throw new Error("model must be an Array !!"); } } return this; }, /** * Set the column types for the table widgets in the collection * @param columnName {String} The column name * @param type {String} The type of the column * @return {qx.ui.website.Table} <code>this</code> reference for chaining. */ setColumnType : function(columnName, type) { this.__checkColumnExistance(columnName); this.__columnMeta[columnName].type = type; return this; }, /** * Returns the type of the specified column * @param columnName {String} The column name * @return {String} The type of the specified column */ getColumnType : function(columnName) { this.eq(0).__checkColumnExistance(columnName); return this.eq(0).__columnMeta[columnName].type; }, /** * Returns the cell at the given position for the first widget in the collection * @param row {Integer} The row number * @param col {Integer} The column number * @return {qxWeb} The cell found at the given position */ getCell : function(row, col) { return qxWeb(this.eq(0).__getRoot().rows.item(row).cells.item(col)); }, /** * Returns a collection containing the rows of the first table in the collection. * @return {qxWeb} The collection containing the table rows */ getRows : function() { return qxWeb(this.eq(0).__getRoot().rows); }, /** * Defines the comparison function to use to sort columns of the given type * @param type {String} The type to define the function for * @param compareFunc {Function} The comparison function * @return {qx.ui.website.Table} <code>this</code> reference for chaining. */ setCompareFunction : function(type, compareFunc) { type = qxWeb.string.firstUp(type); this.setProperty(["_compare" + type], compareFunc); return this; }, /** * Unset the compare function for the given type * * @param type {String} The type to unset the function for * @return {qx.ui.website.Table} <code>this</code> reference for chaining. */ unsetCompareFunction : function(type) { type = qxWeb.string.firstUp(type); var compareFunc = this["_compare" + type] || this._compareString; this.setProperty(["_compare" + type], compareFunc); return this; }, /** * Returns the comparison function for the given type * @param type {String} The type to get the comparison function for * @return {Function} The comparison function */ getCompareFunction : function(type) { type = qxWeb.string.firstUp(type); return this.getProperty("_compare" + type) || this["_compare" + type]; }, /** * Set the function that control the sorting process * @param func {Function} The sorting function * @return {qx.ui.website.Table} <code>this</code> reference for chaining. */ setSortingFunction : function(func) { func = func || function() {}; this.__sortingFunction = func; return this; }, /** * Unset the function that control the sorting process * @return {qx.ui.website.Table} <code>this</code> reference for chaining. */ unsetSortingFunction : function() { this.__sortingFunction = this.__defaultColumnSort; return this; }, /** * Set the function that will be used to process the column filtering * @param func {Function} The filter function * @return {qx.ui.website.Table} <code>this</code> reference for chaining. */ setFilterFunction : function(func) { this.__filterFunction = func; return this; }, /** * Unset the filter function * @return {qx.ui.website.Table} <code>this</code> reference for chaining. */ unsetFilterFunction : function() { this.__filterFunction = this.__defaultColumnFilter; return this; }, /** * Set the filter function to use to filter a specific column * @param columnName {String} The name of the column * @param func {Function} The filter * @return {qx.ui.website.Table} <code>this</code> reference for chaining. * */ setColumnFilter : function(columnName, func) { this.__checkColumnExistance(columnName); if(!this.__filterFunc) { this.__filterFunc = {}; } this.__filterFunc[columnName] = func; return this; }, /** * Returns the filter function set on a specific column * * @param columnName {String} The name of the column * @return {Function} The filter function * */ getColumnFilter : function(columnName) { if(this.__filterFunc){ return this.__filterFunc[columnName]; } return null; }, /** * Set the filter function to use to filter the table rows * @param func {Function} The filter * @return {qx.ui.website.Table} <code>this</code> reference for chaining. */ setRowFilter : function(func) { if(!this.__filterFunc) { this.__filterFunc = {}; } this.__filterFunc.row = func; return this; }, /** * Returns the filter function set on a specific column * @return {Function} The filter function * */ getRowFilter : function() { if(this.__filterFunc) { return this.__filterFunc.row; } return null; }, /** * Sort the column with the given name according to the specified direction * @param columnName {String} The name of the column to sort * @param dir {String} The sorting direction (asc or desc) * @return {qx.ui.website.Table} <code>this</code> reference for chaining. */ sort : function(columnName, dir) { this.__checkColumnExistance(columnName); this.setSortingClass(columnName, dir); this.__sortDOM(this.__sort(columnName, dir)); this.emit("sort", {columName : columnName, direction : dir}); return this; }, /** * Filters rows or columns according to the given parameters * @param keyword {String} The keyword to use to filter * @param columnName {String ?} The column name * @return {qx.ui.website.Table} <code>this</code> reference for chaining. */ filter : function(keyword, columnName) { if (columnName) { this.__checkColumnExistance(columnName); if (keyword == "") { this.resetFilter(columnName); } } else { columnName = qx.ui.website.Table.__allColumnSelector; } if (!this.__filters) { this.__filters = {}; } if (this.__filters[columnName]) { this.__filters[columnName].keyword = keyword; this.__getRoot().appendChild(this.__filters[columnName].rows); } else { this.__filters[columnName] = { keyword : keyword, rows : document.createDocumentFragment() }; } this.__filterDom(keyword, columnName); this.emit("filter", {columName : columnName, keyword : keyword}); return this; }, /** * Resets the filter applied on a specific column * @param columnName {String ?} The column name * @return {qx.ui.website.Table} <code>this</code> reference for chaining. */ resetFilter : function(columnName) { var filters = null; filters = this.__filters; if (filters) { if (columnName) { this.__getRoot().appendChild(filters[columnName].rows); } else { for (var col in filters) { this.__getRoot().appendChild(filters[col].rows); } } } return this; }, /** * Removes the rows of in the table body * @param tableData {String|qxWeb} Html string or collection containing the rows to be added * @return {qx.ui.website.Table} <code>this</code> reference for chaining. */ setContent : function(tableData) { var rows = this.__extractTableRows(tableData); var tbody = this.find('tbody'); tbody.empty(); rows.appendTo(tbody); this.render(); return this; }, /** * Appends new rows to the table * @param tableData {String|qxWeb} Html string or collection containing the rows to be appended * @return {qx.ui.website.Table} <code>this</code> reference for chaining. */ appendContent : function(tableData) { var rows = this.__extractTableRows(tableData); var tbody = this.find('tbody'); rows.appendTo(tbody); this.render(); return this; }, /** * Extracts table rows from a given HTML String or qxWeb collection * @param data {qxWeb|String} Data containing the rows to be extracted * @return {qxWeb} Collection containing extracted rows */ __extractTableRows : function(data) { var rows = qxWeb(); if (typeof data == "string") { var markup = data; data = qxWeb.create(data); if (qxWeb.getNodeName(data) != "table") { data = qxWeb.create("<table>" + markup + "</table>"); } rows = data.find("tbody tr"); } else if (qxWeb.isNode(data) || (data instanceof qxWeb)) { data = qxWeb(data); var nodeName = qxWeb.getNodeName(data); switch (nodeName) { case "table": rows = qxWeb(data).find("tbody tr"); break; case "tr": rows = data; break; case "tbody": rows = qxWeb(data).find("tr"); break; } } return rows; }, /** * Filters the rendered table cells * @param keyword {String} The keyword to use to filter * @param columnName {String ?} The column name * @return {qx.ui.website.Table} <code>this</code> reference for chaining. */ __filterDom : function(keyword, columnName) { var colIndex = this.__getColumnIndex(columnName); var filterFunc = columnName == qx.ui.website.Table.__allColumnSelector ? this.getRowFilter() : this.getColumnFilter(columnName); filterFunc = filterFunc || this.__defaultColumnFilter; var rows = this.__getDataRows(), data = {}; for (var i = 0; i < rows.length; i++) { data = { columnName : columnName, columnIndex : colIndex, cell : colIndex > -1 ? qxWeb(rows[i].cells.item(colIndex)) : null, row : qxWeb(rows[i]), keyword : keyword }; if (!filterFunc.bind(this)(data)) { this.__filters[columnName].rows.appendChild(rows[i]); } } return this; }, /** * Get the current column sorting information for the first widget in the collection * @return {Map} The map containing the current sorting information */ getSortingData : function() { return this.__sortingData; }, //overridden render : function() { var sortingData = this.getSortingData(); var rowSelection = this.getConfig("rowSelection"); this.__applyTemplate(this.__model); if (qx.ui.website.Table.__selectionTypes.indexOf(rowSelection) != -1) { this.__processSelectionInputs(rowSelection); } if (sortingData) { this.__sortDOM(this.__sort(sortingData.columnName, sortingData.direction)); } return this; }, //Private API /** * Renders or removes the selection inputs according to the specified widget selection mode * @param rowSelection {String} The selection mode * @return {qx.ui.website.Table} <code>this</code> reference for chaining. */ __processSelectionInputs : function(rowSelection) { switch (rowSelection) { case "none": qxWeb("." + qx.ui.website.Table.__internalSelectionClass).remove(); break; case "multiple": case "single": this.__createInputs("checkbox"); break; case "single": this.__createInputs("radio"); break; } return this; }, /** * Creates input nodes for the row selection * @param type {String} The type of the inputs to creates * @return {qx.ui.website.Table} <code>this</code> reference for chaining. */ __createInputs : function(type) { this.__createInput(this.__getHeaderRow(), type); var rows = this.find("tbody")[0].getElementsByTagName("tr"); for (var i = 0; i < rows.length; i++) { this.__createInput(rows.item(i), type); } return this; }, /** * Creates an input an input node for a specific row * @param row {HTMLTableRowElement} The row to create the input for * @param type {String} The type of the input tom create (radio or checkbox) * @param nodeName {String} The nodename of the table cell that will contain the input */ __createInput : function(row, type, nodeName) { var cssPrefix = this.getCssPrefix(); var clazz = qx.ui.website.Table; var headerInput = qxWeb("." + clazz.__internalHeaderClass + " input"); var selectionMode = this.getConfig("rowSelection"); var checked = ""; if(headerInput.length > 0) { checked = (selectionMode == "multiple") && headerInput[0].checked ? "checked" : ""; } if (typeof nodeName == "undefined") { nodeName = qxWeb.getNodeName(qxWeb(row.cells.item(0))); } var inputName = this.__inputName; var className = (nodeName == "th") ? clazz.__internalSelectionClass + " " + clazz.__internalHeaderClass : clazz.__internalSelectionClass; var currentInput = qxWeb(row).find("."+clazz.__internalSelectionClass); if(currentInput.length > 0) { if(currentInput[0].type != type) { currentInput[0].type = type; } } else { var id = qx.ui.website.Table.__getUID(); var inputNode = qxWeb.create("<" + nodeName + " class='" + className + "'><input id='"+id+"' name='" + inputName + "' "+checked+" class='"+cssPrefix+"-"+type+" "+ clazz.__internalInputClass + "' type='" + type + "' /><label class='"+clazz.__inputLabelClass+"' for='"+id+"'></label></" + nodeName + ">"); if (row.cells.item(0)) { inputNode.insertBefore(qxWeb(row.cells.item(0))); } else { inputNode.appendTo(qxWeb(row)); } } }, /** * Checks if a column with the specified name exists * @param columnName {String} The name of the column to check */ __checkColumnExistance : function(columnName) { var data = this.__columnMeta; if (data && !data[columnName]) { throw new Error("Column " + columnName + " does not exists !"); } }, /** * Returns the row containing the cells with the column names * @return {HTMLTableRowElement} The row with meta information */ __getHeaderRow : function() { var tHeadOrFoot = this[0].tHead; if (!tHeadOrFoot) { throw new Error("A Table header element is required for this widget."); } var rows = tHeadOrFoot.rows; if(rows.length == 1) { return rows.item(0); } else { rows = qxWeb(".qx-table-header-row"); if(rows.length > 0) { return rows[0]; } } return null; }, /** * Initializes columns metadata * @param model {Array} The widget's model * @return {qx.ui.website.Table} <code>this</code> reference for chaining. */ __getColumnMetaData : function(model) { this.__addClassToHeaderAndFooter(this[0].tHead); this.__addClassToHeaderAndFooter(this[0].tFoot); var data = {}, cells = null, colName = null, cell = null; var headerRow = this.__getHeaderRow(); cells = headerRow.cells; for (var i = 0, l = cells.length; i < l; i++) { cell = qxWeb(cells.item(i)); colName = this.__getColumName(cell[0]) || qx.ui.website.Table.__getUID(); if(!cell[0].getAttribute(qx.ui.website.Table.__dataColName)){ cell.setAttribute(qx.ui.website.Table.__dataColName, colName); } data[colName] = { type: cell[0].getAttribute(qx.ui.website.Table.__dataColType) || "String", name: colName }; } this.__columnMeta = data; return this; }, /** * Adds the internal css class to the header and footer cells * @param footOrHead {HTMLElement} Html element representing the header or footer of the table * @return {qx.ui.website.Table} <code>this</code> reference for chaining. */ __addClassToHeaderAndFooter : function(footOrHead) { if (footOrHead && footOrHead.rows.length > 0) { if (footOrHead.rows.item(0).cells.length > 0) { var row = this.__getHeaderRow(); if (!qxWeb(row.cells.item(0)).hasClass(qx.ui.website.Table.__internalHeaderClass)) { qxWeb(row.cells).addClass(qx.ui.website.Table.__internalHeaderClass); } } } return this; }, /** * Sorts the rows of the table widget * @param dataRows {Array} Array containing the sorted rows * @return {qx.ui.website.Table} <code>this</code> reference for chaining. */ __sortDOM : function(dataRows) { for (var i = 0, l = dataRows.length; i < l; i++) { if (i) { qxWeb(dataRows[i]).insertAfter(dataRows[i - 1]); } else { qxWeb(dataRows[i]).insertBefore(qxWeb(this.__getRoot().rows.item(0))); } } return this; }, /** * registers global events * @return {qx.ui.website.Table} <code>this</code> reference for chaining. */ __registerEvents : function() { this.on("tap", this.__detectClickedCell); this.on("cellClick", function(data) { if(data.cell && data.cell.hasClass(qx.ui.website.Table.__internalHeaderClass)){ this.__sortingFunction.bind(this)(data); } }, this); this.on("pointerover", this.__cellHover, this); this.on("pointerout", this.__cellOut, this); return this; }, /** * Checks if the selection inputs are already rendered * @return {Boolean} True if the inputs are rendered and false otherwise */ __selectionRendered : function() { return qxWeb("." + qx.ui.website.Table.__internalSelectionClass).length > 0; }, /** * Handles clicks that happen on the selection inputs * @param cell {qxWeb} The table cell containing the clicked input * @return {qx.ui.website.Table} <code>this</code> reference for chaining. */ __processSelection : function(cell) { var clazz = qx.ui.website.Table; var inputs = qxWeb("." + clazz.__internalInputClass); var clickedInput = cell.find("input"); var selectionMode = this.getConfig("rowSelection"); var headerInput = qxWeb("." + clazz.__internalHeaderClass + " input"); var selection = []; if (selectionMode == "multiple") { if (cell.hasClass(clazz.__internalHeaderClass)) { inputs.setAttribute("checked", clickedInput[0].checked); } var checked = true; for (var i = 0; i < inputs.length; i++) { if ((inputs[i] != headerInput[0]) && (!inputs[i].checked)) { checked = false; break; } } headerInput.setAttribute("checked", checked); inputs = inputs.toArray(); if (checked) { qxWeb.array.remove(inputs, headerInput[0]); selection = inputs; } else { selection = inputs.filter(function(input) { return input.checked; }); } } else { if (clickedInput[0] != headerInput[0]) { selection.push(clickedInput[0]); } } var selectedRows = selection.map(function(elem) { return elem.parentNode.parentNode; }); selectedRows = qxWeb(selectedRows); qxWeb("."+clazz.__selectedRowClass).removeClass(clazz.__selectedRowClass); selectedRows.addClass(clazz.__selectedRowClass); this.emit("selectionChange", {rows : qxWeb(selectedRows)}); return this; }, /** * Fires a custom table events * @param eventType {String} The event type * @param cell {HTMLTableCellElement} The event target * @param target {HTMLElement} The native event target * @return {Map} Map containing the event data */ __fireEvent : function(eventType, cell, target) { var row = cell[0].parentNode, cells = row.cells; var colNumber = qx.ui.website.Table.__getIndex(cells, cell[0]); var tHead = this.__getHeaderRow(); var headCell = tHead.cells.item(colNumber); var colName = this.__getColumName(headCell); var columnIndex = this.getConfig("rowSelection") != "none" ? this.__getColumnIndex(colName) -1 : this.__getColumnIndex(colName); var data = { cell : qxWeb(cell), row : qxWeb(row), target : target, columnIndex : columnIndex, columnName : colName }; this.emit(eventType, data); return data; }, /** * Click callback * * @param e {Event} The native click event. * @return {qx.ui.website.Table} <code>this</code> reference for chaining. */ __detectClickedCell : function(e) { var target = e.getTarget(); var cell = qxWeb(target); var clazz = qx.ui.website.Table; while (!(cell.hasClass(clazz.__internalCellClass) || cell.hasClass(clazz.__internalHeaderClass) || cell.hasClass(clazz.__internalSelectionClass))) { if (cell.hasClass(this.classname)) { cell = null; break; } cell = cell.getParents().eq(0); } if (cell.hasClass(clazz.__internalSelectionClass)) { window.setTimeout(function(){ this.__processSelection(cell); }.bind(this), 5); }else{ if (cell && cell.length > 0) { this.__fireEvent("cellClick", cell, target); } } return this; }, /** * Pointerover callback * * @param e {Event} The native over event. */ __cellHover : function(e) { var target = e.getTarget(); var cell = qxWeb(target); var hovered = this.__hovered; if(!cell.hasClass("qx-table-cell") && !cell.hasClass("qx-table-header")) { cell = cell.getClosest(".qx-table-cell, .qx-table-header"); } if(cell && (cell.length > 0) && ((hovered && (hovered.cell[0] != cell[0])) || (!hovered)) && !cell.hasClass("qx-table-row-selection")) { if(hovered) { this.emit("cellOut", hovered); } this.__hovered = this.__fireEvent("cellHover", cell, target); } }, /** * pointerout callback * * @param e {Event} The native over event. */ __cellOut : function(e) { var relatedTarget = e.getRelatedTarget(); var cell = qxWeb(relatedTarget); if(this.__hovered) { if(!cell.isChildOf(this)) { this.emit("cellOut", this.__hovered); this.__hovered = null; }else { if(!cell.hasClass("qx-table-cell") && !cell.hasClass("qx-table-header")) { cell = cell.getClosest(".qx-table-cell, .qx-table-header"); if(cell.hasClass("qx-table-row-selection")) { this.emit("cellOut", this.__hovered); this.__hovered = null; } } } } }, /** * Applies the given model to the table cells depending on * the mustache template specified before * @param model {Array} The model to apply * @return {qx.ui.website.Table} <code>this</code> reference for chaining. */ __applyTemplate : function(model) { if(model && model.length > 0) { var cell, row; var tHead = this.__getHeaderRow(); var createdRow = null, colMeta = null; var renderedRow = null; var inputType = (this.getConfig("rowSelection") == "single") ? "radio" : "checkbox"; if (this.__getRoot().rows.length > model.length) { this.__deleteRows(model.length); } var renderedColIndex = 0, templateApplied = false; var coltemplate = this.getTemplate("columnDefault"); var colName = null; for (var i = 0, rowCount = model.length; i < rowCount; i++) { row = model[i]; if (!this.__isRowRendered(i)) { createdRow = this.__getRoot().insertRow(i); if (this.__selectionRendered()) { this.__createInput(createdRow, inputType, "td"); } } for (var j = 0, colCount = row.length; j < colCount; j++) { renderedColIndex = this.__selectionRendered() ? j + 1 : j; colName = this.__getColumName(tHead.cells.item(renderedColIndex)); colMeta = this.__getDataForColumn(colName); coltemplate = this.getTemplate(colName) || coltemplate; renderedRow = this.__getRoot().rows.item(i); cell = qxWeb.create(qxWeb.template.render(coltemplate, model[i][j]))[0]; if(cell.nodeName.toUpperCase() != "TD") { break; } if (!this.__isCellRendered(i, renderedColIndex)) { renderedRow.appendChild(cell); }else { renderedRow.replaceChild(cell, this.getCell(i, renderedColIndex)[0]); } this.emit("cellRender", {cell : cell, row : i, col : j, value : model[i][j]}); } if(i == rowCount-1) { templateApplied = true; } } if (templateApplied) { this.emit("modelApplied", model); } } return this; }, /** * Removes row from the DOM starting from the specified index * @param rowCount {Integer} The number of rows the kept * @return {qx.ui.website.Table} <code>this</code> reference for chaining. */ __deleteRows : function(rowCount) { var renderedRows = this.__getRoot().rows; while(renderedRows.length > rowCount){ this[0].deleteRow(renderedRows.length); } return this; }, /** * Gets the metadata of the column width the specified name * @param columName {String} The name of the column to get the metadata for * @return {Map} Map containing the metadata */ __getDataForColumn : function(columName) { return this.__columnMeta[columName]; }, /** * Gets the Root element containing the data rows * @return {HTMLElement} The element containing the data rows */ __getRoot : function() { return this[0].tBodies.item(0) || this[0]; }, /** * Checks if the row with the given index is rendered * @param index {Integer} The index of the row to check * @return {Boolean} The result of the check */ __isRowRendered : function(index) { if (this.__getRoot().rows.item(index)) { return true; } return false; }, /** * Checks if the cell with the given row and column indexes is rendered * @param rowIndex {Integer} The index of the row to check * @param colIndex {Integer} The index of the column * @return {Boolean} The result of the check */ __isCellRendered : function(rowIndex, colIndex) { if (!this.__isRowRendered(rowIndex)) { return false; } if (this.__getRoot().rows.item(rowIndex).cells.item(colIndex)) { return true; } return false; }, /** * Adds a class to the head and footer of the current sorted column * @param columnName {String} The name of the sorted column * @param dir {String} The sorting direction */ setSortingClass : function(columnName, dir) { var data = { columnName: columnName, direction: dir }; this.__sortingData = data; this.__addSortingClassToCol(this[0].tHead, columnName, dir); }, /** * Adds a class to the head or footer of the current sorted column * @param HeaderOrFooter {Node} The n * @param columnName {String} The name of the sorted column * @param dir {String} The sorting direction */ __addSortingClassToCol : function(HeaderOrFooter, columnName, dir) { var rows = this.__getHeaderRow(); if (HeaderOrFooter && rows) { qxWeb(rows.cells).removeClasses(["qx-table-sort-asc", "qx-table-sort-desc"]); var cell = qxWeb("["+qx.ui.website.Table.__dataColName+"='" + columnName + "'], #" + columnName); cell.addClass("qx-table-sort-" + dir); } }, /** * Sorts the table rows for the given row and direction * @param columnName {String} The name of the column to be sorted * @param direction {String} The sorting direction * @return {Array} Array containing the sorted rows */ __sort : function(columnName, direction) { var meta = this.__getDataForColumn(columnName); var columnType = qxWeb.string.firstUp(meta.type); if(!this["_compare" + columnType] && !this.getProperty("_compare"+ columnType)) { columnType = "String"; } var compareFunc = this.getCompareFunction(columnType).bind(this); var model = this.__getDataRows(); var columnIndex = this.__getColumnIndex(columnName); return model.sort(function(a, b) { var x = this.__getSortingKey(qxWeb(a.cells.item(columnIndex))); var y = this.__getSortingKey(qxWeb(b.cells.item(columnIndex))); return compareFunc(x, y, direction); }.bind(this)); }, /** * Compares two number * @param x {String} The String value of the first number to compare * @param y {String} The String value of the second number to compare * @param direction {String} The sorting direction * @return {Integer} The result of the comparison */ _compareNumber : function(x, y, direction) { x = qx.ui.website.Table.__isNumber(x) ? Number(x) : 0; y = qx.ui.website.Table.__isNumber(y) ? Number(y) : 0; if (direction == "asc") { return x - y; } else if (direction == "desc") { return y - x; } return 0; }, /** * Gets the name of the column containing the given cell * @param headerCell {HTMLTableCellElement} The cell to get the column name for * @return {String} The column name */ __getColumName : function(headerCell) { return headerCell.getAttribute(qx.ui.website.Table.__dataColName) || headerCell.getAttribute("id"); }, /** * Compares two Dates * @param x {String} The String value of the first date to compare * @param y {String} The String value of the second date to compare * @param direction {String} The sorting direction * @return {Integer} The result of the comparison */ _compareDate : function(x, y, direction) { x = qx.ui.website.Table.__isDate(x) ? new Date(x) : new Date(0); y = qx.ui.website.Table.__isDate(y) ? new Date(y) : new Date(0); if (direction == "asc") { return x - y; } else if (direction == "desc") { return y - x; } return 0; }, /** * Compares two Strings * @param x {String} The first string to compare * @param y {String} The second string to compare * @param direction {String} The sorting direction * @return {Integer} The result of the comparison */ _compareString : function(x, y, direction) { if (!this.getConfig("caseSensitive")) { x = x.toLowerCase(); y = y.toLowerCase(); } if (direction == "asc") { return ((x < y) ? -1 : ((x > y) ? 1 : 0)); } else if (direction == "desc") { return ((x > y) ? -1 : ((x < y) ? 1 : 0)); } return 0; }, /** * Returns the value of the cell to use for sorting * @param cell {qxWeb} The cell to get the value of. * @return {String} The sorting key */ __getSortingKey : function(cell) { return cell.getAttribute(qx.ui.website.Table.__dataSortingKey) || this.__getCellValue(cell); }, /** * Returns the value of the cell that will be used for sorting * @param cell {qxWeb} The cell to get the value of * @return {String} The text content of the cell */ __getCellValue : function(cell) { return cell[0].textContent || cell[0].innerText || ""; }, /** * Gets the table's data rows from the DOM * @return {Array} Array containing the rows of the table */ __getDataRows : function() { var rows = this.find("tbody")[0].rows, model = [], cell=null, cells = []; for (var i = 0, l = rows.length; i < l; i++) { cells = rows.item(i).cells; if ((cells.length > 0) && (cells[0].nodeName.toUpperCase() != "TD")) { continue; } for (var j = 0, len = cells.length; j < len; j++) { cell = qxWeb(cells[j]); if (!cell.hasClass(qx.ui.website.Table.__internalCellClass)) { cell.addClass(qx.ui.website.Table.__internalCellClass); } } model.push(rows.item(i)); } return model; }, /** * Default sorting processing * @param data {Map} Sorting data */ __defaultColumnSort : function(data) { var dir = "asc"; var sortedData = this.getSortingData(); if (sortedData) { if (data.columnName == sortedData.columnName) { if (sortedData.direction == dir) { dir = "desc"; } } } if (data.cell.hasClass("qx-table-header")) { this.sort(data.columnName, dir); } }, /** * Default column filter function * @param data {Map} Map containing the filter data * @return {Boolean} True wenn the row containing the current cell should be kept */ __defaultColumnFilter : function(data) { var caseSensitive = this.getConfig("caseSensitive"); var cell = data.columnName == qx.ui.website.Table.__allColumnSelector ? data.row : data.cell; var cellValue = this.__getCellValue(cell); if(caseSensitive){ return cellValue.indexOf(data.keyword) != -1; }else{ return cellValue.toLowerCase().indexOf(data.keyword.toLowerCase()) != -1; } }, /** * Gets the index of the column with the specified name * @param columnName {String} The colukn name * @return {Integer} The index of the column or -1 if the column doesn't exists */ __getColumnIndex : function(columnName) { var tHead = this.__getHeaderRow(); var cells = tHead.cells; for (var i = 0; i < cells.length; i++) { if (columnName == this.__getColumName(cells.item(i))) { return i; } } return -1; } }, defer : function(statics) { qxWeb.$attach({ table: statics.table }); } });