diff --git a/demo/index.htm b/demo/index.htm index ae13225..5f8ee8b 100644 --- a/demo/index.htm +++ b/demo/index.htm @@ -47,7 +47,6 @@
- @@ -61,12 +60,20 @@ + +
+ + + + +
+ - - + + @@ -193,22 +200,47 @@ }); } + var timerKey = 'initRender'; + var timerStart = null; + $("#grid").on("loaded.rs.jquery.bootgrid", function (e){ + if (timerKey) + console.timeEnd(timerKey); + if (timerStart) + $(".loading").text("rendered time: " + (new Date().getTime() - timerStart) + "ms"); + timerKey = null; + timerStart = null; + }); + + function startTimer(key){ + $(".loading").html(timerKey + "loading..."); + console.time(timerKey); + timerStart = new Date(); + } + + startTimer(timerKey); init(); - - $("#append").on("click", function () + appendCounter = 100; + + $(".append").on("click", function (e) { - $("#grid").bootgrid("append", [{ - id: 0, - sender: "hh@derhase.de", - received: "Gestern", - link: "" - }, - { - id: 12, - sender: "er@fsdfs.de", - received: "Heute", - link: "" - }]); + var data = []; + var rows = $(e.target).data('rows'); + for (var i=0; i < rows; i++) + { + var row = { + id: i+appendCounter, + sender: "sample@example.com", + received: new Date(new Date().getTime()+(i*24*60*60*1000)).toString(), + link: "link", + status: "status" + i, + hidden: (i % 2 === 0) ? "yes" : "no" + }; + data.push(row); + } + appendCounter = appendCounter + rows; + timerKey = "[appendData: " + rows + " rows] "; + startTimer(timerKey); + $("#grid").bootgrid("append", data); }); $("#clear").on("click", function () @@ -275,4 +307,4 @@ }); - \ No newline at end of file + diff --git a/src/extensions.js b/src/extensions.js index cb37139..e32ab1c 100644 --- a/src/extensions.js +++ b/src/extensions.js @@ -9,8 +9,8 @@ $.fn.extend({ _bgBusyAria: function(busy) { - return (busy == null || busy) ? - this._bgAria("busy", "true") : + return (busy == null || busy) ? + this._bgAria("busy", "true") : this._bgAria("busy", "false"); }, @@ -21,29 +21,29 @@ $.fn.extend({ _bgEnableAria: function (enable) { - return (enable == null || enable) ? - this.removeClass("disabled")._bgAria("disabled", "false") : + 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") : + return (enable == null || enable) ? + this.removeAttr("disabled") : this.attr("disabled", "disable"); }, _bgShowAria: function (show) { - return (show == null || 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") : + return (select == null || select) ? + this.addClass("active")._bgAria("selected", "true") : this.removeClass("active")._bgAria("selected", "false"); }, @@ -53,118 +53,171 @@ $.fn.extend({ } }); -if (!String.prototype.resolve) -{ - var formatter = { - "checked": function(value) +var formatter = { + "checked": function(value) + { + if (typeof value === "boolean") { - 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); + } + if (hasEnd) + { + s = s.substr(0, s.length - 1); + } + + if (hasStart || hasEnd) { - return (value) ? "checked=\"checked\"" : ""; + str[i] = s; //plain old html + } else { + str[i] = { + token: str[i], + key: s.split(".") + }; } - return value; } - }; + _templateCache[template] = str; + } + return _templateCache[template]; +}; +/* +// ONLY FOR TESTING +var String.prototype.resolve_old = function (substitutes, prefixes) +{ + var result = this; - String.prototype.resolve = function (substitutes, prefixes) + $.each(substitutes, function (key, value) { - var result = this; - $.each(substitutes, function (key, value) + if (value != null && typeof value !== "function") { - if (value != null && typeof value !== "function") + if (typeof value === "object") { - if (typeof value === "object") - { - var keys = (prefixes) ? $.extend([], prefixes) : []; - keys.push(key); - result = result.resolve(value, keys) + ""; - } - else + var keys = (prefixes) ? $.extend([], prefixes) : []; + keys.push(key); + result = result.resolve_old(value, keys) + ""; + } + else + { + if (formatter && formatter[key] && typeof formatter[key] === "function") { - if (formatter && formatter[key] && typeof formatter[key] === "function") - { - value = formatter[key](value); - } - key = (prefixes) ? prefixes.join(".") + "." + key : key; - var pattern = new RegExp("\\{\\{" + key + "\\}\\}", "gm"); - result = result.replace(pattern, (value.replace) ? value.replace(/\$/gi, "$") : value); + value = formatter[key](value); } + key = (prefixes) ? prefixes.join(".") + "." + key : key; + var pattern = new RegExp("\\{\\{" + key + "\\}\\}", "gm"); + result = result.replace(pattern, (value.replace) ? value.replace(/\$/gi, "$") : value); } - }); - return result; - }; -} + } + }); -if (!Array.prototype.first) + return result; +}; +*/ +var template = function (template, substitutes, prefixes) { - Array.prototype.first = function (condition) - { - for (var i = 0; i < this.length; i++) - { - var item = this[i]; - if (condition(item)) + var str = getTemplate(template); + 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])) { - return item; + v = substitutes[key[0]]; + } + else + { + continue; } - } - return null; - }; -} -if (!Array.prototype.contains) + for (var k = 1; k < key.length; k++){ + if (v.hasOwnProperty(key[k])){ + v = v[key[k]]; + } else { + v = ""; + 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 + } + } + // ONLY FOR TESTING + /* + var result_old = this.resolve_old(substitutes, prefixes); + if (result !== result_old){ + console.warn("Difference templating result"); + console.log(result); + console.log(result_old); + } +*/ + return result; +}; +var arrayContains = function (arr, condition) { - Array.prototype.contains = function (condition) + for (var i = 0; i < arr.length; i++) { - for (var i = 0; i < this.length; i++) + var item = arr[i]; + if (condition(item)) { - var item = this[i]; - if (condition(item)) - { - return true; - } + return true; } - return false; - }; -} + } + return false; +}; -if (!Array.prototype.page) +var arrayPage = function (arr, page, size) { - 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) + var skip = (page - 1) * size, + end = skip + size; + return (arr.length > skip) ? + (arr.length > end) ? arr.slice(skip, end) : + arr.slice(skip) : []; +}; + +var arrayWhere = function (arr, condition) { - Array.prototype.where = function (condition) + var result = []; + for (var i = 0; i < arr.length; i++) { - var result = []; - for (var i = 0; i < this.length; i++) + var item = arr[i]; + if (condition(item)) { - var item = this[i]; - if (condition(item)) - { - result.push(item); - } + result.push(item); } - return result; - }; -} + } + return result; +}; -if (!Array.prototype.propValues) +var arrayPropValues = function (arr, propName) { - Array.prototype.propValues = function (propName) + var result = []; + for (var i = 0; i < arr.length; i++) { - var result = []; - for (var i = 0; i < this.length; i++) - { - result.push(this[i][propName]); - } - return result; - }; -} \ No newline at end of file + result.push(arr[i][propName]); + } + return result; +}; + diff --git a/src/internal.js b/src/internal.js index 9b9bba4..febc364 100644 --- a/src/internal.js +++ b/src/internal.js @@ -6,22 +6,18 @@ var namespace = ".rs.jquery.bootgrid"; // GRID INTERNAL FUNCTIONS // ===================== -function appendRow(row) +function appendRows(rows) { - var that = this; + var that = this; - function exists(item) - { - return that.identifier && item[that.identifier] === row[that.identifier]; - } + var appendedRows = rows.slice(); + appendedRows.filter(function(item) { + return !(that.identifier && item[that.identifier] === rows[that.identifier]); + }); - if (!this.rows.contains(exists)) - { - this.rows.push(row); - return true; - } + this.rows = this.rows.concat(appendedRows); - return false; + return appendedRows; } function findFooterAndHeaderItems(selector) @@ -117,7 +113,7 @@ function loadColumns() 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" : + width: ($.isNumeric(data.width)) ? data.width + "px" : (typeof(data.width) === "string") ? data.width : null }; that.columns.push(column); @@ -244,11 +240,11 @@ function loadData() } else { - var rows = (this.searchPhrase.length > 0) ? this.rows.where(containsPhrase) : this.rows, + var rows = (this.searchPhrase.length > 0) ? arrayWhere(this.rows, containsPhrase) : this.rows, total = rows.length; if (this.rowCount !== -1) { - rows = rows.page(this.current, this.rowCount); + rows = arrayPage(rows, this.current, this.rowCount); } // todo: improve the following comment @@ -263,7 +259,7 @@ function loadRows() { var that = this, rows = this.element.find("tbody > tr"); - + var convertedRows = []; rows.each(function () { var $this = $(this), @@ -275,9 +271,9 @@ function loadRows() row[column.id] = column.converter.from(cells.eq(i).text()); }); - appendRow.call(that, row); + convertedRows.push(row); }); - + appendRows.call(that, convertedRows); setTotals.call(this, this.rows.length); sortRows.call(this); } @@ -306,13 +302,13 @@ function prepareTable() if (this.options.navigation & 1) { - this.header = $(tpl.header.resolve(getParams.call(this, { id: this.element._bgId() + "-header" }))); + this.header = $(template(tpl.header, 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" }))); + this.footer = $(template(tpl.footer, getParams.call(this, { id: this.element._bgId() + "-footer" }))); wrapper.after(this.footer); } } @@ -329,13 +325,13 @@ function renderActions() { var that = this, tpl = this.options.templates, - actions = $(tpl.actions.resolve(getParams.call(this))); + actions = $(template(tpl.actions, 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, + var refreshIcon = template(tpl.icon, getParams.call(this, { iconCss: css.iconRefresh })), + refresh = $(template(tpl.actionButton, getParams.call(this, { content: refreshIcon, text: this.options.labels.refresh }))) .on("click" + namespace, function (e) { @@ -365,8 +361,8 @@ function renderColumnSelection(actions) 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 }))), + icon = template(tpl.icon, getParams.call(this, { iconCss: css.iconColumns })), + dropDown = $(template(tpl.actionDropDown, getParams.call(this, { content: icon }))), selector = getCssSelector(css.dropDownItem), checkboxSelector = getCssSelector(css.dropDownItemCheckbox), itemsSelector = getCssSelector(css.dropDownMenuItems); @@ -375,24 +371,25 @@ function renderColumnSelection(actions) { if (column.visibleInSelection) { - var item = $(tpl.actionDropDownCheckboxItem.resolve(getParams.call(that, + var item = $(template(tpl.actionDropDownCheckboxItem, getParams.call(that, { name: column.id, label: column.text, checked: column.visible }))) .on("click" + namespace, selector, function (e) { e.stopPropagation(); - + var $this = $(this), checkbox = $this.find(checkboxSelector); if (!checkbox.prop("disabled")) { column.visible = checkbox.prop("checked"); - var enable = that.columns.where(isVisible).length > 1; + var enable = arrayWhere(that.columns, 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); } }); dropDown.find(getCssSelector(css.dropDownMenuItems)).append(item); @@ -412,7 +409,7 @@ function renderInfos() if (infoItems.length > 0) { var end = (this.current * this.rowCount), - infos = $(this.options.templates.infos.resolve(getParams.call(this, { + infos = $(template(this.options.templates.infos, 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 @@ -427,13 +424,13 @@ function renderNoResultsRow() { var tbody = this.element.children("tbody").first(), tpl = this.options.templates, - count = this.columns.where(isVisible).length; + count = arrayWhere(this.columns, isVisible).length; if (this.selection) { count = count + 1; } - tbody.html(tpl.noResults.resolve(getParams.call(this, { columns: count }))); + tbody.html(template(tpl.noResults, getParams.call(this, { columns: count }))); } function renderPagination() @@ -448,7 +445,7 @@ function renderPagination() var tpl = this.options.templates, current = this.current, totalPages = this.totalPages, - pagination = $(tpl.pagination.resolve(getParams.call(this))), + pagination = $(template(tpl.pagination, getParams.call(this))), offsetRight = totalPages - current, offsetLeft = (this.options.padding - current) * -1, startWith = ((offsetRight >= this.options.padding) ? @@ -491,7 +488,7 @@ function renderPaginationItem(list, page, text, markerCss) tpl = this.options.templates, css = this.options.css, values = getParams.call(this, { css: markerCss, text: text, page: page }), - item = $(tpl.paginationItem.resolve(values)) + item = $(template(tpl.paginationItem, values)) .on("click" + namespace, getCssSelector(css.paginationButton), function (e) { e.stopPropagation(); @@ -532,7 +529,7 @@ function renderRowCountSelection(actions) { var css = this.options.css, tpl = this.options.templates, - dropDown = $(tpl.actionDropDown.resolve(getParams.call(this, { content: getText(this.rowCount) }))), + dropDown = $(template(tpl.actionDropDown, getParams.call(this, { content: getText(this.rowCount) }))), menuSelector = getCssSelector(css.dropDownMenu), menuTextSelector = getCssSelector(css.dropDownMenuText), menuItemsSelector = getCssSelector(css.dropDownMenuItems), @@ -540,7 +537,7 @@ function renderRowCountSelection(actions) $.each(rowCountList, function (index, value) { - var item = $(tpl.actionDropDownItem.resolve(getParams.call(that, + var item = $(template(tpl.actionDropDownItem, getParams.call(that, { text: getText(value), action: value }))) ._bgSelectAria(value === that.rowCount) .on("click" + namespace, menuItemSelector, function (e) @@ -590,9 +587,9 @@ function renderRows(rows) if (that.selection) { var selected = ($.inArray(row[that.identifier], that.selectedRows) !== -1), - selectBox = tpl.select.resolve(getParams.call(that, + selectBox = template(tpl.select, getParams.call(that, { type: "checkbox", value: row[that.identifier], checked: selected })); - cells += tpl.cell.resolve(getParams.call(that, { content: selectBox, css: css.selectCell })); + cells += template(tpl.cell, getParams.call(that, { content: selectBox, css: css.selectCell })); allRowsSelected = (allRowsSelected && selected); if (selected) { @@ -615,7 +612,7 @@ function renderRows(rows) 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, { + cells += template(tpl.cell, getParams.call(that, { content: (value == null || value === "") ? " " : value, css: ((column.align === "right") ? css.right : (column.align === "center") ? css.center : css.left) + cssClass, @@ -627,7 +624,7 @@ function renderRows(rows) { rowAttr += " class=\"" + rowCss + "\""; } - html += tpl.row.resolve(getParams.call(that, { attr: rowAttr, cells: cells })); + html += template(tpl.row, getParams.call(that, { attr: rowAttr, cells: cells })); }); // sets or clears multi selectbox state @@ -712,7 +709,7 @@ function renderSearchField() timer = null, // fast keyup detection currentValue = "", searchFieldSelector = getCssSelector(css.searchField), - search = $(tpl.search.resolve(getParams.call(this))), + search = $(template(tpl.search, getParams.call(this))), searchField = (search.is(searchFieldSelector)) ? search : search.find(searchFieldSelector); @@ -761,8 +758,8 @@ function renderTableHeader() 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, + template(tpl.select, getParams.call(that, { type: "checkbox", value: "all" })) : ""; + html += template(tpl.rawHeaderCell, getParams.call(that, { content: selectBox, css: css.selectCell })); } @@ -773,10 +770,10 @@ function renderTableHeader() 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 })), + icon = template(tpl.icon, getParams.call(that, { iconCss: iconCss })), align = column.headerAlign, cssClass = (column.headerCssClass.length > 0) ? " " + column.headerCssClass : ""; - html += tpl.headerCell.resolve(getParams.call(that, { + html += template(tpl.headerCell, 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, @@ -873,7 +870,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(); }); } @@ -890,13 +887,13 @@ function showLoading() 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; + count = arrayWhere(that.columns, isVisible).length; if (that.selection) { count = count + 1; } - tbody.html(tpl.loading.resolve(getParams.call(that, { columns: count }))); + tbody.html(template(tpl.loading, getParams.call(that, { columns: count }))); if (that.rowCount !== -1 && padding > 0) { tbody.find("tr > td").css("padding", "20px 0 " + padding + "px"); @@ -945,4 +942,4 @@ function sortRows() this.rows.sort(sort); } } -} \ No newline at end of file +} diff --git a/src/public.js b/src/public.js index 16c5c44..032ffb3 100644 --- a/src/public.js +++ b/src/public.js @@ -128,7 +128,7 @@ Grid.defaults = { * @for searchSettings **/ delay: 250, - + /** * The characters to type before the search gets executed. * @@ -334,7 +334,10 @@ Grid.defaults = { **/ labels: { all: "All", - infos: "Showing {{ctx.start}} to {{ctx.end}} of {{ctx.total}} entries", + showing: "Showing", + to: "to", + of: "of", + entries: "entries", loading: "Loading...", noResults: "No results found!", refresh: "Refresh", @@ -376,7 +379,7 @@ Grid.defaults = { * @for statusMapping **/ 2: "warning", - + /** * Specifies a dangerous or potentially negative action. * @@ -406,7 +409,7 @@ Grid.defaults = { header: "

", headerCell: "", icon: "", - infos: "
{{lbl.infos}}
", + infos: "
{{lbl.showing}} {{ctx.start}} {{lbl.to}} {{ctx.end}} {{lbl.of}} {{ctx.total}} {{lbl.entries}}
", loading: "", noResults: "", pagination: "", @@ -433,14 +436,7 @@ Grid.prototype.append = function(rows) } else { - var appendedRows = []; - for (var i = 0; i < rows.length; i++) - { - if (appendRow.call(this, rows[i])) - { - appendedRows.push(rows[i]); - } - } + var appendedRows = appendRows.call(this, rows); sortRows.call(this); highlightAppendedRows.call(this, appendedRows); loadData.call(this); @@ -560,7 +556,7 @@ Grid.prototype.remove = function(rowIds) }; /** - * 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 @@ -596,7 +592,7 @@ Grid.prototype.select = function(rowIds) { if (this.selection) { - rowIds = rowIds || this.currentRows.propValues(this.identifier); + rowIds = rowIds || arrayPropValues(this.currentRows, this.identifier); var id, i, selectedRows = []; @@ -662,7 +658,7 @@ Grid.prototype.deselect = function(rowIds) { if (this.selection) { - rowIds = rowIds || this.currentRows.propValues(this.identifier); + rowIds = rowIds || arrayPropValues(this.currentRows, this.identifier); var id, i, pos, deselectedRows = []; @@ -705,7 +701,7 @@ Grid.prototype.deselect = function(rowIds) }; /** - * 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 @@ -853,4 +849,4 @@ Grid.prototype.getTotalPageCount = function() Grid.prototype.getTotalRowCount = function() { return this.total; -}; \ No newline at end of file +}; diff --git a/test/tests-extensions.js b/test/tests-extensions.js index f0e5994..64d21d6 100644 --- a/test/tests-extensions.js +++ b/test/tests-extensions.js @@ -13,7 +13,7 @@ test("String.resolve basic (one dimension) test", 1, function () stringToResolve = "{{first}} {{second}}"; // when - var result = stringToResolve.resolve(values); + var result = template(stringToResolve, values); // then equal(result, "test case", "Valid string"); @@ -38,7 +38,7 @@ test("String.resolve advanced (n dimension) test", 1, function () stringToResolve = "{{first.sub}} {{second}} {{third.more}} {{third.adv.test}} {{third.case}}"; // when - var result = stringToResolve.resolve(values); + var result = template(stringToResolve, values); // then equal(result, "this is a more advanced test case", "Valid string");
IDSenderIDSender Received Link Status {{ctx.column.text}}{{ctx.icon}}
{{lbl.loading}}
{{lbl.noResults}}