diff --git a/.gitignore b/.gitignore index 1cc5b6e..f328077 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ src/*.map *.diff *.patch .DS_Store -settings.json \ No newline at end of file +settings.json +.idea diff --git a/dist/jquery.bootgrid.js b/dist/jquery.bootgrid.js index 4f27d44..64d1d62 100644 --- a/dist/jquery.bootgrid.js +++ b/dist/jquery.bootgrid.js @@ -246,6 +246,8 @@ renderNoResultsRow.call(that); // overrides loading mask that.element._bgBusyAria(false).trigger("loaded" + namespace); } + + that.options.ajaxError(jqXHR, textStatus, errorThrown); } }; settings = $.extend(this.options.ajaxSettings, settings); @@ -1190,6 +1192,15 @@ * @since 1.1.0 **/ responseHandler: function (response) { return response; }, + + /** + * Exposes Ajax Error event to callee + * @property ajaxError + * @type Function + * @default function(jqXHR, textStatus, errorThrown) {}; + * @for defaults + * */ + ajaxError: function(jqXHR, textStatus, errorThrown) {}, /** * A list of converters. @@ -2039,4 +2050,4 @@ // ============ $("[data-toggle=\"bootgrid\"]").bootgrid(); -})(jQuery, window); \ No newline at end of file +})(jQuery, window); diff --git a/src/extensions.js b/src/extensions.js index cb37139..1bd4d67 100644 --- a/src/extensions.js +++ b/src/extensions.js @@ -1,170 +1,182 @@ // GRID COMMON TYPE EXTENSIONS // ============ -$.fn.extend({ - _bgAria: function (name, value) - { - return (value) ? this.attr("aria-" + name, value) : this.attr("aria-" + name); +$.fn.extend( + { + _bgAria : function(name, value) { + return (value) ? this.attr("aria-" + name, value) : this.attr("aria-" + name); }, - - _bgBusyAria: function(busy) - { - return (busy == null || busy) ? - this._bgAria("busy", "true") : - this._bgAria("busy", "false"); + + _bgBusyAria : function(busy) { + return (busy == null || busy) ? + this._bgAria("busy", "true") : + this._bgAria("busy", "false"); }, - - _bgRemoveAria: function (name) - { - return this.removeAttr("aria-" + name); + + _bgRemoveAria : function(name) { + return this.removeAttr("aria-" + name); }, - - _bgEnableAria: function (enable) - { - return (enable == null || enable) ? - this.removeClass("disabled")._bgAria("disabled", "false") : - this.addClass("disabled")._bgAria("disabled", "true"); + + _bgEnableAria : function(enable) { + return (enable == null || enable) ? + this.removeClass("disabled")._bgAria("disabled", "false") : + this.addClass("disabled")._bgAria("disabled", "true"); }, - - _bgEnableField: function (enable) - { - return (enable == null || enable) ? - this.removeAttr("disabled") : - this.attr("disabled", "disable"); + + _bgEnableField : function(enable) { + return (enable == null || enable) ? + this.removeAttr("disabled") : + this.attr("disabled", "disable"); }, - - _bgShowAria: function (show) - { - return (show == null || show) ? - this.show()._bgAria("hidden", "false") : - this.hide()._bgAria("hidden", "true"); + + _bgShowAria : function(show) { + return (show == null || show) ? + this.show()._bgAria("hidden", "false") : + this.hide()._bgAria("hidden", "true"); }, - - _bgSelectAria: function (select) - { - return (select == null || select) ? - this.addClass("active")._bgAria("selected", "true") : - this.removeClass("active")._bgAria("selected", "false"); + + _bgSelectAria : function(select) { + return (select == null || select) ? + this.addClass("active")._bgAria("selected", "true") : + this.removeClass("active")._bgAria("selected", "false"); }, - - _bgId: function (id) - { - return (id) ? this.attr("id", id) : this.attr("id"); + + _bgId : function(id) { + return (id) ? this.attr("id", id) : this.attr("id"); } -}); + } +); -if (!String.prototype.resolve) -{ - var formatter = { - "checked": function(value) - { - if (typeof value === "boolean") - { - return (value) ? "checked=\"checked\"" : ""; - } - return value; +if (!String.prototype.resolve) { + var formatter = { + "checked" : function(value) { + if (typeof value === "boolean") { + return (value) ? "checked=\"checked\"" : ""; + } + return value; + } + }; + + var _templateCache = {}; + var getTemplate = function(template) { + if (!_templateCache.hasOwnProperty(template)) { + var str = template.split(/{([^{}]+)}/g); + + for (var i = 0; i < str.length; i++) { + var s = str[ i ]; + var hasStart = (s.charAt(0) === "}"); + var hasEnd = (s.charAt(s.length - 1) === "{"); + if (hasStart) { + s = s.substr(1); } - }; - - String.prototype.resolve = function (substitutes, prefixes) - { - var result = this; - $.each(substitutes, function (key, value) - { - if (value != null && typeof value !== "function") - { - if (typeof value === "object") - { - var keys = (prefixes) ? $.extend([], prefixes) : []; - keys.push(key); - result = result.resolve(value, keys) + ""; - } - else - { - if (formatter && formatter[key] && typeof formatter[key] === "function") - { - value = formatter[key](value); - } - key = (prefixes) ? prefixes.join(".") + "." + key : key; - var pattern = new RegExp("\\{\\{" + key + "\\}\\}", "gm"); - result = result.replace(pattern, (value.replace) ? value.replace(/\$/gi, "$") : value); - } - } - }); - return result; - }; + if (hasEnd) { + s = s.substr(0, s.length - 1); + } + + if (hasStart || hasEnd) { + str[ i ] = s; //plain old html + } else { + str[ i ] = { + token : str[ i ], + key : s.split(".") + }; + } + } + _templateCache[ template ] = str; + } + return _templateCache[ template ]; + }; + + String.prototype.resolve = function(substitutes, prefixes) { + var str = getTemplate(this); + var result = ""; + for (var i = 0; i < str.length; i++) { + if (typeof str[ i ] === "object") { + var key = str[ i ].key; + var v = ""; + // now we have a variable to be substitued + if (substitutes.hasOwnProperty(key[ 0 ])) { + v = substitutes[ key[ 0 ] ]; + } + else { + continue; + } + + for (var k = 1; k < key.length; k++) { + if (v.hasOwnProperty(key[ k ])) { + v = v[ key[ k ] ]; + } else { + v = null; + break; + } + } + var formatter_key = key[ key.length - 1 ]; + if (formatter && formatter[ formatter_key ] && typeof formatter[ formatter_key ] === "function") { + result += formatter[ formatter_key ](v); + } else { + result += v; + } + } else { + result += str[ i ]; // plain old html + } + } + return result; + }; } -if (!Array.prototype.first) -{ - Array.prototype.first = function (condition) - { - for (var i = 0; i < this.length; i++) - { - var item = this[i]; - if (condition(item)) - { - return item; - } - } - return null; - }; +if (!Array.prototype.first) { + Array.prototype.first = function(condition) { + for (var i = 0; i < this.length; i++) { + var item = this[ i ]; + if (condition(item)) { + return item; + } + } + return null; + }; } -if (!Array.prototype.contains) -{ - Array.prototype.contains = function (condition) - { - for (var i = 0; i < this.length; i++) - { - var item = this[i]; - if (condition(item)) - { - return true; - } - } - return false; - }; +if (!Array.prototype.contains) { + Array.prototype.contains = function(condition) { + for (var i = 0; i < this.length; i++) { + var item = this[ i ]; + if (condition(item)) { + return true; + } + } + return false; + }; } -if (!Array.prototype.page) -{ - Array.prototype.page = function (page, size) - { - var skip = (page - 1) * size, - end = skip + size; - return (this.length > skip) ? - (this.length > end) ? this.slice(skip, end) : - this.slice(skip) : []; - }; +if (!Array.prototype.page) { + Array.prototype.page = function(page, size) { + var skip = (page - 1) * size, + end = skip + size; + return (this.length > skip) ? + (this.length > end) ? this.slice(skip, end) : + this.slice(skip) : []; + }; } -if (!Array.prototype.where) -{ - Array.prototype.where = function (condition) - { - var result = []; - for (var i = 0; i < this.length; i++) - { - var item = this[i]; - if (condition(item)) - { - result.push(item); - } - } - return result; - }; +if (!Array.prototype.where) { + Array.prototype.where = function(condition) { + var result = []; + for (var i = 0; i < this.length; i++) { + var item = this[ i ]; + if (condition(item)) { + result.push(item); + } + } + return result; + }; } -if (!Array.prototype.propValues) -{ - Array.prototype.propValues = function (propName) - { - var result = []; - for (var i = 0; i < this.length; i++) - { - result.push(this[i][propName]); - } - return result; - }; -} \ No newline at end of file +if (!Array.prototype.propValues) { + Array.prototype.propValues = function(propName) { + var result = []; + for (var i = 0; i < this.length; i++) { + result.push(this[ i ][ propName ]); + } + return result; + }; +} diff --git a/src/fontawesome.js b/src/fontawesome.js index 6be8c30..7a6e1ca 100644 --- a/src/fontawesome.js +++ b/src/fontawesome.js @@ -1,8 +1,10 @@ -$.extend($.fn.bootgrid.Constructor.defaults.css, { - icon: "icon fa", - iconColumns: "fa-th-list", - iconDown: "fa-sort-desc", - iconRefresh: "fa-refresh", - iconSearch: "fa-search", - iconUp: "fa-sort-asc" -}); \ No newline at end of file +$.extend( + $.fn.bootgrid.Constructor.defaults.css, { + icon : "icon fa", + iconColumns : "fa-th-list", + iconDown : "fa-sort-desc", + iconRefresh : "fa-refresh", + iconSearch : "fa-search", + iconUp : "fa-sort-asc" + } +); diff --git a/src/internal.js b/src/internal.js index 9b9bba4..0b200a9 100644 --- a/src/internal.js +++ b/src/internal.js @@ -6,943 +6,908 @@ var namespace = ".rs.jquery.bootgrid"; // GRID INTERNAL FUNCTIONS // ===================== -function appendRow(row) -{ - var that = this; - - function exists(item) - { - return that.identifier && item[that.identifier] === row[that.identifier]; - } - - if (!this.rows.contains(exists)) - { - this.rows.push(row); - return true; - } - - return false; +function appendRow(row) { + var that = this; + + function exists(item) { + return that.identifier && item[ that.identifier ] === row[ that.identifier ]; + } + + if (!this.rows.contains(exists)) { + this.rows.push(row); + return true; + } + + return false; } -function findFooterAndHeaderItems(selector) -{ - var footer = (this.footer) ? this.footer.find(selector) : $(), - header = (this.header) ? this.header.find(selector) : $(); - return $.merge(footer, header); +function findFooterAndHeaderItems(selector) { + var footer = (this.footer) ? this.footer.find(selector) : $(), + header = (this.header) ? this.header.find(selector) : $(); + return $.merge(footer, header); } -function getParams(context) -{ - return (context) ? $.extend({}, this.cachedParams, { ctx: context }) : - this.cachedParams; +function getParams(context) { + return (context) ? $.extend({}, this.cachedParams, { ctx : context }) : + this.cachedParams; } -function getRequest() -{ - var request = { - current: this.current, - rowCount: this.rowCount, - sort: this.sortDictionary, - searchPhrase: this.searchPhrase - }, - post = this.options.post; - - post = ($.isFunction(post)) ? post() : post; - return this.options.requestHandler($.extend(true, request, post)); +function getRequest() { + var request = { + current : this.current, + rowCount : this.rowCount, + sort : this.sortDictionary, + searchPhrase : this.searchPhrase + }, + post = this.options.post; + + post = ($.isFunction(post)) ? post() : post; + return this.options.requestHandler($.extend(true, request, post)); } -function getCssSelector(css) -{ - return "." + $.trim(css).replace(/\s+/gm, "."); +function getCssSelector(css) { + return "." + $.trim(css).replace(/\s+/gm, "."); } -function getUrl() -{ - var url = this.options.url; - return ($.isFunction(url)) ? url() : url; +function getUrl() { + var url = this.options.url; + return ($.isFunction(url)) ? url() : url; } -function init() -{ - this.element.trigger("initialize" + namespace); - - loadColumns.call(this); // Loads columns from HTML thead tag - this.selection = this.options.selection && this.identifier != null; - loadRows.call(this); // Loads rows from HTML tbody tag if ajax is false - prepareTable.call(this); - renderTableHeader.call(this); - renderSearchField.call(this); - renderActions.call(this); - loadData.call(this); - - this.element.trigger("initialized" + namespace); +function init() { + this.element.trigger("initialize" + namespace); + + loadColumns.call(this); // Loads columns from HTML thead tag + this.selection = this.options.selection && this.identifier != null; + loadRows.call(this); // Loads rows from HTML tbody tag if ajax is false + prepareTable.call(this); + renderTableHeader.call(this); + renderSearchField.call(this); + renderActions.call(this); + loadData.call(this); + + this.element.trigger("initialized" + namespace); } -function highlightAppendedRows(rows) -{ - if (this.options.highlightRows) - { - // todo: implement - } +function highlightAppendedRows(rows) { + if (this.options.highlightRows) { + // todo: implement + } } -function isVisible(column) -{ - return column.visible; +function isVisible(column) { + return column.visible; } -function loadColumns() -{ - var that = this, - firstHeadRow = this.element.find("thead > tr").first(), - sorted = false; - - /*jshint -W018*/ - firstHeadRow.children().each(function () - { - var $this = $(this), - data = $this.data(), - column = { - id: data.columnId, - identifier: that.identifier == null && data.identifier || false, - converter: that.options.converters[data.converter || data.type] || that.options.converters["string"], - text: $this.text(), - align: data.align || "left", - headerAlign: data.headerAlign || "left", - cssClass: data.cssClass || "", - headerCssClass: data.headerCssClass || "", - formatter: that.options.formatters[data.formatter] || null, - order: (!sorted && (data.order === "asc" || data.order === "desc")) ? data.order : null, - searchable: !(data.searchable === false), // default: true - sortable: !(data.sortable === false), // default: true - visible: !(data.visible === false), // default: true - visibleInSelection: !(data.visibleInSelection === false), // default: true - width: ($.isNumeric(data.width)) ? data.width + "px" : - (typeof(data.width) === "string") ? data.width : null - }; - that.columns.push(column); - if (column.order != null) - { - that.sortDictionary[column.id] = column.order; - } - - // Prevents multiple identifiers - if (column.identifier) - { - that.identifier = column.id; - that.converter = column.converter; - } - - // ensures that only the first order will be applied in case of multi sorting is disabled - if (!that.options.multiSort && column.order !== null) - { - sorted = true; - } - }); - /*jshint +W018*/ +function loadColumns() { + var that = this, + firstHeadRow = this.element.find("thead > tr").first(), + sorted = false; + + /*jshint -W018*/ + firstHeadRow.children().each( + function() { + var $this = $(this), + data = $this.data(), + column = { + id : data.columnId, + identifier : that.identifier == null && data.identifier || false, + converter : that.options.converters[ data.converter || data.type ] || + that.options.converters[ "string" ], + text : $this.text(), + align : data.align || "left", + headerAlign : data.headerAlign || "left", + cssClass : data.cssClass || "", + headerCssClass : data.headerCssClass || "", + formatter : that.options.formatters[ data.formatter ] || null, + order : (!sorted && (data.order === "asc" || data.order === "desc")) ? data.order : null, + searchable : !(data.searchable === false), // default: true + sortable : !(data.sortable === false), // default: true + visible : !(data.visible === false), // default: true + visibleInSelection : !(data.visibleInSelection === false), // default: true + width : ($.isNumeric(data.width)) ? data.width + "px" : + (typeof(data.width) === "string") ? data.width : null + }; + that.columns.push(column); + if (column.order != null) { + that.sortDictionary[ column.id ] = column.order; + } + + // Prevents multiple identifiers + if (column.identifier) { + that.identifier = column.id; + that.converter = column.converter; + } + + // ensures that only the first order will be applied in case of multi sorting is disabled + if (!that.options.multiSort && column.order !== null) { + sorted = true; + } + } + ); + /*jshint +W018*/ } /* -response = { - current: 1, - rowCount: 10, - rows: [{}, {}], - sort: [{ "columnId": "asc" }], - total: 101 -} -*/ - -function loadData() -{ - var that = this; - - this.element._bgBusyAria(true).trigger("load" + namespace); - showLoading.call(this); - - function containsPhrase(row) - { - var column, - searchPattern = new RegExp(that.searchPhrase, (that.options.caseSensitive) ? "g" : "gi"); - - for (var i = 0; i < that.columns.length; i++) - { - column = that.columns[i]; - if (column.searchable && column.visible && - column.converter.to(row[column.id]).search(searchPattern) > -1) - { - return true; - } - } - - return false; - } - - function update(rows, total) - { - that.currentRows = rows; - setTotals.call(that, total); - - if (!that.options.keepSelection) - { - that.selectedRows = []; - } - - renderRows.call(that, rows); - renderInfos.call(that); - renderPagination.call(that); - - that.element._bgBusyAria(false).trigger("loaded" + namespace); - } - - if (this.options.ajax) - { - var request = getRequest.call(this), - url = getUrl.call(this); - - if (url == null || typeof url !== "string" || url.length === 0) - { - throw new Error("Url setting must be a none empty string or a function that returns one."); - } - - // aborts the previous ajax request if not already finished or failed - if (this.xqr) - { - this.xqr.abort(); - } - - var settings = { - url: url, - data: request, - success: function(response) - { - that.xqr = null; - - if (typeof (response) === "string") - { - response = $.parseJSON(response); - } - - response = that.options.responseHandler(response); - - that.current = response.current; - update(response.rows, response.total); - }, - error: function (jqXHR, textStatus, errorThrown) - { - that.xqr = null; - - if (textStatus !== "abort") - { - renderNoResultsRow.call(that); // overrides loading mask - that.element._bgBusyAria(false).trigger("loaded" + namespace); - } - } - }; - settings = $.extend(this.options.ajaxSettings, settings); - - this.xqr = $.ajax(settings); - } - else - { - var rows = (this.searchPhrase.length > 0) ? this.rows.where(containsPhrase) : this.rows, - total = rows.length; - if (this.rowCount !== -1) - { - rows = rows.page(this.current, this.rowCount); - } - - // todo: improve the following comment - // setTimeout decouples the initialization so that adding event handlers happens before - window.setTimeout(function () { update(rows, total); }, 10); - } -} - -function loadRows() -{ - if (!this.options.ajax) - { - var that = this, - rows = this.element.find("tbody > tr"); - - rows.each(function () - { - var $this = $(this), - cells = $this.children("td"), - row = {}; - - $.each(that.columns, function (i, column) - { - row[column.id] = column.converter.from(cells.eq(i).text()); - }); - - appendRow.call(that, row); - }); - - setTotals.call(this, this.rows.length); - sortRows.call(this); + response = { + current: 1, + rowCount: 10, + rows: [{}, {}], + sort: [{ "columnId": "asc" }], + total: 101 + } + */ + +function loadData() { + var that = this; + + this.element._bgBusyAria(true).trigger("load" + namespace); + showLoading.call(this); + + function containsPhrase(row) { + var column, + searchPattern = new RegExp(that.searchPhrase, (that.options.caseSensitive) ? "g" : "gi"); + + for (var i = 0; i < that.columns.length; i++) { + column = that.columns[ i ]; + if (column.searchable && column.visible && + column.converter.to(row[ column.id ]).search(searchPattern) > -1) { + return true; + } } -} - -function setTotals(total) -{ - this.total = total; - this.totalPages = (this.rowCount === -1) ? 1 : - Math.ceil(this.total / this.rowCount); -} - -function prepareTable() -{ - var tpl = this.options.templates, - wrapper = (this.element.parent().hasClass(this.options.css.responsiveTable)) ? - this.element.parent() : this.element; - - this.element.addClass(this.options.css.table); - - // checks whether there is an tbody element; otherwise creates one - if (this.element.children("tbody").length === 0) - { - this.element.append(tpl.body); + + return false; + } + + function update(rows, total) { + that.currentRows = rows; + setTotals.call(that, total); + + if (!that.options.keepSelection) { + that.selectedRows = []; } - - if (this.options.navigation & 1) - { - this.header = $(tpl.header.resolve(getParams.call(this, { id: this.element._bgId() + "-header" }))); - wrapper.before(this.header); + + renderRows.call(that, rows); + renderInfos.call(that); + renderPagination.call(that); + + that.element._bgBusyAria(false).trigger("loaded" + namespace); + } + + if (this.options.ajax) { + var request = getRequest.call(this), + url = getUrl.call(this); + + if (url == null || typeof url !== "string" || url.length === 0) { + throw new Error("Url setting must be a none empty string or a function that returns one."); } - - if (this.options.navigation & 2) - { - this.footer = $(tpl.footer.resolve(getParams.call(this, { id: this.element._bgId() + "-footer" }))); - wrapper.after(this.footer); + + // aborts the previous ajax request if not already finished or failed + if (this.xqr) { + this.xqr.abort(); } -} - -function renderActions() -{ - if (this.options.navigation !== 0) - { - var css = this.options.css, - selector = getCssSelector(css.actions), - actionItems = findFooterAndHeaderItems.call(this, selector); - - if (actionItems.length > 0) - { - var that = this, - tpl = this.options.templates, - actions = $(tpl.actions.resolve(getParams.call(this))); - - // Refresh Button - if (this.options.ajax) - { - var refreshIcon = tpl.icon.resolve(getParams.call(this, { iconCss: css.iconRefresh })), - refresh = $(tpl.actionButton.resolve(getParams.call(this, - { content: refreshIcon, text: this.options.labels.refresh }))) - .on("click" + namespace, function (e) - { - // todo: prevent multiple fast clicks (fast click detection) - e.stopPropagation(); - that.current = 1; - loadData.call(that); - }); - actions.append(refresh); - } - - // Row count selection - renderRowCountSelection.call(this, actions); - - // Column selection - renderColumnSelection.call(this, actions); - - replacePlaceHolder.call(this, actionItems, actions); + + var settings = { + url : url, + data : request, + success : function(response) { + that.xqr = null; + + if (typeof (response) === "string") { + response = $.parseJSON(response); } + + response = that.options.responseHandler(response); + + that.current = response.current; + update(response.rows, response.total); + }, + error : function(jqXHR, textStatus, errorThrown) { + that.xqr = null; + + if (textStatus !== "abort") { + renderNoResultsRow.call(that); // overrides loading mask + that.element._bgBusyAria(false).trigger("loaded" + namespace); + } + } + }; + settings = $.extend(this.options.ajaxSettings, settings); + + this.xqr = $.ajax(settings); + } + else { + var rows = (this.searchPhrase.length > 0) ? this.rows.where(containsPhrase) : this.rows, + total = rows.length; + if (this.rowCount !== -1) { + rows = rows.page(this.current, this.rowCount); } + + // todo: improve the following comment + // setTimeout decouples the initialization so that adding event handlers happens before + window.setTimeout( + function() { + update(rows, total); + }, 10 + ); + } } -function renderColumnSelection(actions) -{ - if (this.options.columnSelection && this.columns.length > 1) - { - var that = this, - css = this.options.css, - tpl = this.options.templates, - icon = tpl.icon.resolve(getParams.call(this, { iconCss: css.iconColumns })), - dropDown = $(tpl.actionDropDown.resolve(getParams.call(this, { content: icon }))), - selector = getCssSelector(css.dropDownItem), - checkboxSelector = getCssSelector(css.dropDownItemCheckbox), - itemsSelector = getCssSelector(css.dropDownMenuItems); - - $.each(this.columns, function (i, column) - { - if (column.visibleInSelection) - { - var item = $(tpl.actionDropDownCheckboxItem.resolve(getParams.call(that, - { name: column.id, label: column.text, checked: column.visible }))) - .on("click" + namespace, selector, function (e) - { - e.stopPropagation(); +function loadRows() { + if (!this.options.ajax) { + var that = this, + rows = this.element.find("tbody > tr"); - var $this = $(this), - checkbox = $this.find(checkboxSelector); - if (!checkbox.prop("disabled")) - { - column.visible = checkbox.prop("checked"); - var enable = that.columns.where(isVisible).length > 1; - $this.parents(itemsSelector).find(selector + ":has(" + checkboxSelector + ":checked)") - ._bgEnableAria(enable).find(checkboxSelector)._bgEnableField(enable); + rows.each( + function() { + var $this = $(this), + cells = $this.children("td"), + row = {}; + + $.each( + that.columns, function(i, column) { + row[ column.id ] = column.converter.from(cells.eq(i).text()); + } + ); + + appendRow.call(that, row); + } + ); - that.element.find("tbody").empty(); // Fixes an column visualization bug - renderTableHeader.call(that); - loadData.call(that); - } - }); - dropDown.find(getCssSelector(css.dropDownMenuItems)).append(item); - } - }); - actions.append(dropDown); - } + setTotals.call(this, this.rows.length); + sortRows.call(this); + } } -function renderInfos() -{ - if (this.options.navigation !== 0) - { - var selector = getCssSelector(this.options.css.infos), - infoItems = findFooterAndHeaderItems.call(this, selector); - - if (infoItems.length > 0) - { - var end = (this.current * this.rowCount), - infos = $(this.options.templates.infos.resolve(getParams.call(this, { - end: (this.total === 0 || end === -1 || end > this.total) ? this.total : end, - start: (this.total === 0) ? 0 : (end - this.rowCount + 1), - total: this.total - }))); - - replacePlaceHolder.call(this, infoItems, infos); - } - } +function setTotals(total) { + this.total = total; + this.totalPages = (this.rowCount === -1) ? 1 : + Math.ceil(this.total / this.rowCount); } -function renderNoResultsRow() -{ - var tbody = this.element.children("tbody").first(), - tpl = this.options.templates, - count = this.columns.where(isVisible).length; - - if (this.selection) - { - count = count + 1; - } - tbody.html(tpl.noResults.resolve(getParams.call(this, { columns: count }))); +function prepareTable() { + var tpl = this.options.templates, + wrapper = (this.element.parent().hasClass(this.options.css.responsiveTable)) ? + this.element.parent() : this.element; + + this.element.addClass(this.options.css.table); + + // checks whether there is an tbody element; otherwise creates one + if (this.element.children("tbody").length === 0) { + this.element.append(tpl.body); + } + + if (this.options.navigation & 1) { + this.header = $(tpl.header.resolve(getParams.call(this, { id : this.element._bgId() + "-header" }))); + wrapper.before(this.header); + } + + if (this.options.navigation & 2) { + this.footer = $(tpl.footer.resolve(getParams.call(this, { id : this.element._bgId() + "-footer" }))); + wrapper.after(this.footer); + } } -function renderPagination() -{ - if (this.options.navigation !== 0) - { - var selector = getCssSelector(this.options.css.pagination), - paginationItems = findFooterAndHeaderItems.call(this, selector)._bgShowAria(this.rowCount !== -1); - - if (this.rowCount !== -1 && paginationItems.length > 0) - { - var tpl = this.options.templates, - current = this.current, - totalPages = this.totalPages, - pagination = $(tpl.pagination.resolve(getParams.call(this))), - offsetRight = totalPages - current, - offsetLeft = (this.options.padding - current) * -1, - startWith = ((offsetRight >= this.options.padding) ? - Math.max(offsetLeft, 1) : - Math.max((offsetLeft - this.options.padding + offsetRight), 1)), - maxCount = this.options.padding * 2 + 1, - count = (totalPages >= maxCount) ? maxCount : totalPages; - - renderPaginationItem.call(this, pagination, "first", "«", "first") - ._bgEnableAria(current > 1); - renderPaginationItem.call(this, pagination, "prev", "<", "prev") - ._bgEnableAria(current > 1); - - for (var i = 0; i < count; i++) - { - var pos = i + startWith; - renderPaginationItem.call(this, pagination, pos, pos, "page-" + pos) - ._bgEnableAria()._bgSelectAria(pos === current); - } - - if (count === 0) - { - renderPaginationItem.call(this, pagination, 1, 1, "page-" + 1) - ._bgEnableAria(false)._bgSelectAria(); - } - - renderPaginationItem.call(this, pagination, "next", ">", "next") - ._bgEnableAria(totalPages > current); - renderPaginationItem.call(this, pagination, "last", "»", "last") - ._bgEnableAria(totalPages > current); - - replacePlaceHolder.call(this, paginationItems, pagination); - } - } +function renderActions() { + if (this.options.navigation !== 0) { + var css = this.options.css, + selector = getCssSelector(css.actions), + actionItems = findFooterAndHeaderItems.call(this, selector); + + if (actionItems.length > 0) { + var that = this, + tpl = this.options.templates, + actions = $(tpl.actions.resolve(getParams.call(this))); + + // Refresh Button + if (this.options.ajax) { + var refreshIcon = tpl.icon.resolve(getParams.call(this, { iconCss : css.iconRefresh })), + refresh = $( + tpl.actionButton.resolve( + getParams.call( + this, + { content : refreshIcon, text : this.options.labels.refresh } + ) + ) + ) + .on( + "click" + namespace, function(e) { + // todo: prevent multiple fast clicks (fast click detection) + e.stopPropagation(); + that.current = 1; + loadData.call(that); + } + ); + actions.append(refresh); + } + + // Row count selection + renderRowCountSelection.call(this, actions); + + // Column selection + renderColumnSelection.call(this, actions); + + replacePlaceHolder.call(this, actionItems, actions); + } + } } -function renderPaginationItem(list, page, text, markerCss) -{ - var that = this, - tpl = this.options.templates, - css = this.options.css, - values = getParams.call(this, { css: markerCss, text: text, page: page }), - item = $(tpl.paginationItem.resolve(values)) - .on("click" + namespace, getCssSelector(css.paginationButton), function (e) - { +function renderColumnSelection(actions) { + if (this.options.columnSelection && this.columns.length > 1) { + var that = this, + css = this.options.css, + tpl = this.options.templates, + icon = tpl.icon.resolve(getParams.call(this, { iconCss : css.iconColumns })), + dropDown = $(tpl.actionDropDown.resolve(getParams.call(this, { content : icon }))), + selector = getCssSelector(css.dropDownItem), + checkboxSelector = getCssSelector(css.dropDownItemCheckbox), + itemsSelector = getCssSelector(css.dropDownMenuItems); + + $.each( + this.columns, function(i, column) { + if (column.visibleInSelection) { + var item = $( + tpl.actionDropDownCheckboxItem.resolve( + getParams.call( + that, + { name : column.id, label : column.text, checked : column.visible } + ) + ) + ) + .on( + "click" + namespace, selector, function(e) { e.stopPropagation(); - e.preventDefault(); - - var $this = $(this), - parent = $this.parent(); - if (!parent.hasClass("active") && !parent.hasClass("disabled")) - { - var commandList = { - first: 1, - prev: that.current - 1, - next: that.current + 1, - last: that.totalPages - }; - var command = $this.data("page"); - that.current = commandList[command] || command; - loadData.call(that); + + var $this = $(this), + checkbox = $this.find(checkboxSelector); + if (!checkbox.prop("disabled")) { + column.visible = checkbox.prop("checked"); + var enable = that.columns.where(isVisible).length > 1; + $this.parents(itemsSelector).find(selector + ":has(" + checkboxSelector + ":checked)") + ._bgEnableAria(enable).find(checkboxSelector)._bgEnableField(enable); + + that.element.find("tbody").empty(); // Fixes an column visualization bug + renderTableHeader.call(that); + loadData.call(that); + that.element.trigger("columnToggle" + namespace, column); } - $this.trigger("blur"); - }); - - list.append(item); - return item; -} - -function renderRowCountSelection(actions) -{ - var that = this, - rowCountList = this.options.rowCount; - - function getText(value) - { - return (value === -1) ? that.options.labels.all : value; - } - - if ($.isArray(rowCountList)) - { - var css = this.options.css, - tpl = this.options.templates, - dropDown = $(tpl.actionDropDown.resolve(getParams.call(this, { content: getText(this.rowCount) }))), - menuSelector = getCssSelector(css.dropDownMenu), - menuTextSelector = getCssSelector(css.dropDownMenuText), - menuItemsSelector = getCssSelector(css.dropDownMenuItems), - menuItemSelector = getCssSelector(css.dropDownItemButton); - - $.each(rowCountList, function (index, value) - { - var item = $(tpl.actionDropDownItem.resolve(getParams.call(that, - { text: getText(value), action: value }))) - ._bgSelectAria(value === that.rowCount) - .on("click" + namespace, menuItemSelector, function (e) - { - e.preventDefault(); - - var $this = $(this), - newRowCount = $this.data("action"); - if (newRowCount !== that.rowCount) - { - // todo: sophisticated solution needed for calculating which page is selected - that.current = 1; // that.rowCount === -1 ---> All - that.rowCount = newRowCount; - $this.parents(menuItemsSelector).children().each(function () - { - var $item = $(this), - currentRowCount = $item.find(menuItemSelector).data("action"); - $item._bgSelectAria(currentRowCount === newRowCount); - }); - $this.parents(menuSelector).find(menuTextSelector).text(getText(newRowCount)); - loadData.call(that); - } - }); - dropDown.find(menuItemsSelector).append(item); - }); - actions.append(dropDown); - } + } + ); + dropDown.find(getCssSelector(css.dropDownMenuItems)).append(item); + } + } + ); + actions.append(dropDown); + } } -function renderRows(rows) -{ - if (rows.length > 0) - { - var that = this, - css = this.options.css, - tpl = this.options.templates, - tbody = this.element.children("tbody").first(), - allRowsSelected = true, - html = ""; - - $.each(rows, function (index, row) - { - var cells = "", - rowAttr = " data-row-id=\"" + ((that.identifier == null) ? index : row[that.identifier]) + "\"", - rowCss = ""; - - if (that.selection) - { - var selected = ($.inArray(row[that.identifier], that.selectedRows) !== -1), - selectBox = tpl.select.resolve(getParams.call(that, - { type: "checkbox", value: row[that.identifier], checked: selected })); - cells += tpl.cell.resolve(getParams.call(that, { content: selectBox, css: css.selectCell })); - allRowsSelected = (allRowsSelected && selected); - if (selected) - { - rowCss += css.selected; - rowAttr += " aria-selected=\"true\""; - } - } - - var status = row.status != null && that.options.statusMapping[row.status]; - if (status) - { - rowCss += status; - } - - $.each(that.columns, function (j, column) - { - if (column.visible) - { - var value = ($.isFunction(column.formatter)) ? - column.formatter.call(that, column, row) : - column.converter.to(row[column.id]), - cssClass = (column.cssClass.length > 0) ? " " + column.cssClass : ""; - cells += tpl.cell.resolve(getParams.call(that, { - content: (value == null || value === "") ? " " : value, - css: ((column.align === "right") ? css.right : (column.align === "center") ? - css.center : css.left) + cssClass, - style: (column.width == null) ? "" : "width:" + column.width + ";" })); +function renderInfos() { + if (this.options.navigation !== 0) { + var selector = getCssSelector(this.options.css.infos), + infoItems = findFooterAndHeaderItems.call(this, selector); + + if (infoItems.length > 0) { + var end = (this.current * this.rowCount), + infos = $( + this.options.templates.infos.resolve( + getParams.call( + this, { + end : (this.total === 0 || end === -1 || end > this.total) ? this.total : end, + start : (this.total === 0) ? 0 : (end - this.rowCount + 1), + total : this.total } - }); - - if (rowCss.length > 0) - { - rowAttr += " class=\"" + rowCss + "\""; - } - html += tpl.row.resolve(getParams.call(that, { attr: rowAttr, cells: cells })); - }); - - // sets or clears multi selectbox state - that.element.find("thead " + getCssSelector(that.options.css.selectBox)) - .prop("checked", allRowsSelected); - - tbody.html(html); - - registerRowEvents.call(this, tbody); - } - else - { - renderNoResultsRow.call(this); + ) + ) + ); + + replacePlaceHolder.call(this, infoItems, infos); } + } } -function registerRowEvents(tbody) -{ - var that = this, - selectBoxSelector = getCssSelector(this.options.css.selectBox); - - if (this.selection) - { - tbody.off("click" + namespace, selectBoxSelector) - .on("click" + namespace, selectBoxSelector, function(e) - { - e.stopPropagation(); - - var $this = $(this), - id = that.converter.from($this.val()); +function renderNoResultsRow() { + var tbody = this.element.children("tbody").first(), + tpl = this.options.templates, + count = this.columns.where(isVisible).length; + + if (this.selection) { + count = count + 1; + } + tbody.html(tpl.noResults.resolve(getParams.call(this, { columns : count }))); +} - if ($this.prop("checked")) - { - that.select([id]); - } - else - { - that.deselect([id]); - } - }); - } +function renderPagination() { + if (this.options.navigation !== 0) { + var selector = getCssSelector(this.options.css.pagination), + paginationItems = findFooterAndHeaderItems.call(this, selector)._bgShowAria(this.rowCount !== -1); + + if (this.rowCount !== -1 && paginationItems.length > 0) { + var tpl = this.options.templates, + current = this.current, + totalPages = this.totalPages, + pagination = $(tpl.pagination.resolve(getParams.call(this))), + offsetRight = totalPages - current, + offsetLeft = (this.options.padding - current) * -1, + startWith = ((offsetRight >= this.options.padding) ? + Math.max(offsetLeft, 1) : + Math.max((offsetLeft - this.options.padding + offsetRight), 1)), + maxCount = this.options.padding * 2 + 1, + count = (totalPages >= maxCount) ? maxCount : totalPages; + + renderPaginationItem.call(this, pagination, "first", "«", "first") + ._bgEnableAria(current > 1); + renderPaginationItem.call(this, pagination, "prev", "<", "prev") + ._bgEnableAria(current > 1); + + for (var i = 0; i < count; i++) { + var pos = i + startWith; + renderPaginationItem.call(this, pagination, pos, pos, "page-" + pos) + ._bgEnableAria()._bgSelectAria(pos === current); + } + + if (count === 0) { + renderPaginationItem.call(this, pagination, 1, 1, "page-" + 1) + ._bgEnableAria(false)._bgSelectAria(); + } + + renderPaginationItem.call(this, pagination, "next", ">", "next") + ._bgEnableAria(totalPages > current); + renderPaginationItem.call(this, pagination, "last", "»", "last") + ._bgEnableAria(totalPages > current); + + replacePlaceHolder.call(this, paginationItems, pagination); + } + } +} - tbody.off("click" + namespace, "> tr") - .on("click" + namespace, "> tr", function(e) - { +function renderPaginationItem(list, page, text, markerCss) { + var that = this, + tpl = this.options.templates, + css = this.options.css, + values = getParams.call(this, { css : markerCss, text : text, page : page }), + item = $(tpl.paginationItem.resolve(values)) + .on( + "click" + namespace, getCssSelector(css.paginationButton), function(e) { e.stopPropagation(); - - var $this = $(this), - id = (that.identifier == null) ? $this.data("row-id") : - that.converter.from($this.data("row-id") + ""), - row = (that.identifier == null) ? that.currentRows[id] : - that.currentRows.first(function (item) { return item[that.identifier] === id; }); - - if (that.selection && that.options.rowSelect) - { - if ($this.hasClass(that.options.css.selected)) - { - that.deselect([id]); - } - else - { - that.select([id]); - } + e.preventDefault(); + + var $this = $(this), + parent = $this.parent(); + if (!parent.hasClass("active") && !parent.hasClass("disabled")) { + var commandList = { + first : 1, + prev : that.current - 1, + next : that.current + 1, + last : that.totalPages + }; + var command = $this.data("page"); + that.current = commandList[ command ] || command; + loadData.call(that); } - - that.element.trigger("click" + namespace, [that.columns, row]); - }); -} - -function renderSearchField() -{ - if (this.options.navigation !== 0) - { - var css = this.options.css, - selector = getCssSelector(css.search), - searchItems = findFooterAndHeaderItems.call(this, selector); - - if (searchItems.length > 0) - { - var that = this, - tpl = this.options.templates, - timer = null, // fast keyup detection - currentValue = "", - searchFieldSelector = getCssSelector(css.searchField), - search = $(tpl.search.resolve(getParams.call(this))), - searchField = (search.is(searchFieldSelector)) ? search : - search.find(searchFieldSelector); - - searchField.on("keyup" + namespace, function (e) - { - e.stopPropagation(); - var newValue = $(this).val(); - if (currentValue !== newValue || (e.which === 13 && newValue !== "")) - { - currentValue = newValue; - if (e.which === 13 || newValue.length === 0 || newValue.length >= that.options.searchSettings.characters) - { - window.clearTimeout(timer); - timer = window.setTimeout(function () - { - executeSearch.call(that, newValue); - }, that.options.searchSettings.delay); - } - } - }); - - replacePlaceHolder.call(this, searchItems, search); - } - } + $this.trigger("blur"); + } + ); + + list.append(item); + return item; } -function executeSearch(phrase) -{ - if (this.searchPhrase !== phrase) - { - this.current = 1; - this.searchPhrase = phrase; - loadData.call(this); - } +function renderRowCountSelection(actions) { + var that = this, + rowCountList = this.options.rowCount; + + function getText(value) { + return (value === -1) ? that.options.labels.all : value; + } + + if ($.isArray(rowCountList)) { + var css = this.options.css, + tpl = this.options.templates, + dropDown = $(tpl.actionDropDown.resolve(getParams.call(this, { content : getText(this.rowCount) }))), + menuSelector = getCssSelector(css.dropDownMenu), + menuTextSelector = getCssSelector(css.dropDownMenuText), + menuItemsSelector = getCssSelector(css.dropDownMenuItems), + menuItemSelector = getCssSelector(css.dropDownItemButton); + + $.each( + rowCountList, function(index, value) { + var item = $( + tpl.actionDropDownItem.resolve( + getParams.call( + that, + { text : getText(value), action : value } + ) + ) + ) + ._bgSelectAria(value === that.rowCount) + .on( + "click" + namespace, menuItemSelector, function(e) { + e.preventDefault(); + + var $this = $(this), + newRowCount = $this.data("action"); + if (newRowCount !== that.rowCount) { + // todo: sophisticated solution needed for calculating which page is selected + that.current = 1; // that.rowCount === -1 ---> All + that.rowCount = newRowCount; + $this.parents(menuItemsSelector).children().each( + function() { + var $item = $(this), + currentRowCount = $item.find(menuItemSelector).data("action"); + $item._bgSelectAria(currentRowCount === newRowCount); + } + ); + $this.parents(menuSelector).find(menuTextSelector).text(getText(newRowCount)); + loadData.call(that); + } + } + ); + dropDown.find(menuItemsSelector).append(item); + } + ); + actions.append(dropDown); + } } -function renderTableHeader() -{ - var that = this, - headerRow = this.element.find("thead > tr"), - css = this.options.css, - tpl = this.options.templates, - html = "", - sorting = this.options.sorting; - - if (this.selection) - { - var selectBox = (this.options.multiSelect) ? - tpl.select.resolve(getParams.call(that, { type: "checkbox", value: "all" })) : ""; - html += tpl.rawHeaderCell.resolve(getParams.call(that, { content: selectBox, - css: css.selectCell })); - } - - $.each(this.columns, function (index, column) - { - if (column.visible) - { - var sortOrder = that.sortDictionary[column.id], - iconCss = ((sorting && sortOrder && sortOrder === "asc") ? css.iconUp : - (sorting && sortOrder && sortOrder === "desc") ? css.iconDown : ""), - icon = tpl.icon.resolve(getParams.call(that, { iconCss: iconCss })), - align = column.headerAlign, - cssClass = (column.headerCssClass.length > 0) ? " " + column.headerCssClass : ""; - html += tpl.headerCell.resolve(getParams.call(that, { - column: column, icon: icon, sortable: sorting && column.sortable && css.sortable || "", - css: ((align === "right") ? css.right : (align === "center") ? - css.center : css.left) + cssClass, - style: (column.width == null) ? "" : "width:" + column.width + ";" })); +function renderRows(rows) { + if (rows.length > 0) { + var that = this, + css = this.options.css, + tpl = this.options.templates, + tbody = this.element.children("tbody").first(), + allRowsSelected = true, + html = ""; + + $.each( + rows, function(index, row) { + var cells = "", + rowAttr = " data-row-id=\"" + ((that.identifier == null) ? index : row[ that.identifier ]) + "\"", + rowCss = ""; + + if (that.selection) { + var selected = ($.inArray(row[ that.identifier ], that.selectedRows) !== -1), + selectBox = tpl.select.resolve( + getParams.call( + that, + { type : "checkbox", value : row[ that.identifier ], checked : selected } + ) + ); + cells += tpl.cell.resolve(getParams.call(that, { content : selectBox, css : css.selectCell })); + allRowsSelected = (allRowsSelected && selected); + if (selected) { + rowCss += css.selected; + rowAttr += " aria-selected=\"true\""; + } } - }); - - headerRow.html(html); - - if (sorting) - { - var sortingSelector = getCssSelector(css.sortable); - headerRow.off("click" + namespace, sortingSelector) - .on("click" + namespace, sortingSelector, function (e) - { - e.preventDefault(); - - setTableHeaderSortDirection.call(that, $(this)); - sortRows.call(that); - loadData.call(that); - }); - } - - // todo: create a own function for that piece of code - if (this.selection && this.options.multiSelect) - { - var selectBoxSelector = getCssSelector(css.selectBox); - headerRow.off("click" + namespace, selectBoxSelector) - .on("click" + namespace, selectBoxSelector, function(e) - { - e.stopPropagation(); - - if ($(this).prop("checked")) - { - that.select(); - } - else - { - that.deselect(); - } - }); - } + + var status = row.status != null && that.options.statusMapping[ row.status ]; + if (status) { + rowCss += status; + } + + $.each( + that.columns, function(j, column) { + if (column.visible) { + var value = ($.isFunction(column.formatter)) ? + column.formatter.call(that, column, row) : + column.converter.to(row[ column.id ]), + cssClass = (column.cssClass.length > 0) ? " " + column.cssClass : ""; + cells += tpl.cell.resolve( + getParams.call( + that, { + content : (value == null || value === "") ? " " : value, + css : ((column.align === "right") ? css.right : (column.align === "center") ? + css.center : css.left) + cssClass, + style : (column.width == null) ? "" : "width:" + column.width + ";" + } + ) + ); + } + } + ); + + if (rowCss.length > 0) { + rowAttr += " class=\"" + rowCss + "\""; + } + html += tpl.row.resolve(getParams.call(that, { attr : rowAttr, cells : cells })); + } + ); + + // sets or clears multi selectbox state + that.element.find("thead " + getCssSelector(that.options.css.selectBox)) + .prop("checked", allRowsSelected); + + tbody.html(html); + + registerRowEvents.call(this, tbody); + } + else { + renderNoResultsRow.call(this); + } } -function setTableHeaderSortDirection(element) -{ - var css = this.options.css, - iconSelector = getCssSelector(css.icon), - columnId = element.data("column-id") || element.parents("th").first().data("column-id"), - sortOrder = this.sortDictionary[columnId], - icon = element.find(iconSelector); - - if (!this.options.multiSort) - { - element.parents("tr").first().find(iconSelector).removeClass(css.iconDown + " " + css.iconUp); - this.sortDictionary = {}; - } +function registerRowEvents(tbody) { + var that = this, + selectBoxSelector = getCssSelector(this.options.css.selectBox); + + if (this.selection) { + tbody.off("click" + namespace, selectBoxSelector) + .on( + "click" + namespace, selectBoxSelector, function(e) { + e.stopPropagation(); + + var $this = $(this), + id = that.converter.from($this.val()); + + if ($this.prop("checked")) { + that.select([ id ]); + } + else { + that.deselect([ id ]); + } + } + ); + } + + tbody.off("click" + namespace, "> tr") + .on( + "click" + namespace, "> tr", function(e) { + e.stopPropagation(); + + var $this = $(this), + id = (that.identifier == null) ? $this.data("row-id") : + that.converter.from($this.data("row-id") + ""), + row = (that.identifier == null) ? that.currentRows[ id ] : + that.currentRows.first( + function(item) { + return item[ that.identifier ] === id; + } + ); + + if (that.selection && that.options.rowSelect) { + if ($this.hasClass(that.options.css.selected)) { + that.deselect([ id ]); + } + else { + that.select([ id ]); + } + } + + that.element.trigger("click" + namespace, [ that.columns, row ]); + } + ); +} - if (sortOrder && sortOrder === "asc") - { - this.sortDictionary[columnId] = "desc"; - icon.removeClass(css.iconUp).addClass(css.iconDown); - } - else if (sortOrder && sortOrder === "desc") - { - if (this.options.multiSort) - { - var newSort = {}; - for (var key in this.sortDictionary) - { - if (key !== columnId) - { - newSort[key] = this.sortDictionary[key]; - } +function renderSearchField() { + if (this.options.navigation !== 0) { + var css = this.options.css, + selector = getCssSelector(css.search), + searchItems = findFooterAndHeaderItems.call(this, selector); + + if (searchItems.length > 0) { + var that = this, + tpl = this.options.templates, + timer = null, // fast keyup detection + currentValue = "", + searchFieldSelector = getCssSelector(css.searchField), + search = $(tpl.search.resolve(getParams.call(this))), + searchField = (search.is(searchFieldSelector)) ? search : + search.find(searchFieldSelector); + + searchField.on( + "keyup" + namespace, function(e) { + e.stopPropagation(); + var newValue = $(this).val(); + if (currentValue !== newValue || (e.which === 13 && newValue !== "")) { + currentValue = newValue; + if (e.which === 13 || newValue.length === 0 || newValue.length >= that.options.searchSettings.characters) { + window.clearTimeout(timer); + timer = window.setTimeout( + function() { + executeSearch.call(that, newValue); + }, that.options.searchSettings.delay + ); } - this.sortDictionary = newSort; - icon.removeClass(css.iconDown); + } } - else - { - this.sortDictionary[columnId] = "asc"; - icon.removeClass(css.iconDown).addClass(css.iconUp); - } - } - else - { - this.sortDictionary[columnId] = "asc"; - icon.addClass(css.iconUp); + ); + + replacePlaceHolder.call(this, searchItems, search); } + } } -function replacePlaceHolder(placeholder, element) -{ - placeholder.each(function (index, item) - { - // todo: check how append is implemented. Perhaps cloning here is superfluous. - $(item).before(element.clone(true)).remove(); - }); +function executeSearch(phrase) { + if (this.searchPhrase !== phrase) { + this.current = 1; + this.searchPhrase = phrase; + loadData.call(this); + } } -function showLoading() -{ - var that = this; - - window.setTimeout(function() - { - if (that.element._bgAria("busy") === "true") - { - var tpl = that.options.templates, - thead = that.element.children("thead").first(), - tbody = that.element.children("tbody").first(), - firstCell = tbody.find("tr > td").first(), - padding = (that.element.height() - thead.height()) - (firstCell.height() + 20), - count = that.columns.where(isVisible).length; - - if (that.selection) - { - count = count + 1; - } - tbody.html(tpl.loading.resolve(getParams.call(that, { columns: count }))); - if (that.rowCount !== -1 && padding > 0) - { - tbody.find("tr > td").css("padding", "20px 0 " + padding + "px"); - } +function renderTableHeader() { + var that = this, + headerRow = this.element.find("thead > tr"), + css = this.options.css, + tpl = this.options.templates, + html = "", + sorting = this.options.sorting; + + if (this.selection) { + var selectBox = (this.options.multiSelect) ? + tpl.select.resolve(getParams.call(that, { type : "checkbox", value : "all" })) : ""; + html += tpl.rawHeaderCell.resolve( + getParams.call( + that, { + content : selectBox, + css : css.selectCell } - }, 250); + ) + ); + } + + $.each( + this.columns, function(index, column) { + if (column.visible) { + var sortOrder = that.sortDictionary[ column.id ], + iconCss = ((sorting && sortOrder && sortOrder === "asc") ? css.iconUp : + (sorting && sortOrder && sortOrder === "desc") ? css.iconDown : ""), + icon = tpl.icon.resolve(getParams.call(that, { iconCss : iconCss })), + align = column.headerAlign, + cssClass = (column.headerCssClass.length > 0) ? " " + column.headerCssClass : ""; + html += tpl.headerCell.resolve( + getParams.call( + that, { + column : column, icon : icon, sortable : sorting && column.sortable && css.sortable || "", + css : ((align === "right") ? css.right : (align === "center") ? + css.center : css.left) + cssClass, + style : (column.width == null) ? "" : "width:" + column.width + ";" + } + ) + ); + } + } + ); + + headerRow.html(html); + + if (sorting) { + var sortingSelector = getCssSelector(css.sortable); + headerRow.off("click" + namespace, sortingSelector) + .on( + "click" + namespace, sortingSelector, function(e) { + e.preventDefault(); + + setTableHeaderSortDirection.call(that, $(this)); + sortRows.call(that); + loadData.call(that); + } + ); + } + + // todo: create a own function for that piece of code + if (this.selection && this.options.multiSelect) { + var selectBoxSelector = getCssSelector(css.selectBox); + headerRow.off("click" + namespace, selectBoxSelector) + .on( + "click" + namespace, selectBoxSelector, function(e) { + e.stopPropagation(); + + if ($(this).prop("checked")) { + that.select(); + } + else { + that.deselect(); + } + } + ); + } } -function sortRows() -{ - var sortArray = []; - - function sort(x, y, current) - { - current = current || 0; - var next = current + 1, - item = sortArray[current]; - - function sortOrder(value) - { - return (item.order === "asc") ? value : value * -1; +function setTableHeaderSortDirection(element) { + var css = this.options.css, + iconSelector = getCssSelector(css.icon), + columnId = element.data("column-id") || element.parents("th").first().data("column-id"), + sortOrder = this.sortDictionary[ columnId ], + icon = element.find(iconSelector); + + if (!this.options.multiSort) { + element.parents("tr").first().find(iconSelector).removeClass(css.iconDown + " " + css.iconUp); + this.sortDictionary = {}; + } + + if (sortOrder && sortOrder === "asc") { + this.sortDictionary[ columnId ] = "desc"; + icon.removeClass(css.iconUp).addClass(css.iconDown); + } + else if (sortOrder && sortOrder === "desc") { + if (this.options.multiSort) { + var newSort = {}; + for (var key in this.sortDictionary) { + if (key !== columnId) { + newSort[ key ] = this.sortDictionary[ key ]; } + } + this.sortDictionary = newSort; + icon.removeClass(css.iconDown); + } + else { + this.sortDictionary[ columnId ] = "asc"; + icon.removeClass(css.iconDown).addClass(css.iconUp); + } + } + else { + this.sortDictionary[ columnId ] = "asc"; + icon.addClass(css.iconUp); + } +} - return (x[item.id] > y[item.id]) ? sortOrder(1) : - (x[item.id] < y[item.id]) ? sortOrder(-1) : - (sortArray.length > next) ? sort(x, y, next) : 0; +function replacePlaceHolder(placeholder, element) { + placeholder.each( + function(index, item) { + // todo: check how append is implemented. Perhaps cloning here is superfluous. + $(item).before(element).remove(); } + ); +} - if (!this.options.ajax) - { - var that = this; - - for (var key in this.sortDictionary) - { - if (this.options.multiSort || sortArray.length === 0) - { - sortArray.push({ - id: key, - order: this.sortDictionary[key] - }); - } +function showLoading() { + var that = this; + + window.setTimeout( + function() { + if (that.element._bgAria("busy") === "true") { + var tpl = that.options.templates, + thead = that.element.children("thead").first(), + tbody = that.element.children("tbody").first(), + firstCell = tbody.find("tr > td").first(), + padding = (that.element.height() - thead.height()) - (firstCell.height() + 20), + count = that.columns.where(isVisible).length; + + if (that.selection) { + count = count + 1; } - - if (sortArray.length > 0) - { - this.rows.sort(sort); + tbody.html(tpl.loading.resolve(getParams.call(that, { columns : count }))); + if (that.rowCount !== -1 && padding > 0) { + tbody.find("tr > td").css("padding", "20px 0 " + padding + "px"); } + } + }, 250 + ); +} + +function sortRows() { + var sortArray = []; + + function sort(x, y, current) { + current = current || 0; + var next = current + 1, + item = sortArray[ current ]; + + function sortOrder(value) { + return (item.order === "asc") ? value : value * -1; + } + + return (x[ item.id ] > y[ item.id ]) ? sortOrder(1) : + (x[ item.id ] < y[ item.id ]) ? sortOrder(-1) : + (sortArray.length > next) ? sort(x, y, next) : 0; + } + + if (!this.options.ajax) { + var that = this; + + for (var key in this.sortDictionary) { + if (this.options.multiSort || sortArray.length === 0) { + sortArray.push( + { + id : key, + order : this.sortDictionary[ key ] + } + ); + } } -} \ No newline at end of file + + if (sortArray.length > 0) { + this.rows.sort(sort); + } + } +} diff --git a/src/plugin.js b/src/plugin.js index c51abe9..63f4406 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -3,38 +3,33 @@ var old = $.fn.bootgrid; -$.fn.bootgrid = function (option) -{ - var args = Array.prototype.slice.call(arguments, 1), - returnValue = null, - elements = this.each(function (index) - { - var $this = $(this), - instance = $this.data(namespace), - options = typeof option === "object" && option; - - if (!instance && option === "destroy") - { - return; - } - if (!instance) - { - $this.data(namespace, (instance = new Grid(this, options))); - init.call(instance); +$.fn.bootgrid = function(option) { + var args = Array.prototype.slice.call(arguments, 1), + returnValue = null, + elements = this.each( + function(index) { + var $this = $(this), + instance = $this.data(namespace), + options = typeof option === "object" && option; + + if (!instance && option === "destroy") { + return; + } + if (!instance) { + $this.data(namespace, (instance = new Grid(this, options))); + init.call(instance); + } + if (typeof option === "string") { + if (option.indexOf("get") === 0 && index === 0) { + returnValue = instance[ option ].apply(instance, args); } - if (typeof option === "string") - { - if (option.indexOf("get") === 0 && index === 0) - { - returnValue = instance[option].apply(instance, args); - } - else if (option.indexOf("get") !== 0) - { - return instance[option].apply(instance, args); - } + else if (option.indexOf("get") !== 0) { + return instance[ option ].apply(instance, args); } - }); - return (typeof option === "string" && option.indexOf("get") === 0) ? returnValue : elements; + } + } + ); + return (typeof option === "string" && option.indexOf("get") === 0) ? returnValue : elements; }; $.fn.bootgrid.Constructor = Grid; @@ -42,13 +37,12 @@ $.fn.bootgrid.Constructor = Grid; // GRID NO CONFLICT // =============== -$.fn.bootgrid.noConflict = function () -{ - $.fn.bootgrid = old; - return this; +$.fn.bootgrid.noConflict = function() { + $.fn.bootgrid = old; + return this; }; // GRID DATA-API // ============ -$("[data-toggle=\"bootgrid\"]").bootgrid(); \ No newline at end of file +$("[data-toggle=\"bootgrid\"]").bootgrid(); diff --git a/src/public.js b/src/public.js index 16c5c44..074b8a7 100644 --- a/src/public.js +++ b/src/public.js @@ -10,36 +10,35 @@ * @param options {Object} The options to override default settings. * @chainable **/ -var Grid = function(element, options) -{ - this.element = $(element); - this.origin = this.element.clone(); - this.options = $.extend(true, {}, Grid.defaults, this.element.data(), options); - // overrides rowCount explicitly because deep copy ($.extend) leads to strange behaviour - var rowCount = this.options.rowCount = this.element.data().rowCount || options.rowCount || this.options.rowCount; - this.columns = []; - this.current = 1; - this.currentRows = []; - this.identifier = null; // The first column ID that is marked as identifier - this.selection = false; - this.converter = null; // The converter for the column that is marked as identifier - this.rowCount = ($.isArray(rowCount)) ? rowCount[0] : rowCount; - this.rows = []; - this.searchPhrase = ""; - this.selectedRows = []; - this.sortDictionary = {}; - this.total = 0; - this.totalPages = 0; - this.cachedParams = { - lbl: this.options.labels, - css: this.options.css, - ctx: {} - }; - this.header = null; - this.footer = null; - this.xqr = null; - - // todo: implement cache +var Grid = function(element, options) { + this.element = $(element); + this.origin = this.element.clone(); + this.options = $.extend(true, {}, Grid.defaults, this.element.data(), options); + // overrides rowCount explicitly because deep copy ($.extend) leads to strange behaviour + var rowCount = this.options.rowCount = this.element.data().rowCount || options.rowCount || this.options.rowCount; + this.columns = []; + this.current = 1; + this.currentRows = []; + this.identifier = null; // The first column ID that is marked as identifier + this.selection = false; + this.converter = null; // The converter for the column that is marked as identifier + this.rowCount = ($.isArray(rowCount)) ? rowCount[ 0 ] : rowCount; + this.rows = []; + this.searchPhrase = ""; + this.selectedRows = []; + this.sortDictionary = {}; + this.total = 0; + this.totalPages = 0; + this.cachedParams = { + lbl : this.options.labels, + css : this.options.css, + ctx : {} + }; + this.header = null; + this.footer = null; + this.xqr = null; + + // todo: implement cache }; /** @@ -56,366 +55,391 @@ var Grid = function(element, options) * $("#bootgrid").bootgrid({ selection = true }); **/ Grid.defaults = { - navigation: 3, // it's a flag: 0 = none, 1 = top, 2 = bottom, 3 = both (top and bottom) - padding: 2, // page padding (pagination) - columnSelection: true, - rowCount: [10, 25, 50, -1], // rows per page int or array of int (-1 represents "All") - - /** - * Enables row selection (to enable multi selection see also `multiSelect`). Default value is `false`. - * - * @property selection - * @type Boolean - * @default false - * @for defaults - * @since 1.0.0 - **/ - selection: false, - - /** - * Enables multi selection (`selection` must be set to `true` as well). Default value is `false`. - * - * @property multiSelect - * @type Boolean - * @default false - * @for defaults - * @since 1.0.0 - **/ - multiSelect: false, - - /** - * Enables entire row click selection (`selection` must be set to `true` as well). Default value is `false`. - * - * @property rowSelect - * @type Boolean - * @default false - * @for defaults - * @since 1.1.0 - **/ - rowSelect: false, - - /** - * Defines whether the row selection is saved internally on filtering, paging and sorting - * (even if the selected rows are not visible). - * - * @property keepSelection - * @type Boolean - * @default false - * @for defaults - * @since 1.1.0 - **/ - keepSelection: false, - - highlightRows: false, // highlights new rows (find the page of the first new row) - sorting: true, - multiSort: false, - + navigation : 3, // it's a flag: 0 = none, 1 = top, 2 = bottom, 3 = both (top and bottom) + padding : 2, // page padding (pagination) + columnSelection : true, + rowCount : [ 10, 25, 50, -1 ], // rows per page int or array of int (-1 represents "All") + + /** + * Enables row selection (to enable multi selection see also `multiSelect`). Default value is `false`. + * + * @property selection + * @type Boolean + * @default false + * @for defaults + * @since 1.0.0 + **/ + selection : false, + + /** + * Enables multi selection (`selection` must be set to `true` as well). Default value is `false`. + * + * @property multiSelect + * @type Boolean + * @default false + * @for defaults + * @since 1.0.0 + **/ + multiSelect : false, + + /** + * Enables entire row click selection (`selection` must be set to `true` as well). Default value is `false`. + * + * @property rowSelect + * @type Boolean + * @default false + * @for defaults + * @since 1.1.0 + **/ + rowSelect : false, + + /** + * Defines whether the row selection is saved internally on filtering, paging and sorting + * (even if the selected rows are not visible). + * + * @property keepSelection + * @type Boolean + * @default false + * @for defaults + * @since 1.1.0 + **/ + keepSelection : false, + + highlightRows : false, // highlights new rows (find the page of the first new row) + sorting : true, + multiSort : false, + + /** + * General search settings to configure the search field behaviour. + * + * @property searchSettings + * @type Object + * @for defaults + * @since 1.2.0 + **/ + searchSettings : { /** - * General search settings to configure the search field behaviour. + * The time in milliseconds to wait before search gets executed. * - * @property searchSettings - * @type Object - * @for defaults - * @since 1.2.0 + * @property delay + * @type Number + * @default 250 + * @for searchSettings **/ - searchSettings: { - /** - * The time in milliseconds to wait before search gets executed. - * - * @property delay - * @type Number - * @default 250 - * @for searchSettings - **/ - delay: 250, - - /** - * The characters to type before the search gets executed. - * - * @property characters - * @type Number - * @default 1 - * @for searchSettings - **/ - characters: 1 - }, - + delay : 250, + /** - * Defines whether the data shall be loaded via an asynchronous HTTP (Ajax) request. + * The characters to type before the search gets executed. * - * @property ajax - * @type Boolean - * @default false - * @for defaults + * @property characters + * @type Number + * @default 1 + * @for searchSettings **/ - ajax: false, - + characters : 1 + }, + + /** + * Defines whether the data shall be loaded via an asynchronous HTTP (Ajax) request. + * + * @property ajax + * @type Boolean + * @default false + * @for defaults + **/ + ajax : false, + + /** + * Ajax request settings that shall be used for server-side communication. + * All setting except data, error, success and url can be overridden. + * For the full list of settings go to http://api.jquery.com/jQuery.ajax/. + * + * @property ajaxSettings + * @type Object + * @for defaults + * @since 1.2.0 + **/ + ajaxSettings : { /** - * Ajax request settings that shall be used for server-side communication. - * All setting except data, error, success and url can be overridden. - * For the full list of settings go to http://api.jquery.com/jQuery.ajax/. + * Specifies the HTTP method which shall be used when sending data to the server. + * Go to http://api.jquery.com/jQuery.ajax/ for more details. + * This setting is overriden for backward compatibility. * - * @property ajaxSettings - * @type Object - * @for defaults - * @since 1.2.0 + * @property method + * @type String + * @default "POST" + * @for ajaxSettings **/ - ajaxSettings: { - /** - * Specifies the HTTP method which shall be used when sending data to the server. - * Go to http://api.jquery.com/jQuery.ajax/ for more details. - * This setting is overriden for backward compatibility. - * - * @property method - * @type String - * @default "POST" - * @for ajaxSettings - **/ - method: "POST" + method : "POST" + }, + + /** + * Enriches the request object with additional properties. Either a `PlainObject` or a `Function` + * that returns a `PlainObject` can be passed. Default value is `{}`. + * + * @property post + * @type Object|Function + * @default function (request) { return request; } + * @for defaults + * @deprecated Use instead `requestHandler` + **/ + post : {}, // or use function () { return {}; } (reserved properties are "current", "rowCount", "sort" and + // "searchPhrase") + + /** + * Sets the data URL to a data service (e.g. a REST service). Either a `String` or a `Function` + * that returns a `String` can be passed. Default value is `""`. + * + * @property url + * @type String|Function + * @default "" + * @for defaults + **/ + url : "", // or use function () { return ""; } + + /** + * Defines whether the search is case sensitive or insensitive. + * + * @property caseSensitive + * @type Boolean + * @default true + * @for defaults + * @since 1.1.0 + **/ + caseSensitive : true, + + // note: The following properties should not be used via data-api attributes + + /** + * Transforms the JSON request object in what ever is needed on the server-side implementation. + * + * @property requestHandler + * @type Function + * @default function (request) { return request; } + * @for defaults + * @since 1.1.0 + **/ + requestHandler : function(request) { + return request; + }, + + /** + * Transforms the response object into the expected JSON response object. + * + * @property responseHandler + * @type Function + * @default function (response) { return response; } + * @for defaults + * @since 1.1.0 + **/ + responseHandler : function(response) { + return response; + }, + + /** + * A list of converters. + * + * @property converters + * @type Object + * @for defaults + * @since 1.0.0 + **/ + converters : { + numeric : { + from : function(value) { + return +value; + }, // converts from string to numeric + to : function(value) { + return value + ""; + } // converts from numeric to string }, - - /** - * Enriches the request object with additional properties. Either a `PlainObject` or a `Function` - * that returns a `PlainObject` can be passed. Default value is `{}`. - * - * @property post - * @type Object|Function - * @default function (request) { return request; } - * @for defaults - * @deprecated Use instead `requestHandler` - **/ - post: {}, // or use function () { return {}; } (reserved properties are "current", "rowCount", "sort" and "searchPhrase") - - /** - * Sets the data URL to a data service (e.g. a REST service). Either a `String` or a `Function` - * that returns a `String` can be passed. Default value is `""`. - * - * @property url - * @type String|Function - * @default "" - * @for defaults - **/ - url: "", // or use function () { return ""; } - - /** - * Defines whether the search is case sensitive or insensitive. - * - * @property caseSensitive - * @type Boolean - * @default true - * @for defaults - * @since 1.1.0 - **/ - caseSensitive: true, - - // note: The following properties should not be used via data-api attributes - + string : { + // default converter + from : function(value) { + return value; + }, + to : function(value) { + return value; + } + } + }, + + /** + * Contains all css classes. + * + * @property css + * @type Object + * @for defaults + **/ + css : { + actions : "actions btn-group", // must be a unique class name or constellation of class names within + // the header and footer + center : "text-center", + columnHeaderAnchor : "column-header-anchor", // must be a unique class name or constellation of class names + // within the column header cell + columnHeaderText : "text", + dropDownItem : "dropdown-item", // must be a unique class name or constellation of class names within the + // actionDropDown, + dropDownItemButton : "dropdown-item-button", // must be a unique class name or constellation of class names + // within the actionDropDown + dropDownItemCheckbox : "dropdown-item-checkbox", // must be a unique class name or constellation of class names + // within the actionDropDown + dropDownMenu : "dropdown btn-group", // must be a unique class name or constellation of class names within + // the actionDropDown + dropDownMenuItems : "dropdown-menu pull-right", // must be a unique class name or constellation of class names + // within the actionDropDown + dropDownMenuText : "dropdown-text", // must be a unique class name or constellation of class names within the + // actionDropDown + footer : "bootgrid-footer container-fluid", + header : "bootgrid-header container-fluid", + icon : "icon glyphicon", + iconColumns : "glyphicon-th-list", + iconDown : "glyphicon-chevron-down", + iconRefresh : "glyphicon-refresh", + iconSearch : "glyphicon-search", + iconUp : "glyphicon-chevron-up", + infos : "infos", // must be a unique class name or constellation of class names within the header + // and footer, + left : "text-left", + pagination : "pagination", // must be a unique class name or constellation of class names within the + // header and footer + paginationButton : "button", // must be a unique class name or constellation of class names within the + // pagination + /** - * Transforms the JSON request object in what ever is needed on the server-side implementation. + * CSS class to select the parent div which activates responsive mode. * - * @property requestHandler - * @type Function - * @default function (request) { return request; } - * @for defaults + * @property responsiveTable + * @type String + * @default "table-responsive" + * @for css * @since 1.1.0 **/ - requestHandler: function (request) { return request; }, - + responsiveTable : "table-responsive", + + right : "text-right", + search : "search form-group", // must be a unique class name or constellation of class names within the header + // and footer + searchField : "search-field form-control", + selectBox : "select-box", // must be a unique class name or constellation of class names within the entire table + selectCell : "select-cell", // must be a unique class name or constellation of class names within the entire table + /** - * Transforms the response object into the expected JSON response object. + * CSS class to highlight selected rows. * - * @property responseHandler - * @type Function - * @default function (response) { return response; } - * @for defaults + * @property selected + * @type String + * @default "active" + * @for css * @since 1.1.0 **/ - responseHandler: function (response) { return response; }, - - /** - * A list of converters. - * - * @property converters - * @type Object - * @for defaults - * @since 1.0.0 - **/ - converters: { - numeric: { - from: function (value) { return +value; }, // converts from string to numeric - to: function (value) { return value + ""; } // converts from numeric to string - }, - string: { - // default converter - from: function (value) { return value; }, - to: function (value) { return value; } - } - }, - + selected : "active", + + sortable : "sortable", + table : "bootgrid-table table" + }, + + /** + * A dictionary of formatters. + * + * @property formatters + * @type Object + * @for defaults + * @since 1.0.0 + **/ + formatters : {}, + + /** + * Contains all labels. + * + * @property labels + * @type Object + * @for defaults + **/ + labels : { + all : "All", + infos : "Showing {{ctx.start}} to {{ctx.end}} of {{ctx.total}} entries", + loading : "Loading...", + noResults : "No results found!", + refresh : "Refresh", + search : "Search" + }, + + /** + * Specifies the mapping between status and contextual classes to color rows. + * + * @property statusMapping + * @type Object + * @for defaults + * @since 1.2.0 + **/ + statusMapping : { /** - * Contains all css classes. + * Specifies a successful or positive action. * - * @property css - * @type Object - * @for defaults + * @property 0 + * @type String + * @for statusMapping **/ - css: { - actions: "actions btn-group", // must be a unique class name or constellation of class names within the header and footer - center: "text-center", - columnHeaderAnchor: "column-header-anchor", // must be a unique class name or constellation of class names within the column header cell - columnHeaderText: "text", - dropDownItem: "dropdown-item", // must be a unique class name or constellation of class names within the actionDropDown, - dropDownItemButton: "dropdown-item-button", // must be a unique class name or constellation of class names within the actionDropDown - dropDownItemCheckbox: "dropdown-item-checkbox", // must be a unique class name or constellation of class names within the actionDropDown - dropDownMenu: "dropdown btn-group", // must be a unique class name or constellation of class names within the actionDropDown - dropDownMenuItems: "dropdown-menu pull-right", // must be a unique class name or constellation of class names within the actionDropDown - dropDownMenuText: "dropdown-text", // must be a unique class name or constellation of class names within the actionDropDown - footer: "bootgrid-footer container-fluid", - header: "bootgrid-header container-fluid", - icon: "icon glyphicon", - iconColumns: "glyphicon-th-list", - iconDown: "glyphicon-chevron-down", - iconRefresh: "glyphicon-refresh", - iconSearch: "glyphicon-search", - iconUp: "glyphicon-chevron-up", - infos: "infos", // must be a unique class name or constellation of class names within the header and footer, - left: "text-left", - pagination: "pagination", // must be a unique class name or constellation of class names within the header and footer - paginationButton: "button", // must be a unique class name or constellation of class names within the pagination - - /** - * CSS class to select the parent div which activates responsive mode. - * - * @property responsiveTable - * @type String - * @default "table-responsive" - * @for css - * @since 1.1.0 - **/ - responsiveTable: "table-responsive", - - right: "text-right", - search: "search form-group", // must be a unique class name or constellation of class names within the header and footer - searchField: "search-field form-control", - selectBox: "select-box", // must be a unique class name or constellation of class names within the entire table - selectCell: "select-cell", // must be a unique class name or constellation of class names within the entire table - - /** - * CSS class to highlight selected rows. - * - * @property selected - * @type String - * @default "active" - * @for css - * @since 1.1.0 - **/ - selected: "active", - - sortable: "sortable", - table: "bootgrid-table table" - }, - + 0 : "success", + /** - * A dictionary of formatters. + * Specifies a neutral informative change or action. * - * @property formatters - * @type Object - * @for defaults - * @since 1.0.0 + * @property 1 + * @type String + * @for statusMapping **/ - formatters: {}, - + 1 : "info", + /** - * Contains all labels. + * Specifies a warning that might need attention. * - * @property labels - * @type Object - * @for defaults + * @property 2 + * @type String + * @for statusMapping **/ - labels: { - all: "All", - infos: "Showing {{ctx.start}} to {{ctx.end}} of {{ctx.total}} entries", - loading: "Loading...", - noResults: "No results found!", - refresh: "Refresh", - search: "Search" - }, - + 2 : "warning", + /** - * Specifies the mapping between status and contextual classes to color rows. + * Specifies a dangerous or potentially negative action. * - * @property statusMapping - * @type Object - * @for defaults - * @since 1.2.0 + * @property 3 + * @type String + * @for statusMapping **/ - statusMapping: { - /** - * Specifies a successful or positive action. - * - * @property 0 - * @type String - * @for statusMapping - **/ - 0: "success", - - /** - * Specifies a neutral informative change or action. - * - * @property 1 - * @type String - * @for statusMapping - **/ - 1: "info", - - /** - * Specifies a warning that might need attention. - * - * @property 2 - * @type String - * @for statusMapping - **/ - 2: "warning", - - /** - * Specifies a dangerous or potentially negative action. - * - * @property 3 - * @type String - * @for statusMapping - **/ - 3: "danger" - }, - - /** - * Contains all templates. - * - * @property templates - * @type Object - * @for defaults - **/ - templates: { - actionButton: "", - actionDropDown: "
", - actionDropDownItem: "
  • {{ctx.text}}
  • ", - actionDropDownCheckboxItem: "
  • ", - actions: "
    ", - body: "", - cell: "{{ctx.content}}", - footer: "

    ", - header: "

    ", - headerCell: "{{ctx.column.text}}{{ctx.icon}}", - icon: "", - infos: "
    {{lbl.infos}}
    ", - loading: "{{lbl.loading}}", - noResults: "{{lbl.noResults}}", - pagination: "", - paginationItem: "
  • {{ctx.text}}
  • ", - rawHeaderCell: "{{ctx.content}}", // Used for the multi select box - row: "{{ctx.cells}}", - search: "
    ", - select: "" - } + 3 : "danger" + }, + + /** + * Contains all templates. + * + * @property templates + * @type Object + * @for defaults + **/ + templates : { + actionButton : "", + actionDropDown : "
    ", + actionDropDownItem : "
  • {{ctx.text}}
  • ", + actionDropDownCheckboxItem : "
  • ", + actions : "
    ", + body : "", + cell : "{{ctx.content}}", + footer : "

    ", + header : "

    ", + headerCell : "{{ctx.column.text}}{{ctx.icon}}", + icon : "", + infos : "
    {{lbl.infos}}
    ", + loading : "{{lbl.loading}}", + noResults : "{{lbl.noResults}}", + pagination : "", + paginationItem : "
  • {{ctx.text}}
  • ", + rawHeaderCell : "{{ctx.content}}", // Used for the multi select box + row : "{{ctx.cells}}", + search : "
    ", + select : "" + } }; /** @@ -425,29 +449,24 @@ Grid.defaults = { * @param rows {Array} An array of rows to append * @chainable **/ -Grid.prototype.append = function(rows) -{ - if (this.options.ajax) - { - // todo: implement ajax PUT - } - else - { - var appendedRows = []; - for (var i = 0; i < rows.length; i++) - { - if (appendRow.call(this, rows[i])) - { - appendedRows.push(rows[i]); - } - } - sortRows.call(this); - highlightAppendedRows.call(this, appendedRows); - loadData.call(this); - this.element.trigger("appended" + namespace, [appendedRows]); +Grid.prototype.append = function(rows) { + if (this.options.ajax) { + // todo: implement ajax PUT + } + else { + var appendedRows = []; + for (var i = 0; i < rows.length; i++) { + if (appendRow.call(this, rows[ i ])) { + appendedRows.push(rows[ i ]); + } } - - return this; + sortRows.call(this); + highlightAppendedRows.call(this, appendedRows); + loadData.call(this); + this.element.trigger("appended" + namespace, [ appendedRows ]); + } + + return this; }; /** @@ -456,23 +475,20 @@ Grid.prototype.append = function(rows) * @method clear * @chainable **/ -Grid.prototype.clear = function() -{ - if (this.options.ajax) - { - // todo: implement ajax POST - } - else - { - var removedRows = $.extend([], this.rows); - this.rows = []; - this.current = 1; - this.total = 0; - loadData.call(this); - this.element.trigger("cleared" + namespace, [removedRows]); - } - - return this; +Grid.prototype.clear = function() { + if (this.options.ajax) { + // todo: implement ajax POST + } + else { + var removedRows = $.extend([], this.rows); + this.rows = []; + this.current = 1; + this.total = 0; + loadData.call(this); + this.element.trigger("cleared" + namespace, [ removedRows ]); + } + + return this; }; /** @@ -481,21 +497,18 @@ Grid.prototype.clear = function() * @method destroy * @chainable **/ -Grid.prototype.destroy = function() -{ - // todo: this method has to be optimized (the complete initial state must be restored) - $(window).off(namespace); - if (this.options.navigation & 1) - { - this.header.remove(); - } - if (this.options.navigation & 2) - { - this.footer.remove(); - } - this.element.before(this.origin).remove(); - - return this; +Grid.prototype.destroy = function() { + // todo: this method has to be optimized (the complete initial state must be restored) + $(window).off(namespace); + if (this.options.navigation & 1) { + this.header.remove(); + } + if (this.options.navigation & 2) { + this.footer.remove(); + } + this.element.before(this.origin).remove(); + + return this; }; /** @@ -504,12 +517,11 @@ Grid.prototype.destroy = function() * @method reload * @chainable **/ -Grid.prototype.reload = function() -{ - this.current = 1; // reset - loadData.call(this); - - return this; +Grid.prototype.reload = function() { + this.current = 1; // reset + loadData.call(this); + + return this; }; /** @@ -519,69 +531,59 @@ Grid.prototype.reload = function() * @param [rowsIds] {Array} An array of rows ids to remove * @chainable **/ -Grid.prototype.remove = function(rowIds) -{ - if (this.identifier != null) - { - var that = this; - - if (this.options.ajax) - { - // todo: implement ajax DELETE - } - else - { - rowIds = rowIds || this.selectedRows; - var id, - removedRows = []; - - for (var i = 0; i < rowIds.length; i++) - { - id = rowIds[i]; - - for (var j = 0; j < this.rows.length; j++) - { - if (this.rows[j][this.identifier] === id) - { - removedRows.push(this.rows[j]); - this.rows.splice(j, 1); - break; - } - } - } - - this.current = 1; // reset - loadData.call(this); - this.element.trigger("removed" + namespace, [removedRows]); +Grid.prototype.remove = function(rowIds) { + if (this.identifier != null) { + var that = this; + + if (this.options.ajax) { + // todo: implement ajax DELETE + } + else { + rowIds = rowIds || this.selectedRows; + var id, + removedRows = []; + + for (var i = 0; i < rowIds.length; i++) { + id = rowIds[ i ]; + + for (var j = 0; j < this.rows.length; j++) { + if (this.rows[ j ][ this.identifier ] === id) { + removedRows.push(this.rows[ j ]); + this.rows.splice(j, 1); + break; + } } + } + + this.current = 1; // reset + loadData.call(this); + this.element.trigger("removed" + namespace, [ removedRows ]); } - - return this; + } + + return this; }; /** - * Searches in all rows for a specific phrase (but only in visible cells). + * Searches in all rows for a specific phrase (but only in visible cells). * The search filter will be reseted, if no argument is provided. * * @method search * @param [phrase] {String} The phrase to search for * @chainable **/ -Grid.prototype.search = function(phrase) -{ - phrase = phrase || ""; - - if (this.searchPhrase !== phrase) - { - var selector = getCssSelector(this.options.css.searchField), - searchFields = findFooterAndHeaderItems.call(this, selector); - searchFields.val(phrase); - } - - executeSearch.call(this, phrase); - - - return this; +Grid.prototype.search = function(phrase) { + phrase = phrase || ""; + + if (this.searchPhrase !== phrase) { + var selector = getCssSelector(this.options.css.searchField), + searchFields = findFooterAndHeaderItems.call(this, selector); + searchFields.val(phrase); + } + + executeSearch.call(this, phrase); + + return this; }; /** @@ -592,62 +594,52 @@ Grid.prototype.search = function(phrase) * @param [rowsIds] {Array} An array of rows ids to select * @chainable **/ -Grid.prototype.select = function(rowIds) -{ - if (this.selection) - { - rowIds = rowIds || this.currentRows.propValues(this.identifier); - - var id, i, - selectedRows = []; - - while (rowIds.length > 0 && !(!this.options.multiSelect && selectedRows.length === 1)) - { - id = rowIds.pop(); - if ($.inArray(id, this.selectedRows) === -1) - { - for (i = 0; i < this.currentRows.length; i++) - { - if (this.currentRows[i][this.identifier] === id) - { - selectedRows.push(this.currentRows[i]); - this.selectedRows.push(id); - break; - } - } - } - } - - if (selectedRows.length > 0) - { - var selectBoxSelector = getCssSelector(this.options.css.selectBox), - selectMultiSelectBox = this.selectedRows.length >= this.currentRows.length; - - i = 0; - while (!this.options.keepSelection && selectMultiSelectBox && i < this.currentRows.length) - { - selectMultiSelectBox = ($.inArray(this.currentRows[i++][this.identifier], this.selectedRows) !== -1); - } - this.element.find("thead " + selectBoxSelector).prop("checked", selectMultiSelectBox); - - if (!this.options.multiSelect) - { - this.element.find("tbody > tr " + selectBoxSelector + ":checked") - .trigger("click" + namespace); - } - - for (i = 0; i < this.selectedRows.length; i++) - { - this.element.find("tbody > tr[data-row-id=\"" + this.selectedRows[i] + "\"]") - .addClass(this.options.css.selected)._bgAria("selected", "true") - .find(selectBoxSelector).prop("checked", true); - } - - this.element.trigger("selected" + namespace, [selectedRows]); +Grid.prototype.select = function(rowIds) { + if (this.selection) { + rowIds = rowIds || this.currentRows.propValues(this.identifier); + + var id, i, + selectedRows = []; + + while (rowIds.length > 0 && !(!this.options.multiSelect && selectedRows.length === 1)) { + id = rowIds.pop(); + if ($.inArray(id, this.selectedRows) === -1) { + for (i = 0; i < this.currentRows.length; i++) { + if (this.currentRows[ i ][ this.identifier ] === id) { + selectedRows.push(this.currentRows[ i ]); + this.selectedRows.push(id); + break; + } } + } } - - return this; + + if (selectedRows.length > 0) { + var selectBoxSelector = getCssSelector(this.options.css.selectBox), + selectMultiSelectBox = this.selectedRows.length >= this.currentRows.length; + + i = 0; + while (!this.options.keepSelection && selectMultiSelectBox && i < this.currentRows.length) { + selectMultiSelectBox = ($.inArray(this.currentRows[ i++ ][ this.identifier ], this.selectedRows) !== -1); + } + this.element.find("thead " + selectBoxSelector).prop("checked", selectMultiSelectBox); + + if (!this.options.multiSelect) { + this.element.find("tbody > tr " + selectBoxSelector + ":checked") + .trigger("click" + namespace); + } + + for (i = 0; i < this.selectedRows.length; i++) { + this.element.find("tbody > tr[data-row-id=\"" + this.selectedRows[ i ] + "\"]") + .addClass(this.options.css.selected)._bgAria("selected", "true") + .find(selectBoxSelector).prop("checked", true); + } + + this.element.trigger("selected" + namespace, [ selectedRows ]); + } + } + + return this; }; /** @@ -658,75 +650,65 @@ Grid.prototype.select = function(rowIds) * @param [rowsIds] {Array} An array of rows ids to deselect * @chainable **/ -Grid.prototype.deselect = function(rowIds) -{ - if (this.selection) - { - rowIds = rowIds || this.currentRows.propValues(this.identifier); - - var id, i, pos, - deselectedRows = []; - - while (rowIds.length > 0) - { - id = rowIds.pop(); - pos = $.inArray(id, this.selectedRows); - if (pos !== -1) - { - for (i = 0; i < this.currentRows.length; i++) - { - if (this.currentRows[i][this.identifier] === id) - { - deselectedRows.push(this.currentRows[i]); - this.selectedRows.splice(pos, 1); - break; - } - } - } - } - - if (deselectedRows.length > 0) - { - var selectBoxSelector = getCssSelector(this.options.css.selectBox); - - this.element.find("thead " + selectBoxSelector).prop("checked", false); - for (i = 0; i < deselectedRows.length; i++) - { - this.element.find("tbody > tr[data-row-id=\"" + deselectedRows[i][this.identifier] + "\"]") - .removeClass(this.options.css.selected)._bgAria("selected", "false") - .find(selectBoxSelector).prop("checked", false); - } - - this.element.trigger("deselected" + namespace, [deselectedRows]); +Grid.prototype.deselect = function(rowIds) { + if (this.selection) { + rowIds = rowIds || this.currentRows.propValues(this.identifier); + + var id, i, pos, + deselectedRows = []; + + while (rowIds.length > 0) { + id = rowIds.pop(); + pos = $.inArray(id, this.selectedRows); + if (pos !== -1) { + for (i = 0; i < this.currentRows.length; i++) { + if (this.currentRows[ i ][ this.identifier ] === id) { + deselectedRows.push(this.currentRows[ i ]); + this.selectedRows.splice(pos, 1); + break; + } } + } } - - return this; + + if (deselectedRows.length > 0) { + var selectBoxSelector = getCssSelector(this.options.css.selectBox); + + this.element.find("thead " + selectBoxSelector).prop("checked", false); + for (i = 0; i < deselectedRows.length; i++) { + this.element.find("tbody > tr[data-row-id=\"" + deselectedRows[ i ][ this.identifier ] + "\"]") + .removeClass(this.options.css.selected)._bgAria("selected", "false") + .find(selectBoxSelector).prop("checked", false); + } + + this.element.trigger("deselected" + namespace, [ deselectedRows ]); + } + } + + return this; }; /** - * Sorts the rows by a given sort descriptor dictionary. + * Sorts the rows by a given sort descriptor dictionary. * The sort filter will be reseted, if no argument is provided. * * @method sort * @param [dictionary] {Object} A sort descriptor dictionary that contains the sort information * @chainable **/ -Grid.prototype.sort = function(dictionary) -{ - var values = (dictionary) ? $.extend({}, dictionary) : {}; - - if (values === this.sortDictionary) - { - return this; - } - - this.sortDictionary = values; - renderTableHeader.call(this); - sortRows.call(this); - loadData.call(this); - +Grid.prototype.sort = function(dictionary) { + var values = (dictionary) ? $.extend({}, dictionary) : {}; + + if (values === this.sortDictionary) { return this; + } + + this.sortDictionary = values; + renderTableHeader.call(this); + sortRows.call(this); + loadData.call(this); + + return this; }; /** @@ -738,9 +720,8 @@ Grid.prototype.sort = function(dictionary) * @return {Array} Returns a list of the column settings. * @since 1.2.0 **/ -Grid.prototype.getColumnSettings = function() -{ - return $.merge([], this.columns); +Grid.prototype.getColumnSettings = function() { + return $.merge([], this.columns); }; /** @@ -752,9 +733,8 @@ Grid.prototype.getColumnSettings = function() * @return {Number} Returns the current page index. * @since 1.2.0 **/ -Grid.prototype.getCurrentPage = function() -{ - return this.current; +Grid.prototype.getCurrentPage = function() { + return this.current; }; /** @@ -766,9 +746,8 @@ Grid.prototype.getCurrentPage = function() * @return {Array} Returns the current rows. * @since 1.2.0 **/ -Grid.prototype.getCurrentRows = function() -{ - return $.merge([], this.currentRows); +Grid.prototype.getCurrentRows = function() { + return $.merge([], this.currentRows); }; /** @@ -780,9 +759,8 @@ Grid.prototype.getCurrentRows = function() * @return {Number} Returns the row count per page. * @since 1.2.0 **/ -Grid.prototype.getRowCount = function() -{ - return this.rowCount; +Grid.prototype.getRowCount = function() { + return this.rowCount; }; /** @@ -794,9 +772,8 @@ Grid.prototype.getRowCount = function() * @return {String} Returns the actual search phrase. * @since 1.2.0 **/ -Grid.prototype.getSearchPhrase = function() -{ - return this.searchPhrase; +Grid.prototype.getSearchPhrase = function() { + return this.searchPhrase; }; /** @@ -808,9 +785,8 @@ Grid.prototype.getSearchPhrase = function() * @return {Array} Returns all selected rows. * @since 1.2.0 **/ -Grid.prototype.getSelectedRows = function() -{ - return $.merge([], this.selectedRows); +Grid.prototype.getSelectedRows = function() { + return $.merge([], this.selectedRows); }; /** @@ -822,9 +798,8 @@ Grid.prototype.getSelectedRows = function() * @return {Object} Returns the sort dictionary. * @since 1.2.0 **/ -Grid.prototype.getSortDictionary = function() -{ - return $.extend({}, this.sortDictionary); +Grid.prototype.getSortDictionary = function() { + return $.extend({}, this.sortDictionary); }; /** @@ -836,9 +811,8 @@ Grid.prototype.getSortDictionary = function() * @return {Number} Returns the total page count. * @since 1.2.0 **/ -Grid.prototype.getTotalPageCount = function() -{ - return this.totalPages; +Grid.prototype.getTotalPageCount = function() { + return this.totalPages; }; /** @@ -850,7 +824,6 @@ Grid.prototype.getTotalPageCount = function() * @return {Number} Returns the total row count. * @since 1.2.0 **/ -Grid.prototype.getTotalRowCount = function() -{ - return this.total; -}; \ No newline at end of file +Grid.prototype.getTotalRowCount = function() { + return this.total; +};