From 936f695739d670043c36480688c5f2f21a6d2889 Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 22 Dec 2014 17:10:19 -0500 Subject: [PATCH] Added partially working data transfer --- examples/GearsBot/mods.conf | 1 + yeti/module_loader.py | 3 + .../bootgrid/jquery.bootgrid.css | 143 ++ .../bootgrid/jquery.bootgrid.js | 1748 +++++++++++++++++ .../bootgrid/jquery.bootgrid.min.css | 5 + .../bootgrid/jquery.bootgrid.min.js | 6 + .../webui-resources/favicon.ico | Bin 0 -> 1406 bytes .../webui-resources/index.html | 41 +- .../webui-resources/script.js | 22 + yeti/preloaded_modules/webui.py | 13 +- 10 files changed, 1953 insertions(+), 29 deletions(-) create mode 100644 yeti/preloaded_modules/webui-resources/bootgrid/jquery.bootgrid.css create mode 100644 yeti/preloaded_modules/webui-resources/bootgrid/jquery.bootgrid.js create mode 100644 yeti/preloaded_modules/webui-resources/bootgrid/jquery.bootgrid.min.css create mode 100644 yeti/preloaded_modules/webui-resources/bootgrid/jquery.bootgrid.min.js create mode 100644 yeti/preloaded_modules/webui-resources/favicon.ico create mode 100644 yeti/preloaded_modules/webui-resources/script.js diff --git a/examples/GearsBot/mods.conf b/examples/GearsBot/mods.conf index 100a6e6..5333cfe 100644 --- a/examples/GearsBot/mods.conf +++ b/examples/GearsBot/mods.conf @@ -1,4 +1,5 @@ [StartupMods] +yeti.preloaded_modules.webui modules.arcade_drive modules.claw modules.debug \ No newline at end of file diff --git a/yeti/module_loader.py b/yeti/module_loader.py index 8971b30..135c0a7 100644 --- a/yeti/module_loader.py +++ b/yeti/module_loader.py @@ -163,6 +163,9 @@ def load_coroutine(self, module_path=None): self.module_object.add_hook("exception", self._exception_handler) self.module_object.add_hook("reload", self.reload) + #embed self + self.module_object.loader = self + #Add module to the current context: yield from self.module_context.load_module_coroutine(self.module_object) diff --git a/yeti/preloaded_modules/webui-resources/bootgrid/jquery.bootgrid.css b/yeti/preloaded_modules/webui-resources/bootgrid/jquery.bootgrid.css new file mode 100644 index 0000000..a2df310 --- /dev/null +++ b/yeti/preloaded_modules/webui-resources/bootgrid/jquery.bootgrid.css @@ -0,0 +1,143 @@ +/*! + * jQuery Bootgrid v1.1.4 - 11/23/2014 + * Copyright (c) 2014 Rafael Staib (http://www.jquery-bootgrid.com) + * Licensed under MIT http://www.opensource.org/licenses/MIT + */ +.bootgrid-header, +.bootgrid-footer { + margin: 15px 0; +} +.bootgrid-header a, +.bootgrid-footer a { + outline: 0; +} +.bootgrid-header .search, +.bootgrid-footer .search { + display: inline-block; + margin: 0 20px 0 0; + vertical-align: middle; + width: 180px; +} +.bootgrid-header .search .glyphicon, +.bootgrid-footer .search .glyphicon { + top: 0; +} +.bootgrid-header .search.search-field::-ms-clear, +.bootgrid-footer .search.search-field::-ms-clear, +.bootgrid-header .search .search-field::-ms-clear, +.bootgrid-footer .search .search-field::-ms-clear { + display: none; +} +.bootgrid-header .pagination, +.bootgrid-footer .pagination { + margin: 0 !important; +} +.bootgrid-header .actionBar, +.bootgrid-footer .infoBar { + text-align: right; +} +.bootgrid-header .actionBar .btn-group > .btn-group .dropdown-menu, +.bootgrid-footer .infoBar .btn-group > .btn-group .dropdown-menu { + text-align: left; +} +.bootgrid-header .actionBar .btn-group > .btn-group .dropdown-menu .dropdown-item, +.bootgrid-footer .infoBar .btn-group > .btn-group .dropdown-menu .dropdown-item { + cursor: pointer; + display: block; + margin: 0; + padding: 3px 20px; + white-space: nowrap; +} +.bootgrid-header .actionBar .btn-group > .btn-group .dropdown-menu .dropdown-item:hover, +.bootgrid-footer .infoBar .btn-group > .btn-group .dropdown-menu .dropdown-item:hover, +.bootgrid-header .actionBar .btn-group > .btn-group .dropdown-menu .dropdown-item:focus, +.bootgrid-footer .infoBar .btn-group > .btn-group .dropdown-menu .dropdown-item:focus { + color: #262626; + text-decoration: none; + background-color: #f5f5f5; +} +.bootgrid-header .actionBar .btn-group > .btn-group .dropdown-menu .dropdown-item.dropdown-item-checkbox, +.bootgrid-footer .infoBar .btn-group > .btn-group .dropdown-menu .dropdown-item.dropdown-item-checkbox, +.bootgrid-header .actionBar .btn-group > .btn-group .dropdown-menu .dropdown-item .dropdown-item-checkbox, +.bootgrid-footer .infoBar .btn-group > .btn-group .dropdown-menu .dropdown-item .dropdown-item-checkbox { + margin: 0 2px 4px 0; + vertical-align: middle; +} +.bootgrid-header .actionBar .btn-group > .btn-group .dropdown-menu .dropdown-item.disabled, +.bootgrid-footer .infoBar .btn-group > .btn-group .dropdown-menu .dropdown-item.disabled { + cursor: not-allowed; +} +.bootgrid-table { + table-layout: fixed; +} +.bootgrid-table a { + outline: 0; +} +.bootgrid-table th > .column-header-anchor { + color: #333; + cursor: not-allowed; + display: block; + position: relative; + text-decoration: none; +} +.bootgrid-table th > .column-header-anchor.sortable { + cursor: pointer; +} +.bootgrid-table th > .column-header-anchor > .text { + display: block; + margin: 0 16px 0 0; + overflow: hidden; + -ms-text-overflow: ellipsis; + -o-text-overflow: ellipsis; + text-overflow: ellipsis; + white-space: nowrap; +} +.bootgrid-table th > .column-header-anchor > .icon { + display: block; + position: absolute; + right: 0; + top: 2px; +} +.bootgrid-table th:hover, +.bootgrid-table th:active { + background: #fafafa; +} +.bootgrid-table td { + overflow: hidden; + -ms-text-overflow: ellipsis; + -o-text-overflow: ellipsis; + text-overflow: ellipsis; + white-space: nowrap; +} +.bootgrid-table td.loading, +.bootgrid-table td.no-results { + background: #fff; + text-align: center; +} +.bootgrid-table th.select-cell, +.bootgrid-table td.select-cell { + text-align: center; + width: 30px; +} +.bootgrid-table th.select-cell .select-box, +.bootgrid-table td.select-cell .select-box { + margin: 0; + outline: 0; +} +.table-responsive .bootgrid-table { + table-layout: inherit !important; +} +.table-responsive .bootgrid-table th > .column-header-anchor > .text { + overflow: inherit !important; + -ms-text-overflow: inherit !important; + -o-text-overflow: inherit !important; + text-overflow: inherit !important; + white-space: inherit !important; +} +.table-responsive .bootgrid-table td { + overflow: inherit !important; + -ms-text-overflow: inherit !important; + -o-text-overflow: inherit !important; + text-overflow: inherit !important; + white-space: inherit !important; +} diff --git a/yeti/preloaded_modules/webui-resources/bootgrid/jquery.bootgrid.js b/yeti/preloaded_modules/webui-resources/bootgrid/jquery.bootgrid.js new file mode 100644 index 0000000..ffda62a --- /dev/null +++ b/yeti/preloaded_modules/webui-resources/bootgrid/jquery.bootgrid.js @@ -0,0 +1,1748 @@ +/*! + * jQuery Bootgrid v1.1.4 - 11/23/2014 + * Copyright (c) 2014 Rafael Staib (http://www.jquery-bootgrid.com) + * Licensed under MIT http://www.opensource.org/licenses/MIT + */ +;(function ($, window, undefined) +{ + /*jshint validthis: true */ + "use strict"; + + // GRID INTERNAL FIELDS + // ==================== + + 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 getParams(context) + { + return (context) ? $.extend({}, this.cachedParams, { ctx: context }) : + this.cachedParams; + } + + function getRequest() + { + var request = { + current: this.current, + rowCount: this.rowCount, + sort: this.sort, + 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 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 highlightAppendedRows(rows) + { + if (this.options.highlightRows) + { + // todo: implement + } + } + + 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 + }; + that.columns.push(column); + if (column.order != null) + { + that.sort[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, + request = getRequest.call(this), + url = getUrl.call(this); + + if (this.options.ajax && (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."); + } + + 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; + that.total = total; + that.totalPages = Math.ceil(total / that.rowCount); + + 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) + { + // aborts the previous ajax request if not already finished or failed + if (that.xqr) + { + that.xqr.abort(); + } + + that.xqr = $.post(url, request, 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); + }).fail(function (jqXHR, textStatus, errorThrown) + { + that.xqr = null; + + if (textStatus !== "abort") + { + renderNoResultsRow.call(that); // overrides loading mask + that.element._bgBusyAria(false).trigger("loaded" + namespace); + } + }); + } + 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); + }); + + this.total = this.rows.length; + this.totalPages = (this.rowCount === -1) ? 1 : + Math.ceil(this.total / this.rowCount); + + sortRows.call(this); + } + } + + 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 renderActions() + { + if (this.options.navigation !== 0) + { + var css = this.options.css, + selector = getCssSelector(css.actions), + headerActions = this.header.find(selector), + footerActions = this.footer.find(selector); + + if ((headerActions.length + footerActions.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, headerActions, actions, 1); + replacePlaceHolder.call(this, footerActions, actions, 2); + } + } + } + + 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) + { + 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(); + + 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); + } + }); + dropDown.find(getCssSelector(css.dropDownMenuItems)).append(item); + }); + actions.append(dropDown); + } + } + + function renderInfos() + { + if (this.options.navigation !== 0) + { + var selector = getCssSelector(this.options.css.infos), + headerInfos = this.header.find(selector), + footerInfos = this.footer.find(selector); + + if ((headerInfos.length + footerInfos.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, headerInfos, infos, 1); + replacePlaceHolder.call(this, footerInfos, infos, 2); + } + } + } + + 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 renderPagination() + { + if (this.options.navigation !== 0) + { + var selector = getCssSelector(this.options.css.pagination), + headerPagination = this.header.find(selector)._bgShowAria(this.rowCount !== -1), + footerPagination = this.footer.find(selector)._bgShowAria(this.rowCount !== -1); + + if (this.rowCount !== -1 && (headerPagination.length + footerPagination.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, headerPagination, pagination, 1); + replacePlaceHolder.call(this, footerPagination, pagination, 2); + } + } + } + + function renderPaginationItem(list, uri, text, markerCss) + { + var that = this, + tpl = this.options.templates, + css = this.options.css, + values = getParams.call(this, { css: markerCss, text: text, uri: "#" + uri }), + item = $(tpl.paginationItem.resolve(values)) + .on("click" + namespace, getCssSelector(css.paginationButton), function (e) + { + e.stopPropagation(); + + 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.attr("href").substr(1); + that.current = commandList[command] || +command; // + converts string to int + loadData.call(that); + } + $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: 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), uri: "#" + value }))) + ._bgSelectAria(value === that.rowCount) + .on("click" + namespace, menuItemSelector, function (e) + { + e.preventDefault(); + + var $this = $(this), + newRowCount = +$this.attr("href").substr(1); + 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).attr("href").substr(1); + $item._bgSelectAria(currentRowCount === newRowCount); + }); + $this.parents(menuSelector).find(menuTextSelector).text(getText(newRowCount)); + loadData.call(that); + } + }); + dropDown.find(menuItemsSelector).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 = "", + cells = "", + rowAttr = "", + rowCss = ""; + + $.each(rows, function (index, row) + { + 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\""; + } + } + + $.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 })); + } + }); + + 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 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]); + }); + } + + function renderSearchField() + { + if (this.options.navigation !== 0) + { + var css = this.options.css, + selector = getCssSelector(css.search), + headerSearch = this.header.find(selector), + footerSearch = this.footer.find(selector); + + if ((headerSearch.length + footerSearch.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) + { + currentValue = newValue; + window.clearTimeout(timer); + timer = window.setTimeout(function () + { + that.search(newValue); + }, 250); + } + }); + + replacePlaceHolder.call(this, headerSearch, search, 1); + replacePlaceHolder.call(this, footerSearch, search, 2); + } + } + } + + 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.sort[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 })); + } + }); + + headerRow.html(html); + + // todo: create a own function for that piece of code + if (sorting) + { + var sortingSelector = getCssSelector(css.sortable), + iconSelector = getCssSelector(css.icon); + headerRow.off("click" + namespace, sortingSelector) + .on("click" + namespace, sortingSelector, function (e) + { + e.preventDefault(); + var $this = $(this), + columnId = $this.data("column-id") || $this.parents("th").first().data("column-id"), + sortOrder = that.sort[columnId], + icon = $this.find(iconSelector); + + if (!that.options.multiSort) + { + $this.parents("tr").first().find(iconSelector).removeClass(css.iconDown + " " + css.iconUp); + that.sort = {}; + } + + if (sortOrder && sortOrder === "asc") + { + that.sort[columnId] = "desc"; + icon.removeClass(css.iconUp).addClass(css.iconDown); + } + else if (sortOrder && sortOrder === "desc") + { + if (that.options.multiSort) + { + var newSort = {}; + for (var key in that.sort) + { + if (key !== columnId) + { + newSort[key] = that.sort[key]; + } + } + that.sort = newSort; + icon.removeClass(css.iconDown); + } + else + { + that.sort[columnId] = "asc"; + icon.removeClass(css.iconDown).addClass(css.iconUp); + } + } + else + { + that.sort[columnId] = "asc"; + icon.addClass(css.iconUp); + } + + 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 replacePlaceHolder(placeholder, element, flag) + { + if (this.options.navigation & flag) + { + placeholder.each(function (index, item) + { + // todo: check how append is implemented. Perhaps cloning here is superfluous. + $(item).before(element.clone(true)).remove(); + }); + } + } + + function showLoading() + { + var tpl = this.options.templates, + thead = this.element.children("thead").first(), + tbody = this.element.children("tbody").first(), + firstCell = tbody.find("tr > td").first(), + padding = (this.element.height() - thead.height()) - (firstCell.height() + 20), + count = this.columns.where(isVisible).length; + + if (this.selection) + { + count = count + 1; + } + tbody.html(tpl.loading.resolve(getParams.call(this, { columns: count }))); + if (this.rowCount !== -1 && padding > 0) + { + tbody.find("tr > td").css("padding", "20px 0 " + padding + "px"); + } + } + + 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.sort) + { + if (this.options.multiSort || sortArray.length === 0) + { + sortArray.push({ + id: key, + order: this.sort[key] + }); + } + } + + if (sortArray.length > 0) + { + this.rows.sort(sort); + } + } + } + + // GRID PUBLIC CLASS DEFINITION + // ==================== + + /** + * Represents the jQuery Bootgrid plugin. + * + * @class Grid + * @constructor + * @param element {Object} The corresponding DOM element. + * @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.sort = {}; + 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 + }; + + /** + * An object that represents the default settings. + * There are two ways to override the sub-properties. + * Either by doing it generally (global) or on initialization. + * + * @static + * @class defaults + * @for Grid + * @example + * // Global approach + * $.bootgrid.defaults.selection = true; + * @example + * // Initialization approach + * $("#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, + ajax: false, // todo: find a better name for this property to differentiate between client-side and server-side data + + /** + * 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 + }, + 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", + 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" + }, + + /** + * 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" + }, + + /** + * 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: "" + } + }; + + /** + * Appends rows. + * + * @method append + * @param rows {Array} An array of rows to append + * @chainable + **/ + Grid.prototype.append = function(rows) + { + if (this.options.ajax) + { + // todo: implement ajax DELETE + } + 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]); + } + + return this; + }; + + /** + * Removes all 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; + }; + + /** + * Removes the control functionality completely and transforms the current state to the initial HTML structure. + * + * @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; + }; + + /** + * Resets the state and reloads rows. + * + * @method reload + * @chainable + **/ + Grid.prototype.reload = function() + { + this.current = 1; // reset + loadData.call(this); + + return this; + }; + + /** + * Removes rows by ids. Removes selected rows if no ids are provided. + * + * @method remove + * @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]); + } + } + + return this; + }; + + /** + * Searches in all rows for a specific phrase (but only in visible cells). + * + * @method search + * @param phrase {String} The phrase to search for + * @chainable + **/ + Grid.prototype.search = function(phrase) + { + if (this.searchPhrase !== phrase) + { + this.current = 1; + this.searchPhrase = phrase; + loadData.call(this); + } + + return this; + }; + + /** + * Selects rows by ids. Selects all visible rows if no ids are provided. + * In server-side scenarios only visible rows are selectable. + * + * @method select + * @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]); + } + } + + return this; + }; + + /** + * Deselects rows by ids. Deselects all visible rows if no ids are provided. + * In server-side scenarios only visible rows are deselectable. + * + * @method deselect + * @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]); + } + } + + return this; + }; + + + /** + * Sorts rows. + * + * @method sort + * @param dictionary {Object} A dictionary which contains the sort information + * @chainable + **/ + Grid.prototype.sort = function(dictionary) + { + var values = (dictionary) ? $.extend({}, dictionary) : {}; + if (values === this.sort) + { + return this; + } + + this.sort = values; + + renderTableHeader.call(this); + sortRows.call(this); + loadData.call(this); + + return this; + }; + + // GRID COMMON TYPE EXTENSIONS + // ============ + + $.fn.extend({ + _bgAria: function (name, value) + { + return this.attr("aria-" + name, value); + }, + + _bgBusyAria: function(busy) + { + return (busy == null || busy) ? + this._bgAria("busy", "true") : + this._bgAria("busy", "false"); + }, + + _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"); + }, + + _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"); + }, + + _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"); + } + }); + + if (!String.prototype.resolve) + { + var formatter = { + "checked": function(value) + { + if (typeof value === "boolean") + { + return (value) ? "checked=\"checked\"" : ""; + } + return value; + } + }; + + String.prototype.resolve = function (substitutes, prefixes) + { + var result = this; + $.each(substitutes, function (key, value) + { + if (value != null && typeof value !== "function") + { + if (typeof value === "object") + { + var keys = (prefixes) ? $.extend([], prefixes) : []; + keys.push(key); + result = result.resolve(value, keys) + ""; + } + else + { + if (formatter && formatter[key] && typeof formatter[key] === "function") + { + value = formatter[key](value); + } + key = (prefixes) ? prefixes.join(".") + "." + key : key; + var pattern = new RegExp("\\{\\{" + key + "\\}\\}", "gm"); + result = result.replace(pattern, (value.replace) ? value.replace(/\$/gi, "$") : value); + } + } + }); + return result; + }; + } + + if (!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.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.propValues) + { + Array.prototype.propValues = function (propName) + { + var result = []; + for (var i = 0; i < this.length; i++) + { + result.push(this[i][propName]); + } + return result; + }; + } + + // GRID PLUGIN DEFINITION + // ===================== + + var old = $.fn.bootgrid; + + $.fn.bootgrid = function (option) + { + var args = Array.prototype.slice.call(arguments, 1); + return this.each(function () + { + 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") + { + return instance[option].apply(instance, args); + } + }); + }; + + $.fn.bootgrid.Constructor = Grid; + + // GRID NO CONFLICT + // =============== + + $.fn.bootgrid.noConflict = function () + { + $.fn.bootgrid = old; + return this; + }; + + // GRID DATA-API + // ============ + +$("[data-toggle=\"bootgrid\"]").bootgrid(); +})(jQuery, window); \ No newline at end of file diff --git a/yeti/preloaded_modules/webui-resources/bootgrid/jquery.bootgrid.min.css b/yeti/preloaded_modules/webui-resources/bootgrid/jquery.bootgrid.min.css new file mode 100644 index 0000000..215f920 --- /dev/null +++ b/yeti/preloaded_modules/webui-resources/bootgrid/jquery.bootgrid.min.css @@ -0,0 +1,5 @@ +/*! + * jQuery Bootgrid v1.1.4 - 11/23/2014 + * Copyright (c) 2014 Rafael Staib (http://www.jquery-bootgrid.com) + * Licensed under MIT http://www.opensource.org/licenses/MIT + */.bootgrid-footer,.bootgrid-header{margin:15px 0}.bootgrid-footer a,.bootgrid-header a{outline:0}.bootgrid-footer .search,.bootgrid-header .search{display:inline-block;margin:0 20px 0 0;vertical-align:middle;width:180px}.bootgrid-footer .search .glyphicon,.bootgrid-header .search .glyphicon{top:0}.bootgrid-footer .search .search-field::-ms-clear,.bootgrid-footer .search.search-field::-ms-clear,.bootgrid-header .search .search-field::-ms-clear,.bootgrid-header .search.search-field::-ms-clear{display:none}.bootgrid-footer .pagination,.bootgrid-header .pagination{margin:0!important}.bootgrid-footer .infoBar,.bootgrid-header .actionBar{text-align:right}.bootgrid-footer .infoBar .btn-group>.btn-group .dropdown-menu,.bootgrid-header .actionBar .btn-group>.btn-group .dropdown-menu{text-align:left}.bootgrid-footer .infoBar .btn-group>.btn-group .dropdown-menu .dropdown-item,.bootgrid-header .actionBar .btn-group>.btn-group .dropdown-menu .dropdown-item{cursor:pointer;display:block;margin:0;padding:3px 20px;white-space:nowrap}.bootgrid-footer .infoBar .btn-group>.btn-group .dropdown-menu .dropdown-item:focus,.bootgrid-footer .infoBar .btn-group>.btn-group .dropdown-menu .dropdown-item:hover,.bootgrid-header .actionBar .btn-group>.btn-group .dropdown-menu .dropdown-item:focus,.bootgrid-header .actionBar .btn-group>.btn-group .dropdown-menu .dropdown-item:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.bootgrid-footer .infoBar .btn-group>.btn-group .dropdown-menu .dropdown-item .dropdown-item-checkbox,.bootgrid-footer .infoBar .btn-group>.btn-group .dropdown-menu .dropdown-item.dropdown-item-checkbox,.bootgrid-header .actionBar .btn-group>.btn-group .dropdown-menu .dropdown-item .dropdown-item-checkbox,.bootgrid-header .actionBar .btn-group>.btn-group .dropdown-menu .dropdown-item.dropdown-item-checkbox{margin:0 2px 4px 0;vertical-align:middle}.bootgrid-footer .infoBar .btn-group>.btn-group .dropdown-menu .dropdown-item.disabled,.bootgrid-header .actionBar .btn-group>.btn-group .dropdown-menu .dropdown-item.disabled{cursor:not-allowed}.bootgrid-table{table-layout:fixed}.bootgrid-table a{outline:0}.bootgrid-table th>.column-header-anchor{color:#333;cursor:not-allowed;display:block;position:relative;text-decoration:none}.bootgrid-table th>.column-header-anchor.sortable{cursor:pointer}.bootgrid-table th>.column-header-anchor>.text{display:block;margin:0 16px 0 0;overflow:hidden;-ms-text-overflow:ellipsis;-o-text-overflow:ellipsis;text-overflow:ellipsis;white-space:nowrap}.bootgrid-table th>.column-header-anchor>.icon{display:block;position:absolute;right:0;top:2px}.bootgrid-table th:active,.bootgrid-table th:hover{background:#fafafa}.bootgrid-table td{overflow:hidden;-ms-text-overflow:ellipsis;-o-text-overflow:ellipsis;text-overflow:ellipsis;white-space:nowrap}.bootgrid-table td.loading,.bootgrid-table td.no-results{background:#fff;text-align:center}.bootgrid-table td.select-cell,.bootgrid-table th.select-cell{text-align:center;width:30px}.bootgrid-table td.select-cell .select-box,.bootgrid-table th.select-cell .select-box{margin:0;outline:0}.table-responsive .bootgrid-table{table-layout:inherit!important}.table-responsive .bootgrid-table td,.table-responsive .bootgrid-table th>.column-header-anchor>.text{overflow:inherit!important;-ms-text-overflow:inherit!important;-o-text-overflow:inherit!important;text-overflow:inherit!important;white-space:inherit!important} \ No newline at end of file diff --git a/yeti/preloaded_modules/webui-resources/bootgrid/jquery.bootgrid.min.js b/yeti/preloaded_modules/webui-resources/bootgrid/jquery.bootgrid.min.js new file mode 100644 index 0000000..985f194 --- /dev/null +++ b/yeti/preloaded_modules/webui-resources/bootgrid/jquery.bootgrid.min.js @@ -0,0 +1,6 @@ +/*! + * jQuery Bootgrid v1.1.4 - 11/23/2014 + * Copyright (c) 2014 Rafael Staib (http://www.jquery-bootgrid.com) + * Licensed under MIT http://www.opensource.org/licenses/MIT + */ +!function(a,b){"use strict";function c(a){function b(b){return c.identifier&&b[c.identifier]===a[c.identifier]}var c=this;return this.rows.contains(b)?!1:(this.rows.push(a),!0)}function d(b){return b?a.extend({},this.cachedParams,{ctx:b}):this.cachedParams}function e(){var b={current:this.current,rowCount:this.rowCount,sort:this.sort,searchPhrase:this.searchPhrase},c=this.options.post;return c=a.isFunction(c)?c():c,this.options.requestHandler(a.extend(!0,b,c))}function f(b){return"."+a.trim(b).replace(/\s+/gm,".")}function g(){var b=this.options.url;return a.isFunction(b)?b():b}function h(){this.element.trigger("initialize"+C),k.call(this),this.selection=this.options.selection&&null!=this.identifier,m.call(this),n.call(this),y.call(this),x.call(this),o.call(this),l.call(this),this.element.trigger("initialized"+C)}function i(){this.options.highlightRows}function j(a){return a.visible}function k(){var b=this,c=this.element.find("thead > tr").first(),d=!1;c.children().each(function(){var c=a(this),e=c.data(),f={id:e.columnId,identifier:null==b.identifier&&e.identifier||!1,converter:b.options.converters[e.converter||e.type]||b.options.converters.string,text:c.text(),align:e.align||"left",headerAlign:e.headerAlign||"left",cssClass:e.cssClass||"",headerCssClass:e.headerCssClass||"",formatter:b.options.formatters[e.formatter]||null,order:d||"asc"!==e.order&&"desc"!==e.order?null:e.order,searchable:!(e.searchable===!1),sortable:!(e.sortable===!1),visible:!(e.visible===!1)};b.columns.push(f),null!=f.order&&(b.sort[f.id]=f.order),f.identifier&&(b.identifier=f.id,b.converter=f.converter),b.options.multiSort||null===f.order||(d=!0)})}function l(){function c(a){for(var b,c=new RegExp(f.searchPhrase,f.options.caseSensitive?"g":"gi"),d=0;d-1)return!0;return!1}function d(a,b){f.currentRows=a,f.total=b,f.totalPages=Math.ceil(b/f.rowCount),f.options.keepSelection||(f.selectedRows=[]),v.call(f,a),q.call(f),s.call(f),f.element._bgBusyAria(!1).trigger("loaded"+C)}var f=this,h=e.call(this),i=g.call(this);if(this.options.ajax&&(null==i||"string"!=typeof i||0===i.length))throw new Error("Url setting must be a none empty string or a function that returns one.");if(this.element._bgBusyAria(!0).trigger("load"+C),A.call(this),this.options.ajax)f.xqr&&f.xqr.abort(),f.xqr=a.post(i,h,function(b){f.xqr=null,"string"==typeof b&&(b=a.parseJSON(b)),b=f.options.responseHandler(b),f.current=b.current,d(b.rows,b.total)}).fail(function(a,b){f.xqr=null,"abort"!==b&&(r.call(f),f.element._bgBusyAria(!1).trigger("loaded"+C))});else{var j=this.searchPhrase.length>0?this.rows.where(c):this.rows,k=j.length;-1!==this.rowCount&&(j=j.page(this.current,this.rowCount)),b.setTimeout(function(){d(j,k)},10)}}function m(){if(!this.options.ajax){var b=this,d=this.element.find("tbody > tr");d.each(function(){var d=a(this),e=d.children("td"),f={};a.each(b.columns,function(a,b){f[b.id]=b.converter.from(e.eq(a).text())}),c.call(b,f)}),this.total=this.rows.length,this.totalPages=-1===this.rowCount?1:Math.ceil(this.total/this.rowCount),B.call(this)}}function n(){var b=this.options.templates,c=this.element.parent().hasClass(this.options.css.responsiveTable)?this.element.parent():this.element;this.element.addClass(this.options.css.table),0===this.element.children("tbody").length&&this.element.append(b.body),1&this.options.navigation&&(this.header=a(b.header.resolve(d.call(this,{id:this.element._bgId()+"-header"}))),c.before(this.header)),2&this.options.navigation&&(this.footer=a(b.footer.resolve(d.call(this,{id:this.element._bgId()+"-footer"}))),c.after(this.footer))}function o(){if(0!==this.options.navigation){var b=this.options.css,c=f(b.actions),e=this.header.find(c),g=this.footer.find(c);if(e.length+g.length>0){var h=this,i=this.options.templates,j=a(i.actions.resolve(d.call(this)));if(this.options.ajax){var k=i.icon.resolve(d.call(this,{iconCss:b.iconRefresh})),m=a(i.actionButton.resolve(d.call(this,{content:k,text:this.options.labels.refresh}))).on("click"+C,function(a){a.stopPropagation(),h.current=1,l.call(h)});j.append(m)}u.call(this,j),p.call(this,j),z.call(this,e,j,1),z.call(this,g,j,2)}}}function p(b){if(this.options.columnSelection&&this.columns.length>1){var c=this,e=this.options.css,g=this.options.templates,h=g.icon.resolve(d.call(this,{iconCss:e.iconColumns})),i=a(g.actionDropDown.resolve(d.call(this,{content:h}))),k=f(e.dropDownItem),m=f(e.dropDownItemCheckbox),n=f(e.dropDownMenuItems);a.each(this.columns,function(b,h){var o=a(g.actionDropDownCheckboxItem.resolve(d.call(c,{name:h.id,label:h.text,checked:h.visible}))).on("click"+C,k,function(b){b.stopPropagation();var d=a(this),e=d.find(m);if(!e.prop("disabled")){h.visible=e.prop("checked");var f=c.columns.where(j).length>1;d.parents(n).find(k+":has("+m+":checked)")._bgEnableAria(f).find(m)._bgEnableField(f),c.element.find("tbody").empty(),y.call(c),l.call(c)}});i.find(f(e.dropDownMenuItems)).append(o)}),b.append(i)}}function q(){if(0!==this.options.navigation){var b=f(this.options.css.infos),c=this.header.find(b),e=this.footer.find(b);if(c.length+e.length>0){var g=this.current*this.rowCount,h=a(this.options.templates.infos.resolve(d.call(this,{end:0===this.total||-1===g||g>this.total?this.total:g,start:0===this.total?0:g-this.rowCount+1,total:this.total})));z.call(this,c,h,1),z.call(this,e,h,2)}}}function r(){var a=this.element.children("tbody").first(),b=this.options.templates,c=this.columns.where(j).length;this.selection&&(c+=1),a.html(b.noResults.resolve(d.call(this,{columns:c})))}function s(){if(0!==this.options.navigation){var b=f(this.options.css.pagination),c=this.header.find(b)._bgShowAria(-1!==this.rowCount),e=this.footer.find(b)._bgShowAria(-1!==this.rowCount);if(-1!==this.rowCount&&c.length+e.length>0){var g=this.options.templates,h=this.current,i=this.totalPages,j=a(g.pagination.resolve(d.call(this))),k=i-h,l=-1*(this.options.padding-h),m=k>=this.options.padding?Math.max(l,1):Math.max(l-this.options.padding+k,1),n=2*this.options.padding+1,o=i>=n?n:i;t.call(this,j,"first","«","first")._bgEnableAria(h>1),t.call(this,j,"prev","<","prev")._bgEnableAria(h>1);for(var p=0;o>p;p++){var q=p+m;t.call(this,j,q,q,"page-"+q)._bgEnableAria()._bgSelectAria(q===h)}0===o&&t.call(this,j,1,1,"page-1")._bgEnableAria(!1)._bgSelectAria(),t.call(this,j,"next",">","next")._bgEnableAria(i>h),t.call(this,j,"last","»","last")._bgEnableAria(i>h),z.call(this,c,j,1),z.call(this,e,j,2)}}}function t(b,c,e,g){var h=this,i=this.options.templates,j=this.options.css,k=d.call(this,{css:g,text:e,uri:"#"+c}),m=a(i.paginationItem.resolve(k)).on("click"+C,f(j.paginationButton),function(b){b.stopPropagation();var c=a(this),d=c.parent();if(!d.hasClass("active")&&!d.hasClass("disabled")){var e={first:1,prev:h.current-1,next:h.current+1,last:h.totalPages},f=c.attr("href").substr(1);h.current=e[f]||+f,l.call(h)}c.trigger("blur")});return b.append(m),m}function u(b){function c(a){return-1===a?e.options.labels.all:a}var e=this,g=this.options.rowCount;if(a.isArray(g)){var h=this.options.css,i=this.options.templates,j=a(i.actionDropDown.resolve(d.call(this,{content:this.rowCount}))),k=f(h.dropDownMenu),m=f(h.dropDownMenuText),n=f(h.dropDownMenuItems),o=f(h.dropDownItemButton);a.each(g,function(b,f){var g=a(i.actionDropDownItem.resolve(d.call(e,{text:c(f),uri:"#"+f})))._bgSelectAria(f===e.rowCount).on("click"+C,o,function(b){b.preventDefault();var d=a(this),f=+d.attr("href").substr(1);f!==e.rowCount&&(e.current=1,e.rowCount=f,d.parents(n).children().each(function(){var b=a(this),c=+b.find(o).attr("href").substr(1);b._bgSelectAria(c===f)}),d.parents(k).find(m).text(c(f)),l.call(e))});j.find(n).append(g)}),b.append(j)}}function v(b){if(b.length>0){var c=this,e=this.options.css,g=this.options.templates,h=this.element.children("tbody").first(),i=!0,j="",k="",l="",m="";a.each(b,function(b,f){if(k="",l=' data-row-id="'+(null==c.identifier?b:f[c.identifier])+'"',m="",c.selection){var h=-1!==a.inArray(f[c.identifier],c.selectedRows),n=g.select.resolve(d.call(c,{type:"checkbox",value:f[c.identifier],checked:h}));k+=g.cell.resolve(d.call(c,{content:n,css:e.selectCell})),i=i&&h,h&&(m+=e.selected,l+=' aria-selected="true"')}a.each(c.columns,function(b,h){if(h.visible){var i=a.isFunction(h.formatter)?h.formatter.call(c,h,f):h.converter.to(f[h.id]),j=h.cssClass.length>0?" "+h.cssClass:"";k+=g.cell.resolve(d.call(c,{content:null==i||""===i?" ":i,css:("right"===h.align?e.right:"center"===h.align?e.center:e.left)+j}))}}),m.length>0&&(l+=' class="'+m+'"'),j+=g.row.resolve(d.call(c,{attr:l,cells:k}))}),c.element.find("thead "+f(c.options.css.selectBox)).prop("checked",i),h.html(j),w.call(this,h)}else r.call(this)}function w(b){var c=this,d=f(this.options.css.selectBox);this.selection&&b.off("click"+C,d).on("click"+C,d,function(b){b.stopPropagation();var d=a(this),e=c.converter.from(d.val());d.prop("checked")?c.select([e]):c.deselect([e])}),b.off("click"+C,"> tr").on("click"+C,"> tr",function(b){b.stopPropagation();var d=a(this),e=null==c.identifier?d.data("row-id"):c.converter.from(d.data("row-id")+""),f=null==c.identifier?c.currentRows[e]:c.currentRows.first(function(a){return a[c.identifier]===e});c.selection&&c.options.rowSelect&&(d.hasClass(c.options.css.selected)?c.deselect([e]):c.select([e])),c.element.trigger("click"+C,[c.columns,f])})}function x(){if(0!==this.options.navigation){var c=this.options.css,e=f(c.search),g=this.header.find(e),h=this.footer.find(e);if(g.length+h.length>0){var i=this,j=this.options.templates,k=null,l="",m=f(c.searchField),n=a(j.search.resolve(d.call(this))),o=n.is(m)?n:n.find(m);o.on("keyup"+C,function(c){c.stopPropagation();var d=a(this).val();l!==d&&(l=d,b.clearTimeout(k),k=b.setTimeout(function(){i.search(d)},250))}),z.call(this,g,n,1),z.call(this,h,n,2)}}}function y(){var b=this,c=this.element.find("thead > tr"),e=this.options.css,g=this.options.templates,h="",i=this.options.sorting;if(this.selection){var j=this.options.multiSelect?g.select.resolve(d.call(b,{type:"checkbox",value:"all"})):"";h+=g.rawHeaderCell.resolve(d.call(b,{content:j,css:e.selectCell}))}if(a.each(this.columns,function(a,c){if(c.visible){var f=b.sort[c.id],j=i&&f&&"asc"===f?e.iconUp:i&&f&&"desc"===f?e.iconDown:"",k=g.icon.resolve(d.call(b,{iconCss:j})),l=c.headerAlign,m=c.headerCssClass.length>0?" "+c.headerCssClass:"";h+=g.headerCell.resolve(d.call(b,{column:c,icon:k,sortable:i&&c.sortable&&e.sortable||"",css:("right"===l?e.right:"center"===l?e.center:e.left)+m}))}}),c.html(h),i){var k=f(e.sortable),m=f(e.icon);c.off("click"+C,k).on("click"+C,k,function(c){c.preventDefault();var d=a(this),f=d.data("column-id")||d.parents("th").first().data("column-id"),g=b.sort[f],h=d.find(m);if(b.options.multiSort||(d.parents("tr").first().find(m).removeClass(e.iconDown+" "+e.iconUp),b.sort={}),g&&"asc"===g)b.sort[f]="desc",h.removeClass(e.iconUp).addClass(e.iconDown);else if(g&&"desc"===g)if(b.options.multiSort){var i={};for(var j in b.sort)j!==f&&(i[j]=b.sort[j]);b.sort=i,h.removeClass(e.iconDown)}else b.sort[f]="asc",h.removeClass(e.iconDown).addClass(e.iconUp);else b.sort[f]="asc",h.addClass(e.iconUp);B.call(b),l.call(b)})}if(this.selection&&this.options.multiSelect){var n=f(e.selectBox);c.off("click"+C,n).on("click"+C,n,function(c){c.stopPropagation(),a(this).prop("checked")?b.select():b.deselect()})}}function z(b,c,d){this.options.navigation&d&&b.each(function(b,d){a(d).before(c.clone(!0)).remove()})}function A(){var a=this.options.templates,b=this.element.children("thead").first(),c=this.element.children("tbody").first(),e=c.find("tr > td").first(),f=this.element.height()-b.height()-(e.height()+20),g=this.columns.where(j).length;this.selection&&(g+=1),c.html(a.loading.resolve(d.call(this,{columns:g}))),-1!==this.rowCount&&f>0&&c.find("tr > td").css("padding","20px 0 "+f+"px")}function B(){function a(c,d,e){function f(a){return"asc"===h.order?a:-1*a}e=e||0;var g=e+1,h=b[e];return c[h.id]>d[h.id]?f(1):c[h.id]g?a(c,d,g):0}var b=[];if(!this.options.ajax){for(var c in this.sort)(this.options.multiSort||0===b.length)&&b.push({id:c,order:this.sort[c]});b.length>0&&this.rows.sort(a)}}var C=".rs.jquery.bootgrid",D=function(b,c){this.element=a(b),this.origin=this.element.clone(),this.options=a.extend(!0,{},D.defaults,this.element.data(),c);var d=this.options.rowCount=this.element.data().rowCount||c.rowCount||this.options.rowCount;this.columns=[],this.current=1,this.currentRows=[],this.identifier=null,this.selection=!1,this.converter=null,this.rowCount=a.isArray(d)?d[0]:d,this.rows=[],this.searchPhrase="",this.selectedRows=[],this.sort={},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};if(D.defaults={navigation:3,padding:2,columnSelection:!0,rowCount:[10,25,50,-1],selection:!1,multiSelect:!1,rowSelect:!1,keepSelection:!1,highlightRows:!1,sorting:!0,multiSort:!1,ajax:!1,post:{},url:"",caseSensitive:!0,requestHandler:function(a){return a},responseHandler:function(a){return a},converters:{numeric:{from:function(a){return+a},to:function(a){return a+""}},string:{from:function(a){return a},to:function(a){return a}}},css:{actions:"actions btn-group",center:"text-center",columnHeaderAnchor:"column-header-anchor",columnHeaderText:"text",dropDownItem:"dropdown-item",dropDownItemButton:"dropdown-item-button",dropDownItemCheckbox:"dropdown-item-checkbox",dropDownMenu:"dropdown btn-group",dropDownMenuItems:"dropdown-menu pull-right",dropDownMenuText:"dropdown-text",footer:"bootgrid-footer container-fluid",header:"bootgrid-header container-fluid",icon:"icon glyphicon",iconColumns:"glyphicon-th-list",iconDown:"glyphicon-chevron-down",iconRefresh:"glyphicon-refresh",iconUp:"glyphicon-chevron-up",infos:"infos",left:"text-left",pagination:"pagination",paginationButton:"button",responsiveTable:"table-responsive",right:"text-right",search:"search form-group",searchField:"search-field form-control",selectBox:"select-box",selectCell:"select-cell",selected:"active",sortable:"sortable",table:"bootgrid-table table"},formatters:{},labels:{all:"All",infos:"Showing {{ctx.start}} to {{ctx.end}} of {{ctx.total}} entries",loading:"Loading...",noResults:"No results found!",refresh:"Refresh",search:"Search"},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}}',row:"{{ctx.cells}}",search:'
      ',select:''}},D.prototype.append=function(a){if(this.options.ajax);else{for(var b=[],d=0;d0&&(this.options.multiSelect||1!==e.length);)if(c=b.pop(),-1===a.inArray(c,this.selectedRows))for(d=0;d0){var g=f(this.options.css.selectBox),h=this.selectedRows.length>=this.currentRows.length;for(d=0;!this.options.keepSelection&&h&&d tr "+g+":checked").trigger("click"+C),d=0;d tr[data-row-id="'+this.selectedRows[d]+'"]').addClass(this.options.css.selected)._bgAria("selected","true").find(g).prop("checked",!0);this.element.trigger("selected"+C,[e])}}return this},D.prototype.deselect=function(b){if(this.selection){b=b||this.currentRows.propValues(this.identifier);for(var c,d,e,g=[];b.length>0;)if(c=b.pop(),e=a.inArray(c,this.selectedRows),-1!==e)for(d=0;d0){var h=f(this.options.css.selectBox);for(this.element.find("thead "+h).prop("checked",!1),d=0;d tr[data-row-id="'+g[d][this.identifier]+'"]').removeClass(this.options.css.selected)._bgAria("selected","false").find(h).prop("checked",!1);this.element.trigger("deselected"+C,[g])}}return this},D.prototype.sort=function(b){var c=b?a.extend({},b):{};return c===this.sort?this:(this.sort=c,y.call(this),B.call(this),l.call(this),this)},a.fn.extend({_bgAria:function(a,b){return this.attr("aria-"+a,b)},_bgBusyAria:function(a){return null==a||a?this._bgAria("busy","true"):this._bgAria("busy","false")},_bgRemoveAria:function(a){return this.removeAttr("aria-"+a)},_bgEnableAria:function(a){return null==a||a?this.removeClass("disabled")._bgAria("disabled","false"):this.addClass("disabled")._bgAria("disabled","true")},_bgEnableField:function(a){return null==a||a?this.removeAttr("disabled"):this.attr("disabled","disable")},_bgShowAria:function(a){return null==a||a?this.show()._bgAria("hidden","false"):this.hide()._bgAria("hidden","true")},_bgSelectAria:function(a){return null==a||a?this.addClass("active")._bgAria("selected","true"):this.removeClass("active")._bgAria("selected","false")},_bgId:function(a){return a?this.attr("id",a):this.attr("id")}}),!String.prototype.resolve){var E={checked:function(a){return"boolean"==typeof a?a?'checked="checked"':"":a}};String.prototype.resolve=function(b,c){var d=this;return a.each(b,function(b,e){if(null!=e&&"function"!=typeof e)if("object"==typeof e){var f=c?a.extend([],c):[];f.push(b),d=d.resolve(e,f)+""}else{E&&E[b]&&"function"==typeof E[b]&&(e=E[b](e)),b=c?c.join(".")+"."+b:b;var g=new RegExp("\\{\\{"+b+"\\}\\}","gm");d=d.replace(g,e.replace?e.replace(/\$/gi,"$"):e)}}),d}}Array.prototype.first||(Array.prototype.first=function(a){for(var b=0;bc?this.length>d?this.slice(c,d):this.slice(c):[]}),Array.prototype.where||(Array.prototype.where=function(a){for(var b=[],c=0;c^Y$s}bfjJ06;xdb15T^v)mgtOJhTDS0{QY&)6(2}UJk4o)&pn_2 z`Tx(o2YmX})uHbYy=$QXxK~|6w5pfK@6`Gpe4b;wT|V8ng?IaU86S9vi^F}iy0hGW zkH=#V&+@|4-_iHdD4)Im4in=e48C%P_lCdXjckeh2Y)hla+Ha4XSi~Cf^RN<%i{C| zU!I#|c4n6O>pwE@{l>3zODrxeQ2Jwm8w_|dy8V+RPa2`%g!c`CF znvu9}q;43Q1tYg)6mA;D(%-gRSiB9A4?wybvb!MP50kHhcLc|dlduz{>?|4knw(8h zuo;Txt*pK5Yveb|Z82b6wL>4%hlSm{TU zevPO7wASDd#j$H!LSf)!VNz5M1RY)!-VEOh7LS^3=_>^Fx&`GfMuHeeVD;M z&nadBn<-ZO8u@^HP`+9|EWb`(kHuP}SZ4xzE``0E!QRYapA@hQ#fqnfBra*%$g+hz z+nA&akH;}*CzeQKsRLN%AeI}!3TLrm<@v>RN|G{Zy5(P#-!DHTKP>-|{0aGSd99h7 zlAn>Uygwbp*`Ww~6k$LS4lBZGMYyU6o@2}>jNennV#Zj?88-_?saSb`o8<44e?U|EMrXOjH!Y#SFHGue0c{p$T!Ki$+ydQ$agx%GYOrYl<`_d zv2w=Af^oUH;$#1*HM(wdb7Z~dHmf%}PBiL7d_E1_6{>BBHfg-kjRbsd%n1goH`Ug; z5&wo}H{f?;;VSp;P_T+XODyL4{F+d=X?;y_jXxS|jQG~ZV&PDW8>(LC_t!69mM1h+ zH8gDu_{w+H!l6)YdDXzmWs4K6DrXT>z?BxR`{&>CZ%h61?SGBk@uGbE|4;i1kOr|% literal 0 HcmV?d00001 diff --git a/yeti/preloaded_modules/webui-resources/index.html b/yeti/preloaded_modules/webui-resources/index.html index 14eb684..53a39aa 100644 --- a/yeti/preloaded_modules/webui-resources/index.html +++ b/yeti/preloaded_modules/webui-resources/index.html @@ -6,12 +6,15 @@ + Yeti Dashboard + + @@ -30,17 +33,6 @@ Yeti Dashboard - @@ -48,32 +40,23 @@
      -

      Dashboard

      Modules

      -
      - +
      +
      - - - + + + + - - - - - - - +
      SubsystemFilenameStatusSubsystemFilenameStatusCommands
      drivetrainmodules.drivetrainLoaded
      @@ -85,5 +68,7 @@

      Modules

      + + diff --git a/yeti/preloaded_modules/webui-resources/script.js b/yeti/preloaded_modules/webui-resources/script.js new file mode 100644 index 0000000..e2777a9 --- /dev/null +++ b/yeti/preloaded_modules/webui-resources/script.js @@ -0,0 +1,22 @@ +//$("#modtable").bootgrid() + +function onData(data){ + $("#modtable tbody tr").remove() + html = ""; + for(var i = 0; i < data.modules.length; i++){ + html += '' + data.modules[i].subsystem + '' + + data.modules[i].filename + '' + + data.modules[i].filename + '' + + data.modules[i].status + '' + } + $("#modtable tbody").html(html) +} + +function getDataLoop() { + $.getJSON("/json", onData); + //setTimeout(getDataLoop, 1000); +} + +$(document).ready(function(){ + setTimeout(getDataLoop, 1000); +}) \ No newline at end of file diff --git a/yeti/preloaded_modules/webui.py b/yeti/preloaded_modules/webui.py index b7c18ed..fb59a5e 100644 --- a/yeti/preloaded_modules/webui.py +++ b/yeti/preloaded_modules/webui.py @@ -1,6 +1,7 @@ import yeti import asyncio import os +import json from aiohttp import web class WebUI(yeti.Module): @@ -12,7 +13,17 @@ def module_init(self): @asyncio.coroutine def data_handler(self, request): - return web.Response(body="JSON!".encode("utf-8")) + data_structure = dict() + data_structure["modules"] = list() + for modname in self.context.loaded_modules: + mod_object = self.context.loaded_modules[modname] + mod_data = dict() + mod_data["subsystem"] = modname + if hasattr(mod_object, "loader"): + mod_data["filename"] = mod_object.loader.module_path + data_structure["modules"].append(mod_data) + text = json.dumps(data_structure, allow_nan=False) + return web.Response(body=text.encode("utf-8")) @asyncio.coroutine def init_server(self):