diff options
Diffstat (limited to 'build/resources/main/static/plugins/jsgrid/jsgrid.js')
-rw-r--r-- | build/resources/main/static/plugins/jsgrid/jsgrid.js | 2516 |
1 files changed, 2516 insertions, 0 deletions
diff --git a/build/resources/main/static/plugins/jsgrid/jsgrid.js b/build/resources/main/static/plugins/jsgrid/jsgrid.js new file mode 100644 index 0000000..922bcdf --- /dev/null +++ b/build/resources/main/static/plugins/jsgrid/jsgrid.js @@ -0,0 +1,2516 @@ +/* + * jsGrid v1.5.3 (http://js-grid.com) + * (c) 2016 Artem Tabalin + * Licensed under MIT (https://github.com/tabalinas/jsgrid/blob/master/LICENSE) + */ + +(function(window, $, undefined) { + + var JSGRID = "JSGrid", + JSGRID_DATA_KEY = JSGRID, + JSGRID_ROW_DATA_KEY = "JSGridItem", + JSGRID_EDIT_ROW_DATA_KEY = "JSGridEditRow", + + SORT_ORDER_ASC = "asc", + SORT_ORDER_DESC = "desc", + + FIRST_PAGE_PLACEHOLDER = "{first}", + PAGES_PLACEHOLDER = "{pages}", + PREV_PAGE_PLACEHOLDER = "{prev}", + NEXT_PAGE_PLACEHOLDER = "{next}", + LAST_PAGE_PLACEHOLDER = "{last}", + PAGE_INDEX_PLACEHOLDER = "{pageIndex}", + PAGE_COUNT_PLACEHOLDER = "{pageCount}", + ITEM_COUNT_PLACEHOLDER = "{itemCount}", + + EMPTY_HREF = "javascript:void(0);"; + + var getOrApply = function(value, context) { + if($.isFunction(value)) { + return value.apply(context, $.makeArray(arguments).slice(2)); + } + return value; + }; + + var normalizePromise = function(promise) { + var d = $.Deferred(); + + if(promise && promise.then) { + promise.then(function() { + d.resolve.apply(d, arguments); + }, function() { + d.reject.apply(d, arguments); + }); + } else { + d.resolve(promise); + } + + return d.promise(); + }; + + var defaultController = { + loadData: $.noop, + insertItem: $.noop, + updateItem: $.noop, + deleteItem: $.noop + }; + + + function Grid(element, config) { + var $element = $(element); + + $element.data(JSGRID_DATA_KEY, this); + + this._container = $element; + + this.data = []; + this.fields = []; + + this._editingRow = null; + this._sortField = null; + this._sortOrder = SORT_ORDER_ASC; + this._firstDisplayingPage = 1; + + this._init(config); + this.render(); + } + + Grid.prototype = { + width: "auto", + height: "auto", + updateOnResize: true, + + rowClass: $.noop, + rowRenderer: null, + + rowClick: function(args) { + if(this.editing) { + this.editItem($(args.event.target).closest("tr")); + } + }, + rowDoubleClick: $.noop, + + noDataContent: "Not found", + noDataRowClass: "jsgrid-nodata-row", + + heading: true, + headerRowRenderer: null, + headerRowClass: "jsgrid-header-row", + headerCellClass: "jsgrid-header-cell", + + filtering: false, + filterRowRenderer: null, + filterRowClass: "jsgrid-filter-row", + + inserting: false, + insertRowRenderer: null, + insertRowClass: "jsgrid-insert-row", + + editing: false, + editRowRenderer: null, + editRowClass: "jsgrid-edit-row", + + confirmDeleting: true, + deleteConfirm: "Are you sure?", + + selecting: true, + selectedRowClass: "jsgrid-selected-row", + oddRowClass: "jsgrid-row", + evenRowClass: "jsgrid-alt-row", + cellClass: "jsgrid-cell", + + sorting: false, + sortableClass: "jsgrid-header-sortable", + sortAscClass: "jsgrid-header-sort jsgrid-header-sort-asc", + sortDescClass: "jsgrid-header-sort jsgrid-header-sort-desc", + + paging: false, + pagerContainer: null, + pageIndex: 1, + pageSize: 20, + pageButtonCount: 15, + pagerFormat: "Pages: {first} {prev} {pages} {next} {last} {pageIndex} of {pageCount}", + pagePrevText: "Prev", + pageNextText: "Next", + pageFirstText: "First", + pageLastText: "Last", + pageNavigatorNextText: "...", + pageNavigatorPrevText: "...", + pagerContainerClass: "jsgrid-pager-container", + pagerClass: "jsgrid-pager", + pagerNavButtonClass: "jsgrid-pager-nav-button", + pagerNavButtonInactiveClass: "jsgrid-pager-nav-inactive-button", + pageClass: "jsgrid-pager-page", + currentPageClass: "jsgrid-pager-current-page", + + customLoading: false, + pageLoading: false, + + autoload: false, + controller: defaultController, + + loadIndication: true, + loadIndicationDelay: 500, + loadMessage: "Please, wait...", + loadShading: true, + + invalidMessage: "Invalid data entered!", + + invalidNotify: function(args) { + var messages = $.map(args.errors, function(error) { + return error.message || null; + }); + + window.alert([this.invalidMessage].concat(messages).join("\n")); + }, + + onInit: $.noop, + onRefreshing: $.noop, + onRefreshed: $.noop, + onPageChanged: $.noop, + onItemDeleting: $.noop, + onItemDeleted: $.noop, + onItemInserting: $.noop, + onItemInserted: $.noop, + onItemEditing: $.noop, + onItemUpdating: $.noop, + onItemUpdated: $.noop, + onItemInvalid: $.noop, + onDataLoading: $.noop, + onDataLoaded: $.noop, + onOptionChanging: $.noop, + onOptionChanged: $.noop, + onError: $.noop, + + invalidClass: "jsgrid-invalid", + + containerClass: "jsgrid", + tableClass: "jsgrid-table", + gridHeaderClass: "jsgrid-grid-header", + gridBodyClass: "jsgrid-grid-body", + + _init: function(config) { + $.extend(this, config); + this._initLoadStrategy(); + this._initController(); + this._initFields(); + this._attachWindowLoadResize(); + this._attachWindowResizeCallback(); + this._callEventHandler(this.onInit) + }, + + loadStrategy: function() { + return this.pageLoading + ? new jsGrid.loadStrategies.PageLoadingStrategy(this) + : new jsGrid.loadStrategies.DirectLoadingStrategy(this); + }, + + _initLoadStrategy: function() { + this._loadStrategy = getOrApply(this.loadStrategy, this); + }, + + _initController: function() { + this._controller = $.extend({}, defaultController, getOrApply(this.controller, this)); + }, + + renderTemplate: function(source, context, config) { + args = []; + for(var key in config) { + args.push(config[key]); + } + + args.unshift(source, context); + + source = getOrApply.apply(null, args); + return (source === undefined || source === null) ? "" : source; + }, + + loadIndicator: function(config) { + return new jsGrid.LoadIndicator(config); + }, + + validation: function(config) { + return jsGrid.Validation && new jsGrid.Validation(config); + }, + + _initFields: function() { + var self = this; + self.fields = $.map(self.fields, function(field) { + if($.isPlainObject(field)) { + var fieldConstructor = (field.type && jsGrid.fields[field.type]) || jsGrid.Field; + field = new fieldConstructor(field); + } + field._grid = self; + return field; + }); + }, + + _attachWindowLoadResize: function() { + $(window).on("load", $.proxy(this._refreshSize, this)); + }, + + _attachWindowResizeCallback: function() { + if(this.updateOnResize) { + $(window).on("resize", $.proxy(this._refreshSize, this)); + } + }, + + _detachWindowResizeCallback: function() { + $(window).off("resize", this._refreshSize); + }, + + option: function(key, value) { + var optionChangingEventArgs, + optionChangedEventArgs; + + if(arguments.length === 1) + return this[key]; + + optionChangingEventArgs = { + option: key, + oldValue: this[key], + newValue: value + }; + this._callEventHandler(this.onOptionChanging, optionChangingEventArgs); + + this._handleOptionChange(optionChangingEventArgs.option, optionChangingEventArgs.newValue); + + optionChangedEventArgs = { + option: optionChangingEventArgs.option, + value: optionChangingEventArgs.newValue + }; + this._callEventHandler(this.onOptionChanged, optionChangedEventArgs); + }, + + fieldOption: function(field, key, value) { + field = this._normalizeField(field); + + if(arguments.length === 2) + return field[key]; + + field[key] = value; + this._renderGrid(); + }, + + _handleOptionChange: function(name, value) { + this[name] = value; + + switch(name) { + case "width": + case "height": + this._refreshSize(); + break; + case "rowClass": + case "rowRenderer": + case "rowClick": + case "rowDoubleClick": + case "noDataRowClass": + case "noDataContent": + case "selecting": + case "selectedRowClass": + case "oddRowClass": + case "evenRowClass": + this._refreshContent(); + break; + case "pageButtonCount": + case "pagerFormat": + case "pagePrevText": + case "pageNextText": + case "pageFirstText": + case "pageLastText": + case "pageNavigatorNextText": + case "pageNavigatorPrevText": + case "pagerClass": + case "pagerNavButtonClass": + case "pageClass": + case "currentPageClass": + case "pagerRenderer": + this._refreshPager(); + break; + case "fields": + this._initFields(); + this.render(); + break; + case "data": + case "editing": + case "heading": + case "filtering": + case "inserting": + case "paging": + this.refresh(); + break; + case "loadStrategy": + case "pageLoading": + this._initLoadStrategy(); + this.search(); + break; + case "pageIndex": + this.openPage(value); + break; + case "pageSize": + this.refresh(); + this.search(); + break; + case "editRowRenderer": + case "editRowClass": + this.cancelEdit(); + break; + case "updateOnResize": + this._detachWindowResizeCallback(); + this._attachWindowResizeCallback(); + break; + case "invalidNotify": + case "invalidMessage": + break; + default: + this.render(); + break; + } + }, + + destroy: function() { + this._detachWindowResizeCallback(); + this._clear(); + this._container.removeData(JSGRID_DATA_KEY); + }, + + render: function() { + this._renderGrid(); + return this.autoload ? this.loadData() : $.Deferred().resolve().promise(); + }, + + _renderGrid: function() { + this._clear(); + + this._container.addClass(this.containerClass) + .css("position", "relative") + .append(this._createHeader()) + .append(this._createBody()); + + this._pagerContainer = this._createPagerContainer(); + this._loadIndicator = this._createLoadIndicator(); + this._validation = this._createValidation(); + + this.refresh(); + }, + + _createLoadIndicator: function() { + return getOrApply(this.loadIndicator, this, { + message: this.loadMessage, + shading: this.loadShading, + container: this._container + }); + }, + + _createValidation: function() { + return getOrApply(this.validation, this); + }, + + _clear: function() { + this.cancelEdit(); + + clearTimeout(this._loadingTimer); + + this._pagerContainer && this._pagerContainer.empty(); + + this._container.empty() + .css({ position: "", width: "", height: "" }); + }, + + _createHeader: function() { + var $headerRow = this._headerRow = this._createHeaderRow(), + $filterRow = this._filterRow = this._createFilterRow(), + $insertRow = this._insertRow = this._createInsertRow(); + + var $headerGrid = this._headerGrid = $("<table>").addClass(this.tableClass) + .append($headerRow) + .append($filterRow) + .append($insertRow); + + var $header = this._header = $("<div>").addClass(this.gridHeaderClass) + .addClass(this._scrollBarWidth() ? "jsgrid-header-scrollbar" : "") + .append($headerGrid); + + return $header; + }, + + _createBody: function() { + var $content = this._content = $("<tbody>"); + + var $bodyGrid = this._bodyGrid = $("<table>").addClass(this.tableClass) + .append($content); + + var $body = this._body = $("<div>").addClass(this.gridBodyClass) + .append($bodyGrid) + .on("scroll", $.proxy(function(e) { + this._header.scrollLeft(e.target.scrollLeft); + }, this)); + + return $body; + }, + + _createPagerContainer: function() { + var pagerContainer = this.pagerContainer || $("<div>").appendTo(this._container); + return $(pagerContainer).addClass(this.pagerContainerClass); + }, + + _eachField: function(callBack) { + var self = this; + $.each(this.fields, function(index, field) { + if(field.visible) { + callBack.call(self, field, index); + } + }); + }, + + _createHeaderRow: function() { + if($.isFunction(this.headerRowRenderer)) + return $(this.renderTemplate(this.headerRowRenderer, this)); + + var $result = $("<tr>").addClass(this.headerRowClass); + + this._eachField(function(field, index) { + var $th = this._prepareCell("<th>", field, "headercss", this.headerCellClass) + .append(this.renderTemplate(field.headerTemplate, field)) + .appendTo($result); + + if(this.sorting && field.sorting) { + $th.addClass(this.sortableClass) + .on("click", $.proxy(function() { + this.sort(index); + }, this)); + } + }); + + return $result; + }, + + _prepareCell: function(cell, field, cssprop, cellClass) { + return $(cell).css("width", field.width) + .addClass(cellClass || this.cellClass) + .addClass((cssprop && field[cssprop]) || field.css) + .addClass(field.align ? ("jsgrid-align-" + field.align) : ""); + }, + + _createFilterRow: function() { + if($.isFunction(this.filterRowRenderer)) + return $(this.renderTemplate(this.filterRowRenderer, this)); + + var $result = $("<tr>").addClass(this.filterRowClass); + + this._eachField(function(field) { + this._prepareCell("<td>", field, "filtercss") + .append(this.renderTemplate(field.filterTemplate, field)) + .appendTo($result); + }); + + return $result; + }, + + _createInsertRow: function() { + if($.isFunction(this.insertRowRenderer)) + return $(this.renderTemplate(this.insertRowRenderer, this)); + + var $result = $("<tr>").addClass(this.insertRowClass); + + this._eachField(function(field) { + this._prepareCell("<td>", field, "insertcss") + .append(this.renderTemplate(field.insertTemplate, field)) + .appendTo($result); + }); + + return $result; + }, + + _callEventHandler: function(handler, eventParams) { + handler.call(this, $.extend(eventParams, { + grid: this + })); + + return eventParams; + }, + + reset: function() { + this._resetSorting(); + this._resetPager(); + return this._loadStrategy.reset(); + }, + + _resetPager: function() { + this._firstDisplayingPage = 1; + this._setPage(1); + }, + + _resetSorting: function() { + this._sortField = null; + this._sortOrder = SORT_ORDER_ASC; + this._clearSortingCss(); + }, + + refresh: function() { + this._callEventHandler(this.onRefreshing); + + this.cancelEdit(); + + this._refreshHeading(); + this._refreshFiltering(); + this._refreshInserting(); + this._refreshContent(); + this._refreshPager(); + this._refreshSize(); + + this._callEventHandler(this.onRefreshed); + }, + + _refreshHeading: function() { + this._headerRow.toggle(this.heading); + }, + + _refreshFiltering: function() { + this._filterRow.toggle(this.filtering); + }, + + _refreshInserting: function() { + this._insertRow.toggle(this.inserting); + }, + + _refreshContent: function() { + var $content = this._content; + $content.empty(); + + if(!this.data.length) { + $content.append(this._createNoDataRow()); + return this; + } + + var indexFrom = this._loadStrategy.firstDisplayIndex(); + var indexTo = this._loadStrategy.lastDisplayIndex(); + + for(var itemIndex = indexFrom; itemIndex < indexTo; itemIndex++) { + var item = this.data[itemIndex]; + $content.append(this._createRow(item, itemIndex)); + } + }, + + _createNoDataRow: function() { + var amountOfFields = 0; + this._eachField(function() { + amountOfFields++; + }); + + return $("<tr>").addClass(this.noDataRowClass) + .append($("<td>").addClass(this.cellClass).attr("colspan", amountOfFields) + .append(this.renderTemplate(this.noDataContent, this))); + }, + + _createRow: function(item, itemIndex) { + var $result; + + if($.isFunction(this.rowRenderer)) { + $result = this.renderTemplate(this.rowRenderer, this, { item: item, itemIndex: itemIndex }); + } else { + $result = $("<tr>"); + this._renderCells($result, item); + } + + $result.addClass(this._getRowClasses(item, itemIndex)) + .data(JSGRID_ROW_DATA_KEY, item) + .on("click", $.proxy(function(e) { + this.rowClick({ + item: item, + itemIndex: itemIndex, + event: e + }); + }, this)) + .on("dblclick", $.proxy(function(e) { + this.rowDoubleClick({ + item: item, + itemIndex: itemIndex, + event: e + }); + }, this)); + + if(this.selecting) { + this._attachRowHover($result); + } + + return $result; + }, + + _getRowClasses: function(item, itemIndex) { + var classes = []; + classes.push(((itemIndex + 1) % 2) ? this.oddRowClass : this.evenRowClass); + classes.push(getOrApply(this.rowClass, this, item, itemIndex)); + return classes.join(" "); + }, + + _attachRowHover: function($row) { + var selectedRowClass = this.selectedRowClass; + $row.hover(function() { + $(this).addClass(selectedRowClass); + }, + function() { + $(this).removeClass(selectedRowClass); + } + ); + }, + + _renderCells: function($row, item) { + this._eachField(function(field) { + $row.append(this._createCell(item, field)); + }); + return this; + }, + + _createCell: function(item, field) { + var $result; + var fieldValue = this._getItemFieldValue(item, field); + + var args = { value: fieldValue, item : item }; + if($.isFunction(field.cellRenderer)) { + $result = this.renderTemplate(field.cellRenderer, field, args); + } else { + $result = $("<td>").append(this.renderTemplate(field.itemTemplate || fieldValue, field, args)); + } + + return this._prepareCell($result, field); + }, + + _getItemFieldValue: function(item, field) { + var props = field.name.split('.'); + var result = item[props.shift()]; + + while(result && props.length) { + result = result[props.shift()]; + } + + return result; + }, + + _setItemFieldValue: function(item, field, value) { + var props = field.name.split('.'); + var current = item; + var prop = props[0]; + + while(current && props.length) { + item = current; + prop = props.shift(); + current = item[prop]; + } + + if(!current) { + while(props.length) { + item = item[prop] = {}; + prop = props.shift(); + } + } + + item[prop] = value; + }, + + sort: function(field, order) { + if($.isPlainObject(field)) { + order = field.order; + field = field.field; + } + + this._clearSortingCss(); + this._setSortingParams(field, order); + this._setSortingCss(); + return this._loadStrategy.sort(); + }, + + _clearSortingCss: function() { + this._headerRow.find("th") + .removeClass(this.sortAscClass) + .removeClass(this.sortDescClass); + }, + + _setSortingParams: function(field, order) { + field = this._normalizeField(field); + order = order || ((this._sortField === field) ? this._reversedSortOrder(this._sortOrder) : SORT_ORDER_ASC); + + this._sortField = field; + this._sortOrder = order; + }, + + _normalizeField: function(field) { + if($.isNumeric(field)) { + return this.fields[field]; + } + + if(typeof field === "string") { + return $.grep(this.fields, function(f) { + return f.name === field; + })[0]; + } + + return field; + }, + + _reversedSortOrder: function(order) { + return (order === SORT_ORDER_ASC ? SORT_ORDER_DESC : SORT_ORDER_ASC); + }, + + _setSortingCss: function() { + var fieldIndex = this._visibleFieldIndex(this._sortField); + + this._headerRow.find("th").eq(fieldIndex) + .addClass(this._sortOrder === SORT_ORDER_ASC ? this.sortAscClass : this.sortDescClass); + }, + + _visibleFieldIndex: function(field) { + return $.inArray(field, $.grep(this.fields, function(f) { return f.visible; })); + }, + + _sortData: function() { + var sortFactor = this._sortFactor(), + sortField = this._sortField; + + if(sortField) { + this.data.sort(function(item1, item2) { + return sortFactor * sortField.sortingFunc(item1[sortField.name], item2[sortField.name]); + }); + } + }, + + _sortFactor: function() { + return this._sortOrder === SORT_ORDER_ASC ? 1 : -1; + }, + + _itemsCount: function() { + return this._loadStrategy.itemsCount(); + }, + + _pagesCount: function() { + var itemsCount = this._itemsCount(), + pageSize = this.pageSize; + return Math.floor(itemsCount / pageSize) + (itemsCount % pageSize ? 1 : 0); + }, + + _refreshPager: function() { + var $pagerContainer = this._pagerContainer; + $pagerContainer.empty(); + + if(this.paging) { + $pagerContainer.append(this._createPager()); + } + + var showPager = this.paging && this._pagesCount() > 1; + $pagerContainer.toggle(showPager); + }, + + _createPager: function() { + var $result; + + if($.isFunction(this.pagerRenderer)) { + $result = $(this.pagerRenderer({ + pageIndex: this.pageIndex, + pageCount: this._pagesCount() + })); + } else { + $result = $("<div>").append(this._createPagerByFormat()); + } + + $result.addClass(this.pagerClass); + + return $result; + }, + + _createPagerByFormat: function() { + var pageIndex = this.pageIndex, + pageCount = this._pagesCount(), + itemCount = this._itemsCount(), + pagerParts = this.pagerFormat.split(" "); + + return $.map(pagerParts, $.proxy(function(pagerPart) { + var result = pagerPart; + + if(pagerPart === PAGES_PLACEHOLDER) { + result = this._createPages(); + } else if(pagerPart === FIRST_PAGE_PLACEHOLDER) { + result = this._createPagerNavButton(this.pageFirstText, 1, pageIndex > 1); + } else if(pagerPart === PREV_PAGE_PLACEHOLDER) { + result = this._createPagerNavButton(this.pagePrevText, pageIndex - 1, pageIndex > 1); + } else if(pagerPart === NEXT_PAGE_PLACEHOLDER) { + result = this._createPagerNavButton(this.pageNextText, pageIndex + 1, pageIndex < pageCount); + } else if(pagerPart === LAST_PAGE_PLACEHOLDER) { + result = this._createPagerNavButton(this.pageLastText, pageCount, pageIndex < pageCount); + } else if(pagerPart === PAGE_INDEX_PLACEHOLDER) { + result = pageIndex; + } else if(pagerPart === PAGE_COUNT_PLACEHOLDER) { + result = pageCount; + } else if(pagerPart === ITEM_COUNT_PLACEHOLDER) { + result = itemCount; + } + + return $.isArray(result) ? result.concat([" "]) : [result, " "]; + }, this)); + }, + + _createPages: function() { + var pageCount = this._pagesCount(), + pageButtonCount = this.pageButtonCount, + firstDisplayingPage = this._firstDisplayingPage, + pages = []; + + if(firstDisplayingPage > 1) { + pages.push(this._createPagerPageNavButton(this.pageNavigatorPrevText, this.showPrevPages)); + } + + for(var i = 0, pageNumber = firstDisplayingPage; i < pageButtonCount && pageNumber <= pageCount; i++, pageNumber++) { + pages.push(pageNumber === this.pageIndex + ? this._createPagerCurrentPage() + : this._createPagerPage(pageNumber)); + } + + if((firstDisplayingPage + pageButtonCount - 1) < pageCount) { + pages.push(this._createPagerPageNavButton(this.pageNavigatorNextText, this.showNextPages)); + } + + return pages; + }, + + _createPagerNavButton: function(text, pageIndex, isActive) { + return this._createPagerButton(text, this.pagerNavButtonClass + (isActive ? "" : " " + this.pagerNavButtonInactiveClass), + isActive ? function() { this.openPage(pageIndex); } : $.noop); + }, + + _createPagerPageNavButton: function(text, handler) { + return this._createPagerButton(text, this.pagerNavButtonClass, handler); + }, + + _createPagerPage: function(pageIndex) { + return this._createPagerButton(pageIndex, this.pageClass, function() { + this.openPage(pageIndex); + }); + }, + + _createPagerButton: function(text, css, handler) { + var $link = $("<a>").attr("href", EMPTY_HREF) + .html(text) + .on("click", $.proxy(handler, this)); + + return $("<span>").addClass(css).append($link); + }, + + _createPagerCurrentPage: function() { + return $("<span>") + .addClass(this.pageClass) + .addClass(this.currentPageClass) + .text(this.pageIndex); + }, + + _refreshSize: function() { + this._refreshHeight(); + this._refreshWidth(); + }, + + _refreshWidth: function() { + var width = (this.width === "auto") ? this._getAutoWidth() : this.width; + + this._container.width(width); + }, + + _getAutoWidth: function() { + var $headerGrid = this._headerGrid, + $header = this._header; + + $headerGrid.width("auto"); + + var contentWidth = $headerGrid.outerWidth(); + var borderWidth = $header.outerWidth() - $header.innerWidth(); + + $headerGrid.width(""); + + return contentWidth + borderWidth; + }, + + _scrollBarWidth: (function() { + var result; + + return function() { + if(result === undefined) { + var $ghostContainer = $("<div style='width:50px;height:50px;overflow:hidden;position:absolute;top:-10000px;left:-10000px;'></div>"); + var $ghostContent = $("<div style='height:100px;'></div>"); + $ghostContainer.append($ghostContent).appendTo("body"); + var width = $ghostContent.innerWidth(); + $ghostContainer.css("overflow-y", "auto"); + var widthExcludingScrollBar = $ghostContent.innerWidth(); + $ghostContainer.remove(); + result = width - widthExcludingScrollBar; + } + return result; + }; + })(), + + _refreshHeight: function() { + var container = this._container, + pagerContainer = this._pagerContainer, + height = this.height, + nonBodyHeight; + + container.height(height); + + if(height !== "auto") { + height = container.height(); + + nonBodyHeight = this._header.outerHeight(true); + if(pagerContainer.parents(container).length) { + nonBodyHeight += pagerContainer.outerHeight(true); + } + + this._body.outerHeight(height - nonBodyHeight); + } + }, + + showPrevPages: function() { + var firstDisplayingPage = this._firstDisplayingPage, + pageButtonCount = this.pageButtonCount; + + this._firstDisplayingPage = (firstDisplayingPage > pageButtonCount) ? firstDisplayingPage - pageButtonCount : 1; + + this._refreshPager(); + }, + + showNextPages: function() { + var firstDisplayingPage = this._firstDisplayingPage, + pageButtonCount = this.pageButtonCount, + pageCount = this._pagesCount(); + + this._firstDisplayingPage = (firstDisplayingPage + 2 * pageButtonCount > pageCount) + ? pageCount - pageButtonCount + 1 + : firstDisplayingPage + pageButtonCount; + + this._refreshPager(); + }, + + openPage: function(pageIndex) { + if(pageIndex < 1 || pageIndex > this._pagesCount()) + return; + + this._setPage(pageIndex); + this._loadStrategy.openPage(pageIndex); + }, + + _setPage: function(pageIndex) { + var firstDisplayingPage = this._firstDisplayingPage, + pageButtonCount = this.pageButtonCount; + + this.pageIndex = pageIndex; + + if(pageIndex < firstDisplayingPage) { + this._firstDisplayingPage = pageIndex; + } + + if(pageIndex > firstDisplayingPage + pageButtonCount - 1) { + this._firstDisplayingPage = pageIndex - pageButtonCount + 1; + } + + this._callEventHandler(this.onPageChanged, { + pageIndex: pageIndex + }); + }, + + _controllerCall: function(method, param, isCanceled, doneCallback) { + if(isCanceled) + return $.Deferred().reject().promise(); + + this._showLoading(); + + var controller = this._controller; + if(!controller || !controller[method]) { + throw Error("controller has no method '" + method + "'"); + } + + return normalizePromise(controller[method](param)) + .done($.proxy(doneCallback, this)) + .fail($.proxy(this._errorHandler, this)) + .always($.proxy(this._hideLoading, this)); + }, + + _errorHandler: function() { + this._callEventHandler(this.onError, { + args: $.makeArray(arguments) + }); + }, + + _showLoading: function() { + if(!this.loadIndication) + return; + + clearTimeout(this._loadingTimer); + + this._loadingTimer = setTimeout($.proxy(function() { + this._loadIndicator.show(); + }, this), this.loadIndicationDelay); + }, + + _hideLoading: function() { + if(!this.loadIndication) + return; + + clearTimeout(this._loadingTimer); + this._loadIndicator.hide(); + }, + + search: function(filter) { + this._resetSorting(); + this._resetPager(); + return this.loadData(filter); + }, + + loadData: function(filter) { + filter = filter || (this.filtering ? this.getFilter() : {}); + + $.extend(filter, this._loadStrategy.loadParams(), this._sortingParams()); + + var args = this._callEventHandler(this.onDataLoading, { + filter: filter + }); + + return this._controllerCall("loadData", filter, args.cancel, function(loadedData) { + if(!loadedData) + return; + + this._loadStrategy.finishLoad(loadedData); + + this._callEventHandler(this.onDataLoaded, { + data: loadedData + }); + }); + }, + + getFilter: function() { + var result = {}; + this._eachField(function(field) { + if(field.filtering) { + this._setItemFieldValue(result, field, field.filterValue()); + } + }); + return result; + }, + + _sortingParams: function() { + if(this.sorting && this._sortField) { + return { + sortField: this._sortField.name, + sortOrder: this._sortOrder + }; + } + return {}; + }, + + getSorting: function() { + var sortingParams = this._sortingParams(); + return { + field: sortingParams.sortField, + order: sortingParams.sortOrder + }; + }, + + clearFilter: function() { + var $filterRow = this._createFilterRow(); + this._filterRow.replaceWith($filterRow); + this._filterRow = $filterRow; + return this.search(); + }, + + insertItem: function(item) { + var insertingItem = item || this._getValidatedInsertItem(); + + if(!insertingItem) + return $.Deferred().reject().promise(); + + var args = this._callEventHandler(this.onItemInserting, { + item: insertingItem + }); + + return this._controllerCall("insertItem", insertingItem, args.cancel, function(insertedItem) { + insertedItem = insertedItem || insertingItem; + this._loadStrategy.finishInsert(insertedItem); + + this._callEventHandler(this.onItemInserted, { + item: insertedItem + }); + }); + }, + + _getValidatedInsertItem: function() { + var item = this._getInsertItem(); + return this._validateItem(item, this._insertRow) ? item : null; + }, + + _getInsertItem: function() { + var result = {}; + this._eachField(function(field) { + if(field.inserting) { + this._setItemFieldValue(result, field, field.insertValue()); + } + }); + return result; + }, + + _validateItem: function(item, $row) { + var validationErrors = []; + + var args = { + item: item, + itemIndex: this._rowIndex($row), + row: $row + }; + + this._eachField(function(field) { + if(!field.validate || + ($row === this._insertRow && !field.inserting) || + ($row === this._getEditRow() && !field.editing)) + return; + + var fieldValue = this._getItemFieldValue(item, field); + + var errors = this._validation.validate($.extend({ + value: fieldValue, + rules: field.validate + }, args)); + + this._setCellValidity($row.children().eq(this._visibleFieldIndex(field)), errors); + + if(!errors.length) + return; + + validationErrors.push.apply(validationErrors, + $.map(errors, function(message) { + return { field: field, message: message }; + })); + }); + + if(!validationErrors.length) + return true; + + var invalidArgs = $.extend({ + errors: validationErrors + }, args); + this._callEventHandler(this.onItemInvalid, invalidArgs); + this.invalidNotify(invalidArgs); + + return false; + }, + + _setCellValidity: function($cell, errors) { + $cell + .toggleClass(this.invalidClass, !!errors.length) + .attr("title", errors.join("\n")); + }, + + clearInsert: function() { + var insertRow = this._createInsertRow(); + this._insertRow.replaceWith(insertRow); + this._insertRow = insertRow; + this.refresh(); + }, + + editItem: function(item) { + var $row = this.rowByItem(item); + if($row.length) { + this._editRow($row); + } + }, + + rowByItem: function(item) { + if(item.jquery || item.nodeType) + return $(item); + + return this._content.find("tr").filter(function() { + return $.data(this, JSGRID_ROW_DATA_KEY) === item; + }); + }, + + _editRow: function($row) { + if(!this.editing) + return; + + var item = $row.data(JSGRID_ROW_DATA_KEY); + + var args = this._callEventHandler(this.onItemEditing, { + row: $row, + item: item, + itemIndex: this._itemIndex(item) + }); + + if(args.cancel) + return; + + if(this._editingRow) { + this.cancelEdit(); + } + + var $editRow = this._createEditRow(item); + + this._editingRow = $row; + $row.hide(); + $editRow.insertBefore($row); + $row.data(JSGRID_EDIT_ROW_DATA_KEY, $editRow); + }, + + _createEditRow: function(item) { + if($.isFunction(this.editRowRenderer)) { + return $(this.renderTemplate(this.editRowRenderer, this, { item: item, itemIndex: this._itemIndex(item) })); + } + + var $result = $("<tr>").addClass(this.editRowClass); + + this._eachField(function(field) { + var fieldValue = this._getItemFieldValue(item, field); + + this._prepareCell("<td>", field, "editcss") + .append(this.renderTemplate(field.editTemplate || "", field, { value: fieldValue, item: item })) + .appendTo($result); + }); + + return $result; + }, + + updateItem: function(item, editedItem) { + if(arguments.length === 1) { + editedItem = item; + } + + var $row = item ? this.rowByItem(item) : this._editingRow; + editedItem = editedItem || this._getValidatedEditedItem(); + + if(!editedItem) + return; + + return this._updateRow($row, editedItem); + }, + + _getValidatedEditedItem: function() { + var item = this._getEditedItem(); + return this._validateItem(item, this._getEditRow()) ? item : null; + }, + + _updateRow: function($updatingRow, editedItem) { + var updatingItem = $updatingRow.data(JSGRID_ROW_DATA_KEY), + updatingItemIndex = this._itemIndex(updatingItem), + updatedItem = $.extend(true, {}, updatingItem, editedItem); + + var args = this._callEventHandler(this.onItemUpdating, { + row: $updatingRow, + item: updatedItem, + itemIndex: updatingItemIndex, + previousItem: updatingItem + }); + + return this._controllerCall("updateItem", updatedItem, args.cancel, function(loadedUpdatedItem) { + var previousItem = $.extend(true, {}, updatingItem); + updatedItem = loadedUpdatedItem || $.extend(true, updatingItem, editedItem); + + var $updatedRow = this._finishUpdate($updatingRow, updatedItem, updatingItemIndex); + + this._callEventHandler(this.onItemUpdated, { + row: $updatedRow, + item: updatedItem, + itemIndex: updatingItemIndex, + previousItem: previousItem + }); + }); + }, + + _rowIndex: function(row) { + return this._content.children().index($(row)); + }, + + _itemIndex: function(item) { + return $.inArray(item, this.data); + }, + + _finishUpdate: function($updatingRow, updatedItem, updatedItemIndex) { + this.cancelEdit(); + this.data[updatedItemIndex] = updatedItem; + + var $updatedRow = this._createRow(updatedItem, updatedItemIndex); + $updatingRow.replaceWith($updatedRow); + return $updatedRow; + }, + + _getEditedItem: function() { + var result = {}; + this._eachField(function(field) { + if(field.editing) { + this._setItemFieldValue(result, field, field.editValue()); + } + }); + return result; + }, + + cancelEdit: function() { + if(!this._editingRow) + return; + + this._getEditRow().remove(); + this._editingRow.show(); + this._editingRow = null; + }, + + _getEditRow: function() { + return this._editingRow && this._editingRow.data(JSGRID_EDIT_ROW_DATA_KEY); + }, + + deleteItem: function(item) { + var $row = this.rowByItem(item); + + if(!$row.length) + return; + + if(this.confirmDeleting && !window.confirm(getOrApply(this.deleteConfirm, this, $row.data(JSGRID_ROW_DATA_KEY)))) + return; + + return this._deleteRow($row); + }, + + _deleteRow: function($row) { + var deletingItem = $row.data(JSGRID_ROW_DATA_KEY), + deletingItemIndex = this._itemIndex(deletingItem); + + var args = this._callEventHandler(this.onItemDeleting, { + row: $row, + item: deletingItem, + itemIndex: deletingItemIndex + }); + + return this._controllerCall("deleteItem", deletingItem, args.cancel, function() { + this._loadStrategy.finishDelete(deletingItem, deletingItemIndex); + + this._callEventHandler(this.onItemDeleted, { + row: $row, + item: deletingItem, + itemIndex: deletingItemIndex + }); + }); + } + }; + + $.fn.jsGrid = function(config) { + var args = $.makeArray(arguments), + methodArgs = args.slice(1), + result = this; + + this.each(function() { + var $element = $(this), + instance = $element.data(JSGRID_DATA_KEY), + methodResult; + + if(instance) { + if(typeof config === "string") { + methodResult = instance[config].apply(instance, methodArgs); + if(methodResult !== undefined && methodResult !== instance) { + result = methodResult; + return false; + } + } else { + instance._detachWindowResizeCallback(); + instance._init(config); + instance.render(); + } + } else { + new Grid($element, config); + } + }); + + return result; + }; + + var fields = {}; + + var setDefaults = function(config) { + var componentPrototype; + + if($.isPlainObject(config)) { + componentPrototype = Grid.prototype; + } else { + componentPrototype = fields[config].prototype; + config = arguments[1] || {}; + } + + $.extend(componentPrototype, config); + }; + + var locales = {}; + + var locale = function(lang) { + var localeConfig = $.isPlainObject(lang) ? lang : locales[lang]; + + if(!localeConfig) + throw Error("unknown locale " + lang); + + setLocale(jsGrid, localeConfig); + }; + + var setLocale = function(obj, localeConfig) { + $.each(localeConfig, function(field, value) { + if($.isPlainObject(value)) { + setLocale(obj[field] || obj[field[0].toUpperCase() + field.slice(1)], value); + return; + } + + if(obj.hasOwnProperty(field)) { + obj[field] = value; + } else { + obj.prototype[field] = value; + } + }); + }; + + window.jsGrid = { + Grid: Grid, + fields: fields, + setDefaults: setDefaults, + locales: locales, + locale: locale, + version: '1.5.3' + }; + +}(window, jQuery)); + +(function(jsGrid, $, undefined) { + + function LoadIndicator(config) { + this._init(config); + } + + LoadIndicator.prototype = { + + container: "body", + message: "Loading...", + shading: true, + + zIndex: 1000, + shaderClass: "jsgrid-load-shader", + loadPanelClass: "jsgrid-load-panel", + + _init: function(config) { + $.extend(true, this, config); + + this._initContainer(); + this._initShader(); + this._initLoadPanel(); + }, + + _initContainer: function() { + this._container = $(this.container); + }, + + _initShader: function() { + if(!this.shading) + return; + + this._shader = $("<div>").addClass(this.shaderClass) + .hide() + .css({ + position: "absolute", + top: 0, + right: 0, + bottom: 0, + left: 0, + zIndex: this.zIndex + }) + .appendTo(this._container); + }, + + _initLoadPanel: function() { + this._loadPanel = $("<div>").addClass(this.loadPanelClass) + .text(this.message) + .hide() + .css({ + position: "absolute", + top: "50%", + left: "50%", + zIndex: this.zIndex + }) + .appendTo(this._container); + }, + + show: function() { + var $loadPanel = this._loadPanel.show(); + + var actualWidth = $loadPanel.outerWidth(); + var actualHeight = $loadPanel.outerHeight(); + + $loadPanel.css({ + marginTop: -actualHeight / 2, + marginLeft: -actualWidth / 2 + }); + + this._shader.show(); + }, + + hide: function() { + this._loadPanel.hide(); + this._shader.hide(); + } + + }; + + jsGrid.LoadIndicator = LoadIndicator; + +}(jsGrid, jQuery)); + +(function(jsGrid, $, undefined) { + + function DirectLoadingStrategy(grid) { + this._grid = grid; + } + + DirectLoadingStrategy.prototype = { + + firstDisplayIndex: function() { + var grid = this._grid; + return grid.option("paging") ? (grid.option("pageIndex") - 1) * grid.option("pageSize") : 0; + }, + + lastDisplayIndex: function() { + var grid = this._grid; + var itemsCount = grid.option("data").length; + + return grid.option("paging") + ? Math.min(grid.option("pageIndex") * grid.option("pageSize"), itemsCount) + : itemsCount; + }, + + itemsCount: function() { + return this._grid.option("data").length; + }, + + openPage: function(index) { + this._grid.refresh(); + }, + + loadParams: function() { + return {}; + }, + + sort: function() { + this._grid._sortData(); + this._grid.refresh(); + return $.Deferred().resolve().promise(); + }, + + reset: function() { + this._grid.refresh(); + return $.Deferred().resolve().promise(); + }, + + finishLoad: function(loadedData) { + this._grid.option("data", loadedData); + }, + + finishInsert: function(insertedItem) { + var grid = this._grid; + grid.option("data").push(insertedItem); + grid.refresh(); + }, + + finishDelete: function(deletedItem, deletedItemIndex) { + var grid = this._grid; + grid.option("data").splice(deletedItemIndex, 1); + grid.reset(); + } + }; + + + function PageLoadingStrategy(grid) { + this._grid = grid; + this._itemsCount = 0; + } + + PageLoadingStrategy.prototype = { + + firstDisplayIndex: function() { + return 0; + }, + + lastDisplayIndex: function() { + return this._grid.option("data").length; + }, + + itemsCount: function() { + return this._itemsCount; + }, + + openPage: function(index) { + this._grid.loadData(); + }, + + loadParams: function() { + var grid = this._grid; + return { + pageIndex: grid.option("pageIndex"), + pageSize: grid.option("pageSize") + }; + }, + + reset: function() { + return this._grid.loadData(); + }, + + sort: function() { + return this._grid.loadData(); + }, + + finishLoad: function(loadedData) { + this._itemsCount = loadedData.itemsCount; + this._grid.option("data", loadedData.data); + }, + + finishInsert: function(insertedItem) { + this._grid.search(); + }, + + finishDelete: function(deletedItem, deletedItemIndex) { + this._grid.search(); + } + }; + + jsGrid.loadStrategies = { + DirectLoadingStrategy: DirectLoadingStrategy, + PageLoadingStrategy: PageLoadingStrategy + }; + +}(jsGrid, jQuery)); + +(function(jsGrid, $, undefined) { + + var isDefined = function(val) { + return typeof(val) !== "undefined" && val !== null; + }; + + var sortStrategies = { + string: function(str1, str2) { + if(!isDefined(str1) && !isDefined(str2)) + return 0; + + if(!isDefined(str1)) + return -1; + + if(!isDefined(str2)) + return 1; + + return ("" + str1).localeCompare("" + str2); + }, + + number: function(n1, n2) { + return n1 - n2; + }, + + date: function(dt1, dt2) { + return dt1 - dt2; + }, + + numberAsString: function(n1, n2) { + return parseFloat(n1) - parseFloat(n2); + } + }; + + jsGrid.sortStrategies = sortStrategies; + +}(jsGrid, jQuery)); + +(function(jsGrid, $, undefined) { + + function Validation(config) { + this._init(config); + } + + Validation.prototype = { + + _init: function(config) { + $.extend(true, this, config); + }, + + validate: function(args) { + var errors = []; + + $.each(this._normalizeRules(args.rules), function(_, rule) { + if(rule.validator(args.value, args.item, rule.param)) + return; + + var errorMessage = $.isFunction(rule.message) ? rule.message(args.value, args.item) : rule.message; + errors.push(errorMessage); + }); + + return errors; + }, + + _normalizeRules: function(rules) { + if(!$.isArray(rules)) + rules = [rules]; + + return $.map(rules, $.proxy(function(rule) { + return this._normalizeRule(rule); + }, this)); + }, + + _normalizeRule: function(rule) { + if(typeof rule === "string") + rule = { validator: rule }; + + if($.isFunction(rule)) + rule = { validator: rule }; + + if($.isPlainObject(rule)) + rule = $.extend({}, rule); + else + throw Error("wrong validation config specified"); + + if($.isFunction(rule.validator)) + return rule; + + return this._applyNamedValidator(rule, rule.validator); + }, + + _applyNamedValidator: function(rule, validatorName) { + delete rule.validator; + + var validator = validators[validatorName]; + if(!validator) + throw Error("unknown validator \"" + validatorName + "\""); + + if($.isFunction(validator)) { + validator = { validator: validator }; + } + + return $.extend({}, validator, rule); + } + }; + + jsGrid.Validation = Validation; + + + var validators = { + required: { + message: "Field is required", + validator: function(value) { + return value !== undefined && value !== null && value !== ""; + } + }, + + rangeLength: { + message: "Field value length is out of the defined range", + validator: function(value, _, param) { + return value.length >= param[0] && value.length <= param[1]; + } + }, + + minLength: { + message: "Field value is too short", + validator: function(value, _, param) { + return value.length >= param; + } + }, + + maxLength: { + message: "Field value is too long", + validator: function(value, _, param) { + return value.length <= param; + } + }, + + pattern: { + message: "Field value is not matching the defined pattern", + validator: function(value, _, param) { + if(typeof param === "string") { + param = new RegExp("^(?:" + param + ")$"); + } + return param.test(value); + } + }, + + range: { + message: "Field value is out of the defined range", + validator: function(value, _, param) { + return value >= param[0] && value <= param[1]; + } + }, + + min: { + message: "Field value is too small", + validator: function(value, _, param) { + return value >= param; + } + }, + + max: { + message: "Field value is too large", + validator: function(value, _, param) { + return value <= param; + } + } + }; + + jsGrid.validators = validators; + +}(jsGrid, jQuery)); + +(function(jsGrid, $, undefined) { + + function Field(config) { + $.extend(true, this, config); + this.sortingFunc = this._getSortingFunc(); + } + + Field.prototype = { + name: "", + title: null, + css: "", + align: "", + width: 100, + + visible: true, + filtering: true, + inserting: true, + editing: true, + sorting: true, + sorter: "string", // name of SortStrategy or function to compare elements + + headerTemplate: function() { + return (this.title === undefined || this.title === null) ? this.name : this.title; + }, + + itemTemplate: function(value, item) { + return value; + }, + + filterTemplate: function() { + return ""; + }, + + insertTemplate: function() { + return ""; + }, + + editTemplate: function(value, item) { + this._value = value; + return this.itemTemplate(value, item); + }, + + filterValue: function() { + return ""; + }, + + insertValue: function() { + return ""; + }, + + editValue: function() { + return this._value; + }, + + _getSortingFunc: function() { + var sorter = this.sorter; + + if($.isFunction(sorter)) { + return sorter; + } + + if(typeof sorter === "string") { + return jsGrid.sortStrategies[sorter]; + } + + throw Error("wrong sorter for the field \"" + this.name + "\"!"); + } + }; + + jsGrid.Field = Field; + +}(jsGrid, jQuery)); + +(function(jsGrid, $, undefined) { + + var Field = jsGrid.Field; + + function TextField(config) { + Field.call(this, config); + } + + TextField.prototype = new Field({ + + autosearch: true, + readOnly: false, + + filterTemplate: function() { + if(!this.filtering) + return ""; + + var grid = this._grid, + $result = this.filterControl = this._createTextBox(); + + if(this.autosearch) { + $result.on("keypress", function(e) { + if(e.which === 13) { + grid.search(); + e.preventDefault(); + } + }); + } + + return $result; + }, + + insertTemplate: function() { + if(!this.inserting) + return ""; + + return this.insertControl = this._createTextBox(); + }, + + editTemplate: function(value) { + if(!this.editing) + return this.itemTemplate.apply(this, arguments); + + var $result = this.editControl = this._createTextBox(); + $result.val(value); + return $result; + }, + + filterValue: function() { + return this.filterControl.val(); + }, + + insertValue: function() { + return this.insertControl.val(); + }, + + editValue: function() { + return this.editControl.val(); + }, + + _createTextBox: function() { + return $("<input>").attr("type", "text") + .prop("readonly", !!this.readOnly); + } + }); + + jsGrid.fields.text = jsGrid.TextField = TextField; + +}(jsGrid, jQuery)); + +(function(jsGrid, $, undefined) { + + var TextField = jsGrid.TextField; + + function NumberField(config) { + TextField.call(this, config); + } + + NumberField.prototype = new TextField({ + + sorter: "number", + align: "right", + readOnly: false, + + filterValue: function() { + return this.filterControl.val() + ? parseInt(this.filterControl.val() || 0, 10) + : undefined; + }, + + insertValue: function() { + return this.insertControl.val() + ? parseInt(this.insertControl.val() || 0, 10) + : undefined; + }, + + editValue: function() { + return this.editControl.val() + ? parseInt(this.editControl.val() || 0, 10) + : undefined; + }, + + _createTextBox: function() { + return $("<input>").attr("type", "number") + .prop("readonly", !!this.readOnly); + } + }); + + jsGrid.fields.number = jsGrid.NumberField = NumberField; + +}(jsGrid, jQuery)); + +(function(jsGrid, $, undefined) { + + var TextField = jsGrid.TextField; + + function TextAreaField(config) { + TextField.call(this, config); + } + + TextAreaField.prototype = new TextField({ + + insertTemplate: function() { + if(!this.inserting) + return ""; + + return this.insertControl = this._createTextArea(); + }, + + editTemplate: function(value) { + if(!this.editing) + return this.itemTemplate.apply(this, arguments); + + var $result = this.editControl = this._createTextArea(); + $result.val(value); + return $result; + }, + + _createTextArea: function() { + return $("<textarea>").prop("readonly", !!this.readOnly); + } + }); + + jsGrid.fields.textarea = jsGrid.TextAreaField = TextAreaField; + +}(jsGrid, jQuery)); + +(function(jsGrid, $, undefined) { + + var NumberField = jsGrid.NumberField; + var numberValueType = "number"; + var stringValueType = "string"; + + function SelectField(config) { + this.items = []; + this.selectedIndex = -1; + this.valueField = ""; + this.textField = ""; + + if(config.valueField && config.items.length) { + var firstItemValue = config.items[0][config.valueField]; + this.valueType = (typeof firstItemValue) === numberValueType ? numberValueType : stringValueType; + } + + this.sorter = this.valueType; + + NumberField.call(this, config); + } + + SelectField.prototype = new NumberField({ + + align: "center", + valueType: numberValueType, + + itemTemplate: function(value) { + var items = this.items, + valueField = this.valueField, + textField = this.textField, + resultItem; + + if(valueField) { + resultItem = $.grep(items, function(item, index) { + return item[valueField] === value; + })[0] || {}; + } + else { + resultItem = items[value]; + } + + var result = (textField ? resultItem[textField] : resultItem); + + return (result === undefined || result === null) ? "" : result; + }, + + filterTemplate: function() { + if(!this.filtering) + return ""; + + var grid = this._grid, + $result = this.filterControl = this._createSelect(); + + if(this.autosearch) { + $result.on("change", function(e) { + grid.search(); + }); + } + + return $result; + }, + + insertTemplate: function() { + if(!this.inserting) + return ""; + + return this.insertControl = this._createSelect(); + }, + + editTemplate: function(value) { + if(!this.editing) + return this.itemTemplate.apply(this, arguments); + + var $result = this.editControl = this._createSelect(); + (value !== undefined) && $result.val(value); + return $result; + }, + + filterValue: function() { + var val = this.filterControl.val(); + return this.valueType === numberValueType ? parseInt(val || 0, 10) : val; + }, + + insertValue: function() { + var val = this.insertControl.val(); + return this.valueType === numberValueType ? parseInt(val || 0, 10) : val; + }, + + editValue: function() { + var val = this.editControl.val(); + return this.valueType === numberValueType ? parseInt(val || 0, 10) : val; + }, + + _createSelect: function() { + var $result = $("<select>"), + valueField = this.valueField, + textField = this.textField, + selectedIndex = this.selectedIndex; + + $.each(this.items, function(index, item) { + var value = valueField ? item[valueField] : index, + text = textField ? item[textField] : item; + + var $option = $("<option>") + .attr("value", value) + .text(text) + .appendTo($result); + + $option.prop("selected", (selectedIndex === index)); + }); + + $result.prop("disabled", !!this.readOnly); + + return $result; + } + }); + + jsGrid.fields.select = jsGrid.SelectField = SelectField; + +}(jsGrid, jQuery)); + +(function(jsGrid, $, undefined) { + + var Field = jsGrid.Field; + + function CheckboxField(config) { + Field.call(this, config); + } + + CheckboxField.prototype = new Field({ + + sorter: "number", + align: "center", + autosearch: true, + + itemTemplate: function(value) { + return this._createCheckbox().prop({ + checked: value, + disabled: true + }); + }, + + filterTemplate: function() { + if(!this.filtering) + return ""; + + var grid = this._grid, + $result = this.filterControl = this._createCheckbox(); + + $result.prop({ + readOnly: true, + indeterminate: true + }); + + $result.on("click", function() { + var $cb = $(this); + + if($cb.prop("readOnly")) { + $cb.prop({ + checked: false, + readOnly: false + }); + } + else if(!$cb.prop("checked")) { + $cb.prop({ + readOnly: true, + indeterminate: true + }); + } + }); + + if(this.autosearch) { + $result.on("click", function() { + grid.search(); + }); + } + + return $result; + }, + + insertTemplate: function() { + if(!this.inserting) + return ""; + + return this.insertControl = this._createCheckbox(); + }, + + editTemplate: function(value) { + if(!this.editing) + return this.itemTemplate.apply(this, arguments); + + var $result = this.editControl = this._createCheckbox(); + $result.prop("checked", value); + return $result; + }, + + filterValue: function() { + return this.filterControl.get(0).indeterminate + ? undefined + : this.filterControl.is(":checked"); + }, + + insertValue: function() { + return this.insertControl.is(":checked"); + }, + + editValue: function() { + return this.editControl.is(":checked"); + }, + + _createCheckbox: function() { + return $("<input>").attr("type", "checkbox"); + } + }); + + jsGrid.fields.checkbox = jsGrid.CheckboxField = CheckboxField; + +}(jsGrid, jQuery)); + +(function(jsGrid, $, undefined) { + + var Field = jsGrid.Field; + + function ControlField(config) { + Field.call(this, config); + this._configInitialized = false; + } + + ControlField.prototype = new Field({ + css: "jsgrid-control-field", + align: "center", + width: 50, + filtering: false, + inserting: false, + editing: false, + sorting: false, + + buttonClass: "jsgrid-button", + modeButtonClass: "jsgrid-mode-button", + + modeOnButtonClass: "jsgrid-mode-on-button", + searchModeButtonClass: "jsgrid-search-mode-button", + insertModeButtonClass: "jsgrid-insert-mode-button", + editButtonClass: "jsgrid-edit-button", + deleteButtonClass: "jsgrid-delete-button", + searchButtonClass: "jsgrid-search-button", + clearFilterButtonClass: "jsgrid-clear-filter-button", + insertButtonClass: "jsgrid-insert-button", + updateButtonClass: "jsgrid-update-button", + cancelEditButtonClass: "jsgrid-cancel-edit-button", + + searchModeButtonTooltip: "Switch to searching", + insertModeButtonTooltip: "Switch to inserting", + editButtonTooltip: "Edit", + deleteButtonTooltip: "Delete", + searchButtonTooltip: "Search", + clearFilterButtonTooltip: "Clear filter", + insertButtonTooltip: "Insert", + updateButtonTooltip: "Update", + cancelEditButtonTooltip: "Cancel edit", + + editButton: true, + deleteButton: true, + clearFilterButton: true, + modeSwitchButton: true, + + _initConfig: function() { + this._hasFiltering = this._grid.filtering; + this._hasInserting = this._grid.inserting; + + if(this._hasInserting && this.modeSwitchButton) { + this._grid.inserting = false; + } + + this._configInitialized = true; + }, + + headerTemplate: function() { + if(!this._configInitialized) { + this._initConfig(); + } + + var hasFiltering = this._hasFiltering; + var hasInserting = this._hasInserting; + + if(!this.modeSwitchButton || (!hasFiltering && !hasInserting)) + return ""; + + if(hasFiltering && !hasInserting) + return this._createFilterSwitchButton(); + + if(hasInserting && !hasFiltering) + return this._createInsertSwitchButton(); + + return this._createModeSwitchButton(); + }, + + itemTemplate: function(value, item) { + var $result = $([]); + + if(this.editButton) { + $result = $result.add(this._createEditButton(item)); + } + + if(this.deleteButton) { + $result = $result.add(this._createDeleteButton(item)); + } + + return $result; + }, + + filterTemplate: function() { + var $result = this._createSearchButton(); + return this.clearFilterButton ? $result.add(this._createClearFilterButton()) : $result; + }, + + insertTemplate: function() { + return this._createInsertButton(); + }, + + editTemplate: function() { + return this._createUpdateButton().add(this._createCancelEditButton()); + }, + + _createFilterSwitchButton: function() { + return this._createOnOffSwitchButton("filtering", this.searchModeButtonClass, true); + }, + + _createInsertSwitchButton: function() { + return this._createOnOffSwitchButton("inserting", this.insertModeButtonClass, false); + }, + + _createOnOffSwitchButton: function(option, cssClass, isOnInitially) { + var isOn = isOnInitially; + + var updateButtonState = $.proxy(function() { + $button.toggleClass(this.modeOnButtonClass, isOn); + }, this); + + var $button = this._createGridButton(this.modeButtonClass + " " + cssClass, "", function(grid) { + isOn = !isOn; + grid.option(option, isOn); + updateButtonState(); + }); + + updateButtonState(); + + return $button; + }, + + _createModeSwitchButton: function() { + var isInserting = false; + + var updateButtonState = $.proxy(function() { + $button.attr("title", isInserting ? this.searchModeButtonTooltip : this.insertModeButtonTooltip) + .toggleClass(this.insertModeButtonClass, !isInserting) + .toggleClass(this.searchModeButtonClass, isInserting); + }, this); + + var $button = this._createGridButton(this.modeButtonClass, "", function(grid) { + isInserting = !isInserting; + grid.option("inserting", isInserting); + grid.option("filtering", !isInserting); + updateButtonState(); + }); + + updateButtonState(); + + return $button; + }, + + _createEditButton: function(item) { + return this._createGridButton(this.editButtonClass, this.editButtonTooltip, function(grid, e) { + grid.editItem(item); + e.stopPropagation(); + }); + }, + + _createDeleteButton: function(item) { + return this._createGridButton(this.deleteButtonClass, this.deleteButtonTooltip, function(grid, e) { + grid.deleteItem(item); + e.stopPropagation(); + }); + }, + + _createSearchButton: function() { + return this._createGridButton(this.searchButtonClass, this.searchButtonTooltip, function(grid) { + grid.search(); + }); + }, + + _createClearFilterButton: function() { + return this._createGridButton(this.clearFilterButtonClass, this.clearFilterButtonTooltip, function(grid) { + grid.clearFilter(); + }); + }, + + _createInsertButton: function() { + return this._createGridButton(this.insertButtonClass, this.insertButtonTooltip, function(grid) { + grid.insertItem().done(function() { + grid.clearInsert(); + }); + }); + }, + + _createUpdateButton: function() { + return this._createGridButton(this.updateButtonClass, this.updateButtonTooltip, function(grid, e) { + grid.updateItem(); + e.stopPropagation(); + }); + }, + + _createCancelEditButton: function() { + return this._createGridButton(this.cancelEditButtonClass, this.cancelEditButtonTooltip, function(grid, e) { + grid.cancelEdit(); + e.stopPropagation(); + }); + }, + + _createGridButton: function(cls, tooltip, clickHandler) { + var grid = this._grid; + + return $("<input>").addClass(this.buttonClass) + .addClass(cls) + .attr({ + type: "button", + title: tooltip + }) + .on("click", function(e) { + clickHandler(grid, e); + }); + }, + + editValue: function() { + return ""; + } + + }); + + jsGrid.fields.control = jsGrid.ControlField = ControlField; + +}(jsGrid, jQuery)); |