/*

 * 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} &nbsp;&nbsp; {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));

