From fe3f95e569b61ddbd18bad5a2930115988eefcdc Mon Sep 17 00:00:00 2001 From: Zac Spitzer Date: Mon, 12 Oct 2015 12:08:01 +1100 Subject: [PATCH 1/8] Update internal.js there is no need to clone() here, it creates a memory leak as it's not attached the to DOM and therefore isn't removed by remove() --- src/internal.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/internal.js b/src/internal.js index 9b9bba4..1d7984e 100644 --- a/src/internal.js +++ b/src/internal.js @@ -873,7 +873,7 @@ 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(); + $(item).before(element).remove(); }); } @@ -945,4 +945,4 @@ function sortRows() this.rows.sort(sort); } } -} \ No newline at end of file +} From 1225b4a0475bcf57fc88dc9f98d284299f466ff2 Mon Sep 17 00:00:00 2001 From: Zac Spitzer Date: Fri, 30 Oct 2015 14:29:05 +1100 Subject: [PATCH 2/8] Add an event columnToggle Trigger an event when columns are toggled on or off --- src/internal.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/internal.js b/src/internal.js index 1d7984e..5ed0af8 100644 --- a/src/internal.js +++ b/src/internal.js @@ -393,6 +393,7 @@ function renderColumnSelection(actions) that.element.find("tbody").empty(); // Fixes an column visualization bug renderTableHeader.call(that); loadData.call(that); + that.element.trigger("columnToggle" + namespace, column); } }); dropDown.find(getCssSelector(css.dropDownMenuItems)).append(item); From 99ee4de6b6b9bcd942040b950b0bf7f50fe54124 Mon Sep 17 00:00:00 2001 From: CallMeBruce Date: Mon, 18 Apr 2016 12:29:40 +1000 Subject: [PATCH 3/8] Expose ajax error to callee More useful to the calling code to expose any ajax errors. Makes debugging difficult if you silently fail them. --- dist/jquery.bootgrid.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) 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); From 2d26feab84017344a679b45f5bbfdb54149941ae Mon Sep 17 00:00:00 2001 From: Zac Spitzer Date: Thu, 15 Sep 2016 11:48:17 +0200 Subject: [PATCH 4/8] Drastically faster templating approach The current templating approach is really inefficient, as it recursively loops thru every passed available variable for substituion and applies a regex for each one. It's much faster to initially parse the template and then only process those strings which are actually present in the template. This revised implementation both caches the parsed templates and then only processes the string which are present. The performance improvement is quite drastic, especially with large tables. A large table was previously taking 15s to render in Chrome, now takes less than 1s with this approach --- src/extensions.js | 74 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 20 deletions(-) diff --git a/src/extensions.js b/src/extensions.js index cb37139..4956360 100644 --- a/src/extensions.js +++ b/src/extensions.js @@ -65,32 +65,66 @@ if (!String.prototype.resolve) 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); + 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 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) + ""; - } + var str = getTemplate(this); + var result = ""; + for (var i = 0; i < str.length; i++){ + if (typeof str[i] === "object"){ + var key = str[i].key; + // now we have a variable to be substitued + if (substitutes.hasOwnProperty(key[0])) + var v = substitutes[key[0]]; else - { - if (formatter && formatter[key] && typeof formatter[key] === "function") - { - value = formatter[key](value); + continue; + + for (var k = 1; k < key.length; k++){ + if (v.hasOwnProperty(key[k])){ + v = v[key[k]]; + } else { + v = null; + break; } - key = (prefixes) ? prefixes.join(".") + "." + key : key; - var pattern = new RegExp("\\{\\{" + key + "\\}\\}", "gm"); - result = result.replace(pattern, (value.replace) ? value.replace(/\$/gi, "$") : value); } + 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; }; } @@ -167,4 +201,4 @@ if (!Array.prototype.propValues) } return result; }; -} \ No newline at end of file +} From d33185db250f7e109d80ba0cb49b69d1bffc0f1a Mon Sep 17 00:00:00 2001 From: Zac Spitzer Date: Thu, 15 Sep 2016 11:55:46 +0200 Subject: [PATCH 5/8] Update extensions.js --- src/extensions.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/extensions.js b/src/extensions.js index 4956360..968da4d 100644 --- a/src/extensions.js +++ b/src/extensions.js @@ -101,9 +101,10 @@ if (!String.prototype.resolve) 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])) - var v = substitutes[key[0]]; + v = substitutes[key[0]]; else continue; From 8d11d27a72c6b5d73d9a4d0c686296c457456802 Mon Sep 17 00:00:00 2001 From: Zac Spitzer Date: Thu, 15 Sep 2016 12:00:50 +0200 Subject: [PATCH 6/8] lint clean up --- src/extensions.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/extensions.js b/src/extensions.js index 968da4d..cab961c 100644 --- a/src/extensions.js +++ b/src/extensions.js @@ -76,11 +76,16 @@ if (!String.prototype.resolve) var hasStart = (s.charAt(0) === "}"); var hasEnd = (s.charAt(s.length - 1) === "{"); if (hasStart) + { s = s.substr(1); + } if (hasEnd) + { s = s.substr(0, s.length - 1); + } - if (hasStart || hasEnd){ + if (hasStart || hasEnd) + { str[i] = s; //plain old html } else { str[i] = { @@ -104,9 +109,13 @@ if (!String.prototype.resolve) 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])){ From 8e091326b4d0a8170e011818cf33137bbde11011 Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Wed, 26 Oct 2016 08:51:45 +0100 Subject: [PATCH 7/8] Add intelliJ directory to .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 From 4fcd625415071f8d714015fb8b44c47646a4e5fe Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Wed, 26 Oct 2016 09:00:36 +0100 Subject: [PATCH 8/8] Reformat whitespace --- src/extensions.js | 346 ++++----- src/fontawesome.js | 18 +- src/internal.js | 1692 ++++++++++++++++++++++---------------------- src/plugin.js | 64 +- src/public.js | 1195 +++++++++++++++---------------- 5 files changed, 1608 insertions(+), 1707 deletions(-) diff --git a/src/extensions.js b/src/extensions.js index cab961c..1bd4d67 100644 --- a/src/extensions.js +++ b/src/extensions.js @@ -1,214 +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); } - }; - - 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); - } - 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; + if (hasEnd) { + s = s.substr(0, s.length - 1); } - 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 - } + + 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 ] ]; } - return result; - }; + 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; - }; +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 5ed0af8..0b200a9 100644 --- a/src/internal.js +++ b/src/internal.js @@ -6,944 +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); - that.element.trigger("columnToggle" + namespace, column); - } - }); - 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]); - }); + $this.trigger("blur"); + } + ); + + list.append(item); + return item; } -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); - } - } -} - -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); + } } + ); + + replacePlaceHolder.call(this, searchItems, search); } - else - { - this.sortDictionary[columnId] = "asc"; - icon.addClass(css.iconUp); - } + } } -function replacePlaceHolder(placeholder, element) -{ - placeholder.each(function (index, item) - { - // todo: check how append is implemented. Perhaps cloning here is superfluous. - $(item).before(element).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 ] + } + ); + } + } + + 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; +};