File indexing completed on 2025-12-07 05:47:04

0001 /* 
0002 
0003 jTable 2.4.0
0004 http://www.jtable.org
0005 
0006 ---------------------------------------------------------------------------
0007 
0008 Copyright (C) 2011-2014 by Halil İbrahim Kalkan (http://www.halilibrahimkalkan.com)
0009 
0010 Permission is hereby granted, free of charge, to any person obtaining a copy
0011 of this software and associated documentation files (the "Software"), to deal
0012 in the Software without restriction, including without limitation the rights
0013 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
0014 copies of the Software, and to permit persons to whom the Software is
0015 furnished to do so, subject to the following conditions:
0016 
0017 The above copyright notice and this permission notice shall be included in
0018 all copies or substantial portions of the Software.
0019 
0020 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
0021 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
0022 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
0023 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
0024 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
0025 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
0026 THE SOFTWARE.
0027 
0028 */
0029 
0030 /************************************************************************
0031 * CORE jTable module                                                    *
0032 *************************************************************************/
0033 (function ($) {
0034 
0035     var unloadingPage;
0036     
0037     $(window).on('beforeunload', function () {
0038         unloadingPage = true;
0039     });
0040     $(window).on('unload', function () {
0041         unloadingPage = false;
0042     });
0043 
0044     $.widget("hik.jtable", {
0045 
0046         /************************************************************************
0047         * DEFAULT OPTIONS / EVENTS                                              *
0048         *************************************************************************/
0049         options: {
0050 
0051             //Options
0052             actions: {},
0053             fields: {},
0054             animationsEnabled: true,
0055             defaultDateFormat: 'yy-mm-dd',
0056             dialogShowEffect: 'fade',
0057             dialogHideEffect: 'fade',
0058             showCloseButton: false,
0059             loadingAnimationDelay: 500,
0060             saveUserPreferences: true,
0061             jqueryuiTheme: false,
0062             unAuthorizedRequestRedirectUrl: null,
0063 
0064             ajaxSettings: {
0065                 type: 'POST',
0066                 dataType: 'json'
0067             },
0068 
0069             toolbar: {
0070                 hoverAnimation: true,
0071                 hoverAnimationDuration: 60,
0072                 hoverAnimationEasing: undefined,
0073                 items: []
0074             },
0075 
0076             //Events
0077             closeRequested: function (event, data) { },
0078             formCreated: function (event, data) { },
0079             formSubmitting: function (event, data) { },
0080             formClosed: function (event, data) { },
0081             loadingRecords: function (event, data) { },
0082             recordsLoaded: function (event, data) { },
0083             rowInserted: function (event, data) { },
0084             rowsRemoved: function (event, data) { },
0085 
0086             //Localization
0087             messages: {
0088                 serverCommunicationError: 'An error occured while communicating to the server.',
0089                 loadingMessage: 'Loading records...',
0090                 noDataAvailable: 'No data available!',
0091                 areYouSure: 'Are you sure?',
0092                 save: 'Save',
0093                 saving: 'Saving',
0094                 cancel: 'Cancel',
0095                 error: 'Error',
0096                 close: 'Close',
0097                 cannotLoadOptionsFor: 'Can not load options for field {0}'
0098             }
0099         },
0100 
0101         /************************************************************************
0102         * PRIVATE FIELDS                                                        *
0103         *************************************************************************/
0104 
0105         _$mainContainer: null, //Reference to the main container of all elements that are created by this plug-in (jQuery object)
0106 
0107         _$titleDiv: null, //Reference to the title div (jQuery object)
0108         _$toolbarDiv: null, //Reference to the toolbar div (jQuery object)
0109 
0110         _$table: null, //Reference to the main <table> (jQuery object)
0111         _$tableBody: null, //Reference to <body> in the table (jQuery object)
0112         _$tableRows: null, //Array of all <tr> in the table (except "no data" row) (jQuery object array)
0113 
0114         _$busyDiv: null, //Reference to the div that is used to block UI while busy (jQuery object)
0115         _$busyMessageDiv: null, //Reference to the div that is used to show some message when UI is blocked (jQuery object)
0116         _$errorDialogDiv: null, //Reference to the error dialog div (jQuery object)
0117 
0118         _columnList: null, //Name of all data columns in the table (select column and command columns are not included) (string array)
0119         _fieldList: null, //Name of all fields of a record (defined in fields option) (string array)
0120         _keyField: null, //Name of the key field of a record (that is defined as 'key: true' in the fields option) (string)
0121 
0122         _firstDataColumnOffset: 0, //Start index of first record field in table columns (some columns can be placed before first data column, such as select checkbox column) (integer)
0123         _lastPostData: null, //Last posted data on load method (object)
0124 
0125         _cache: null, //General purpose cache dictionary (object)
0126 
0127         /************************************************************************
0128         * CONSTRUCTOR AND INITIALIZATION METHODS                                *
0129         *************************************************************************/
0130 
0131         /* Contructor.
0132         *************************************************************************/
0133         _create: function () {
0134 
0135             //Initialization
0136             this._normalizeFieldsOptions();
0137             this._initializeFields();
0138             this._createFieldAndColumnList();
0139 
0140             //Creating DOM elements
0141             this._createMainContainer();
0142             this._createTableTitle();
0143             this._createToolBar();
0144             this._createTable();
0145             this._createBusyPanel();
0146             this._createErrorDialogDiv();
0147             this._addNoDataRow();
0148 
0149             this._cookieKeyPrefix = this._generateCookieKeyPrefix();            
0150         },
0151 
0152         /* Normalizes some options for all fields (sets default values).
0153         *************************************************************************/
0154         _normalizeFieldsOptions: function () {
0155             var self = this;
0156             $.each(self.options.fields, function (fieldName, props) {
0157                 self._normalizeFieldOptions(fieldName, props);
0158             });
0159         },
0160 
0161         /* Normalizes some options for a field (sets default values).
0162         *************************************************************************/
0163         _normalizeFieldOptions: function (fieldName, props) {
0164             if (props.listClass == undefined) {
0165                 props.listClass = '';
0166             }
0167             if (props.inputClass == undefined) {
0168                 props.inputClass = '';
0169             }
0170 
0171             //Convert dependsOn to array if it's a comma seperated lists
0172             if (props.dependsOn && $.type(props.dependsOn) === 'string') {
0173                 var dependsOnArray = props.dependsOn.split(',');
0174                 props.dependsOn = [];
0175                 for (var i = 0; i < dependsOnArray.length; i++) {
0176                     props.dependsOn.push($.trim(dependsOnArray[i]));
0177                 }
0178             }
0179         },
0180 
0181         /* Intializes some private variables.
0182         *************************************************************************/
0183         _initializeFields: function () {
0184             this._lastPostData = {};
0185             this._$tableRows = [];
0186             this._columnList = [];
0187             this._fieldList = [];
0188             this._cache = [];
0189         },
0190 
0191         /* Fills _fieldList, _columnList arrays and sets _keyField variable.
0192         *************************************************************************/
0193         _createFieldAndColumnList: function () {
0194             var self = this;
0195 
0196             $.each(self.options.fields, function (name, props) {
0197 
0198                 //Add field to the field list
0199                 self._fieldList.push(name);
0200 
0201                 //Check if this field is the key field
0202                 if (props.key == true) {
0203                     self._keyField = name;
0204                 }
0205 
0206                 //Add field to column list if it is shown in the table
0207                 if (props.list != false && props.type != 'hidden') {
0208                     self._columnList.push(name);
0209                 }
0210             });
0211         },
0212 
0213         /* Creates the main container div.
0214         *************************************************************************/
0215         _createMainContainer: function () {
0216             this._$mainContainer = $('<div />')
0217                 .addClass('jtable-main-container')
0218                 .appendTo(this.element);
0219 
0220             this._jqueryuiThemeAddClass(this._$mainContainer, 'ui-widget');
0221         },
0222 
0223         /* Creates title of the table if a title supplied in options.
0224         *************************************************************************/
0225         _createTableTitle: function () {
0226             var self = this;
0227 
0228             if (!self.options.title) {
0229                 return;
0230             }
0231 
0232             var $titleDiv = $('<div />')
0233                 .addClass('jtable-title')
0234                 .appendTo(self._$mainContainer);
0235 
0236             self._jqueryuiThemeAddClass($titleDiv, 'ui-widget-header');
0237 
0238             $('<div />')
0239                 .addClass('jtable-title-text')
0240                 .appendTo($titleDiv)
0241                 .append(self.options.title);
0242 
0243             if (self.options.showCloseButton) {
0244 
0245                 var $textSpan = $('<span />')
0246                     .html(self.options.messages.close);
0247 
0248                 $('<button></button>')
0249                     .addClass('jtable-command-button jtable-close-button')
0250                     .attr('title', self.options.messages.close)
0251                     .append($textSpan)
0252                     .appendTo($titleDiv)
0253                     .click(function (e) {
0254                         e.preventDefault();
0255                         e.stopPropagation();
0256                         self._onCloseRequested();
0257                     });
0258             }
0259 
0260             self._$titleDiv = $titleDiv;
0261         },
0262 
0263         /* Creates the table.
0264         *************************************************************************/
0265         _createTable: function () {
0266             this._$table = $('<table></table>')
0267                 .addClass('jtable')
0268                 .appendTo(this._$mainContainer);
0269 
0270             if (this.options.tableId) {
0271                 this._$table.attr('id', this.options.tableId);
0272             }
0273 
0274             this._jqueryuiThemeAddClass(this._$table, 'ui-widget-content');
0275 
0276             this._createTableHead();
0277             this._createTableBody();
0278         },
0279 
0280         /* Creates header (all column headers) of the table.
0281         *************************************************************************/
0282         _createTableHead: function () {
0283             var $thead = $('<thead></thead>')
0284                 .appendTo(this._$table);
0285 
0286             this._addRowToTableHead($thead);
0287         },
0288 
0289         /* Adds tr element to given thead element
0290         *************************************************************************/
0291         _addRowToTableHead: function ($thead) {
0292             var $tr = $('<tr></tr>')
0293                 .appendTo($thead);
0294 
0295             this._addColumnsToHeaderRow($tr);
0296         },
0297 
0298         /* Adds column header cells to given tr element.
0299         *************************************************************************/
0300         _addColumnsToHeaderRow: function ($tr) {
0301             for (var i = 0; i < this._columnList.length; i++) {
0302                 var fieldName = this._columnList[i];
0303                 var $headerCell = this._createHeaderCellForField(fieldName, this.options.fields[fieldName]);
0304                 $headerCell.appendTo($tr);
0305             }
0306         },
0307 
0308         /* Creates a header cell for given field.
0309         *  Returns th jQuery object.
0310         *************************************************************************/
0311         _createHeaderCellForField: function (fieldName, field) {
0312             field.width = field.width || '10%'; //default column width: 10%.
0313 
0314             var $headerTextSpan = $('<span />')
0315                 .addClass('jtable-column-header-text')
0316                 .html(field.title);
0317 
0318             var $headerContainerDiv = $('<div />')
0319                 .addClass('jtable-column-header-container')
0320                 .append($headerTextSpan);
0321 
0322             var $th = $('<th></th>')
0323                 .addClass('jtable-column-header')
0324                 .addClass(field.listClass)
0325                 .css('width', field.width)
0326                 .data('fieldName', fieldName)
0327                 .append($headerContainerDiv);
0328 
0329             this._jqueryuiThemeAddClass($th, 'ui-state-default');
0330 
0331             return $th;
0332         },
0333 
0334         /* Creates an empty header cell that can be used as command column headers.
0335         *************************************************************************/
0336         _createEmptyCommandHeader: function () {
0337             var $th = $('<th></th>')
0338                 .addClass('jtable-command-column-header')
0339                 .css('width', '1%');
0340 
0341             this._jqueryuiThemeAddClass($th, 'ui-state-default');
0342 
0343             return $th;
0344         },
0345 
0346         /* Creates tbody tag and adds to the table.
0347         *************************************************************************/
0348         _createTableBody: function () {
0349             this._$tableBody = $('<tbody></tbody>').appendTo(this._$table);
0350         },
0351 
0352         /* Creates a div to block UI while jTable is busy.
0353         *************************************************************************/
0354         _createBusyPanel: function () {
0355             this._$busyMessageDiv = $('<div />').addClass('jtable-busy-message').prependTo(this._$mainContainer);
0356             this._$busyDiv = $('<div />').addClass('jtable-busy-panel-background').prependTo(this._$mainContainer);
0357             this._jqueryuiThemeAddClass(this._$busyMessageDiv, 'ui-widget-header');
0358             this._hideBusy();
0359         },
0360 
0361         /* Creates and prepares error dialog div.
0362         *************************************************************************/
0363         _createErrorDialogDiv: function () {
0364             var self = this;
0365 
0366             self._$errorDialogDiv = $('<div></div>').appendTo(self._$mainContainer);
0367             self._$errorDialogDiv.dialog({
0368                 autoOpen: false,
0369                 show: self.options.dialogShowEffect,
0370                 hide: self.options.dialogHideEffect,
0371                 modal: true,
0372                 title: self.options.messages.error,
0373                 buttons: [{
0374                     text: self.options.messages.close,
0375                     click: function () {
0376                         self._$errorDialogDiv.dialog('close');
0377                     }
0378                 }]
0379             });
0380         },
0381 
0382         /************************************************************************
0383         * PUBLIC METHODS                                                        *
0384         *************************************************************************/
0385 
0386         /* Loads data using AJAX call, clears table and fills with new data.
0387         *************************************************************************/
0388         load: function (postData, completeCallback) {
0389             this._lastPostData = postData;
0390             this._reloadTable(completeCallback);
0391         },
0392 
0393         /* Refreshes (re-loads) table data with last postData.
0394         *************************************************************************/
0395         reload: function (completeCallback) {
0396             this._reloadTable(completeCallback);
0397         },
0398 
0399         /* Gets a jQuery row object according to given record key
0400         *************************************************************************/
0401         getRowByKey: function (key) {
0402             for (var i = 0; i < this._$tableRows.length; i++) {
0403                 if (key == this._getKeyValueOfRecord(this._$tableRows[i].data('record'))) {
0404                     return this._$tableRows[i];
0405                 }
0406             }
0407 
0408             return null;
0409         },
0410 
0411         /* Completely removes the table from it's container.
0412         *************************************************************************/
0413         destroy: function () {
0414             this.element.empty();
0415             $.Widget.prototype.destroy.call(this);
0416         },
0417 
0418         /************************************************************************
0419         * PRIVATE METHODS                                                       *
0420         *************************************************************************/
0421 
0422         /* Used to change options dynamically after initialization.
0423         *************************************************************************/
0424         _setOption: function (key, value) {
0425 
0426         },
0427 
0428         /* LOADING RECORDS  *****************************************************/
0429 
0430         /* Performs an AJAX call to reload data of the table.
0431         *************************************************************************/
0432         _reloadTable: function (completeCallback) {
0433             var self = this;
0434 
0435             var completeReload = function(data) {
0436                 self._hideBusy();
0437 
0438                 //Show the error message if server returns error
0439                 if (data.Result != 'OK') {
0440                     self._showError(data.Message);
0441                     return;
0442                 }
0443 
0444                 //Re-generate table rows
0445                 self._removeAllRows('reloading');
0446                 self._addRecordsToTable(data.Records);
0447 
0448                 self._onRecordsLoaded(data);
0449 
0450                 //Call complete callback
0451                 if (completeCallback) {
0452                     completeCallback();
0453                 }
0454             };
0455 
0456             self._showBusy(self.options.messages.loadingMessage, self.options.loadingAnimationDelay); //Disable table since it's busy
0457             self._onLoadingRecords();
0458 
0459             //listAction may be a function, check if it is
0460             if ($.isFunction(self.options.actions.listAction)) {
0461 
0462                 //Execute the function
0463                 var funcResult = self.options.actions.listAction(self._lastPostData, self._createJtParamsForLoading());
0464 
0465                 //Check if result is a jQuery Deferred object
0466                 if (self._isDeferredObject(funcResult)) {
0467                     funcResult.done(function(data) {
0468                         completeReload(data);
0469                     }).fail(function() {
0470                         self._showError(self.options.messages.serverCommunicationError);
0471                     }).always(function() {
0472                         self._hideBusy();
0473                     });
0474                 } else { //assume it's the data we're loading
0475                     completeReload(funcResult);
0476                 }
0477 
0478             } else { //assume listAction as URL string.
0479 
0480                 //Generate URL (with query string parameters) to load records
0481                 var loadUrl = self._createRecordLoadUrl();
0482 
0483                 //Load data from server using AJAX
0484                 self._ajax({
0485                     url: loadUrl,
0486                     data: self._lastPostData,
0487                     success: function (data) {
0488                         completeReload(data);
0489                     },
0490                     error: function () {
0491                         self._hideBusy();
0492                         self._showError(self.options.messages.serverCommunicationError);
0493                     }
0494                 });
0495 
0496             }
0497         },
0498 
0499         /* Creates URL to load records.
0500         *************************************************************************/
0501         _createRecordLoadUrl: function () {
0502             return this.options.actions.listAction;
0503         },
0504 
0505         _createJtParamsForLoading: function() {
0506             return {
0507                 //Empty as default, paging, sorting or other extensions can override this method to add additional params to load request
0508             };
0509         },
0510 
0511         /* TABLE MANIPULATION METHODS *******************************************/
0512 
0513         /* Creates a row from given record
0514         *************************************************************************/
0515         _createRowFromRecord: function (record) {
0516             var $tr = $('<tr></tr>')
0517                 .addClass('jtable-data-row')
0518                 .attr('data-record-key', this._getKeyValueOfRecord(record))
0519                 .data('record', record);
0520 
0521             this._addCellsToRowUsingRecord($tr);
0522             return $tr;
0523         },
0524 
0525         /* Adds all cells to given row.
0526         *************************************************************************/
0527         _addCellsToRowUsingRecord: function ($row) {
0528             var record = $row.data('record');
0529             for (var i = 0; i < this._columnList.length; i++) {
0530                 this._createCellForRecordField(record, this._columnList[i])
0531                     .appendTo($row);
0532             }
0533         },
0534 
0535         /* Create a cell for given field.
0536         *************************************************************************/
0537         _createCellForRecordField: function (record, fieldName) {
0538             return $('<td></td>')
0539                 .addClass(this.options.fields[fieldName].listClass)
0540                 .append((this._getDisplayTextForRecordField(record, fieldName)));
0541         },
0542 
0543         /* Adds a list of records to the table.
0544         *************************************************************************/
0545         _addRecordsToTable: function (records) {
0546             var self = this;
0547 
0548             $.each(records, function (index, record) {
0549                 self._addRow(self._createRowFromRecord(record));
0550             });
0551 
0552             self._refreshRowStyles();
0553         },
0554 
0555         /* Adds a single row to the table.
0556         * NOTE: THIS METHOD IS DEPRECATED AND WILL BE REMOVED FROM FEATURE RELEASES.
0557         * USE _addRow METHOD.
0558         *************************************************************************/
0559         _addRowToTable: function ($tableRow, index, isNewRow, animationsEnabled) {
0560             var options = {
0561                 index: this._normalizeNumber(index, 0, this._$tableRows.length, this._$tableRows.length)
0562             };
0563 
0564             if (isNewRow == true) {
0565                 options.isNewRow = true;
0566             }
0567 
0568             if (animationsEnabled == false) {
0569                 options.animationsEnabled = false;
0570             }
0571 
0572             this._addRow($tableRow, options);
0573         },
0574 
0575         /* Adds a single row to the table.
0576         *************************************************************************/
0577         _addRow: function ($row, options) {
0578             //Set defaults
0579             options = $.extend({
0580                 index: this._$tableRows.length,
0581                 isNewRow: false,
0582                 animationsEnabled: true
0583             }, options);
0584 
0585             //Remove 'no data' row if this is first row
0586             if (this._$tableRows.length <= 0) {
0587                 this._removeNoDataRow();
0588             }
0589 
0590             //Add new row to the table according to it's index
0591             options.index = this._normalizeNumber(options.index, 0, this._$tableRows.length, this._$tableRows.length);
0592             if (options.index == this._$tableRows.length) {
0593                 //add as last row
0594                 this._$tableBody.append($row);
0595                 this._$tableRows.push($row);
0596             } else if (options.index == 0) {
0597                 //add as first row
0598                 this._$tableBody.prepend($row);
0599                 this._$tableRows.unshift($row);
0600             } else {
0601                 //insert to specified index
0602                 this._$tableRows[options.index - 1].after($row);
0603                 this._$tableRows.splice(options.index, 0, $row);
0604             }
0605 
0606             this._onRowInserted($row, options.isNewRow);
0607 
0608             //Show animation if needed
0609             if (options.isNewRow) {
0610                 this._refreshRowStyles();
0611                 if (this.options.animationsEnabled && options.animationsEnabled) {
0612                     this._showNewRowAnimation($row);
0613                 }
0614             }
0615         },
0616 
0617         /* Shows created animation for a table row
0618         * TODO: Make this animation cofigurable and changable
0619         *************************************************************************/
0620         _showNewRowAnimation: function ($tableRow) {
0621             var className = 'jtable-row-created';
0622             if (this.options.jqueryuiTheme) {
0623                 className = className + ' ui-state-highlight';
0624             }
0625 
0626             $tableRow.addClass(className, 'slow', '', function () {
0627                 $tableRow.removeClass(className, 5000);
0628             });
0629         },
0630 
0631         /* Removes a row or rows (jQuery selection) from table.
0632         *************************************************************************/
0633         _removeRowsFromTable: function ($rows, reason) {
0634             var self = this;
0635 
0636             //Check if any row specified
0637             if ($rows.length <= 0) {
0638                 return;
0639             }
0640 
0641             //remove from DOM
0642             $rows.addClass('jtable-row-removed').remove();
0643 
0644             //remove from _$tableRows array
0645             $rows.each(function () {
0646                 var index = self._findRowIndex($(this));
0647                 if (index >= 0) {
0648                     self._$tableRows.splice(index, 1);
0649                 }
0650             });
0651 
0652             self._onRowsRemoved($rows, reason);
0653 
0654             //Add 'no data' row if all rows removed from table
0655             if (self._$tableRows.length == 0) {
0656                 self._addNoDataRow();
0657             }
0658 
0659             self._refreshRowStyles();
0660         },
0661 
0662         /* Finds index of a row in table.
0663         *************************************************************************/
0664         _findRowIndex: function ($row) {
0665             return this._findIndexInArray($row, this._$tableRows, function ($row1, $row2) {
0666                 return $row1.data('record') == $row2.data('record');
0667             });
0668         },
0669 
0670         /* Removes all rows in the table and adds 'no data' row.
0671         *************************************************************************/
0672         _removeAllRows: function (reason) {
0673             //If no rows does exists, do nothing
0674             if (this._$tableRows.length <= 0) {
0675                 return;
0676             }
0677 
0678             //Select all rows (to pass it on raising _onRowsRemoved event)
0679             var $rows = this._$tableBody.find('tr.jtable-data-row');
0680 
0681             //Remove all rows from DOM and the _$tableRows array
0682             this._$tableBody.empty();
0683             this._$tableRows = [];
0684 
0685             this._onRowsRemoved($rows, reason);
0686 
0687             //Add 'no data' row since we removed all rows
0688             this._addNoDataRow();
0689         },
0690 
0691         /* Adds "no data available" row to the table.
0692         *************************************************************************/
0693         _addNoDataRow: function () {
0694             if (this._$tableBody.find('>tr.jtable-no-data-row').length > 0) {
0695                 return;
0696             }
0697 
0698             var $tr = $('<tr></tr>')
0699                 .addClass('jtable-no-data-row')
0700                 .appendTo(this._$tableBody);
0701 
0702             var totalColumnCount = this._$table.find('thead th').length;
0703             $('<td></td>')
0704                 .attr('colspan', totalColumnCount)
0705                 .html(this.options.messages.noDataAvailable)
0706                 .appendTo($tr);
0707         },
0708 
0709         /* Removes "no data available" row from the table.
0710         *************************************************************************/
0711         _removeNoDataRow: function () {
0712             this._$tableBody.find('.jtable-no-data-row').remove();
0713         },
0714 
0715         /* Refreshes styles of all rows in the table
0716         *************************************************************************/
0717         _refreshRowStyles: function () {
0718             for (var i = 0; i < this._$tableRows.length; i++) {
0719                 if (i % 2 == 0) {
0720                     this._$tableRows[i].addClass('jtable-row-even');
0721                 } else {
0722                     this._$tableRows[i].removeClass('jtable-row-even');
0723                 }
0724             }
0725         },
0726 
0727         /* RENDERING FIELD VALUES ***********************************************/
0728 
0729         /* Gets text for a field of a record according to it's type.
0730         *************************************************************************/
0731         _getDisplayTextForRecordField: function (record, fieldName) {
0732             var field = this.options.fields[fieldName];
0733             var fieldValue = record[fieldName];
0734 
0735             //if this is a custom field, call display function
0736             if (field.display) {
0737                 return field.display({ record: record });
0738             }
0739 
0740             if (field.type == 'date') {
0741                 return this._getDisplayTextForDateRecordField(field, fieldValue);
0742             } else if (field.type == 'checkbox') {
0743                 return this._getCheckBoxTextForFieldByValue(fieldName, fieldValue);
0744             } else if (field.options) { //combobox or radio button list since there are options.
0745                 var options = this._getOptionsForField(fieldName, {
0746                     record: record,
0747                     value: fieldValue,
0748                     source: 'list',
0749                     dependedValues: this._createDependedValuesUsingRecord(record, field.dependsOn)
0750                 });
0751                 return this._findOptionByValue(options, fieldValue).DisplayText;
0752             } else { //other types
0753                 return fieldValue;
0754             }
0755         },
0756 
0757         /* Creates and returns an object that's properties are depended values of a record.
0758         *************************************************************************/
0759         _createDependedValuesUsingRecord: function (record, dependsOn) {
0760             if (!dependsOn) {
0761                 return {};
0762             }
0763 
0764             var dependedValues = {};
0765             for (var i = 0; i < dependsOn.length; i++) {
0766                 dependedValues[dependsOn[i]] = record[dependsOn[i]];
0767             }
0768 
0769             return dependedValues;
0770         },
0771 
0772         /* Finds an option object by given value.
0773         *************************************************************************/
0774         _findOptionByValue: function (options, value) {
0775             for (var i = 0; i < options.length; i++) {
0776                 if (options[i].Value == value) {
0777                     return options[i];
0778                 }
0779             }
0780 
0781             return {}; //no option found
0782         },
0783 
0784         /* Gets text for a date field.
0785         *************************************************************************/
0786         _getDisplayTextForDateRecordField: function (field, fieldValue) {
0787             if (!fieldValue) {
0788                 return '';
0789             }
0790 
0791             var displayFormat = field.displayFormat || this.options.defaultDateFormat;
0792             var date = this._parseDate(fieldValue);
0793             return $.datepicker.formatDate(displayFormat, date);
0794         },
0795 
0796         /* Gets options for a field according to user preferences.
0797         *************************************************************************/
0798         _getOptionsForField: function (fieldName, funcParams) {
0799             var field = this.options.fields[fieldName];
0800             var optionsSource = field.options;
0801 
0802             if ($.isFunction(optionsSource)) {
0803                 //prepare parameter to the function
0804                 funcParams = $.extend(true, {
0805                     _cacheCleared: false,
0806                     dependedValues: {},
0807                     clearCache: function () {
0808                         this._cacheCleared = true;
0809                     }
0810                 }, funcParams);
0811 
0812                 //call function and get actual options source
0813                 optionsSource = optionsSource(funcParams);
0814             }
0815 
0816             var options;
0817 
0818             //Build options according to it's source type
0819             if (typeof optionsSource == 'string') { //It is an Url to download options
0820                 var cacheKey = 'options_' + fieldName + '_' + optionsSource; //create a unique cache key
0821                 if (funcParams._cacheCleared || (!this._cache[cacheKey])) {
0822                     //if user calls clearCache() or options are not found in the cache, download options
0823                     this._cache[cacheKey] = this._buildOptionsFromArray(this._downloadOptions(fieldName, optionsSource));
0824                     this._sortFieldOptions(this._cache[cacheKey], field.optionsSorting);
0825                 } else {
0826                     //found on cache..
0827                     //if this method (_getOptionsForField) is called to get option for a specific value (on funcParams.source == 'list')
0828                     //and this value is not in cached options, we need to re-download options to get the unfound (probably new) option.
0829                     if (funcParams.value != undefined) {
0830                         var optionForValue = this._findOptionByValue(this._cache[cacheKey], funcParams.value);
0831                         if (optionForValue.DisplayText == undefined) { //this value is not in cached options...
0832                             this._cache[cacheKey] = this._buildOptionsFromArray(this._downloadOptions(fieldName, optionsSource));
0833                             this._sortFieldOptions(this._cache[cacheKey], field.optionsSorting);
0834                         }
0835                     }
0836                 }
0837 
0838                 options = this._cache[cacheKey];
0839             } else if (jQuery.isArray(optionsSource)) { //It is an array of options
0840                 options = this._buildOptionsFromArray(optionsSource);
0841                 this._sortFieldOptions(options, field.optionsSorting);
0842             } else { //It is an object that it's properties are options
0843                 options = this._buildOptionsArrayFromObject(optionsSource);
0844                 this._sortFieldOptions(options, field.optionsSorting);
0845             }
0846 
0847             return options;
0848         },
0849 
0850         /* Download options for a field from server.
0851         *************************************************************************/
0852         _downloadOptions: function (fieldName, url) {
0853             var self = this;
0854             var options = [];
0855 
0856             self._ajax({
0857                 url: url,
0858                 async: false,
0859                 success: function (data) {
0860                     if (data.Result != 'OK') {
0861                         self._showError(data.Message);
0862                         return;
0863                     }
0864 
0865                     options = data.Options;
0866                 },
0867                 error: function () {
0868                     var errMessage = self._formatString(self.options.messages.cannotLoadOptionsFor, fieldName);
0869                     self._showError(errMessage);
0870                 }
0871             });
0872 
0873             return options;
0874         },
0875 
0876         /* Sorts given options according to sorting parameter.
0877         *  sorting can be: 'value', 'value-desc', 'text' or 'text-desc'.
0878         *************************************************************************/
0879         _sortFieldOptions: function (options, sorting) {
0880 
0881             if ((!options) || (!options.length) || (!sorting)) {
0882                 return;
0883             }
0884 
0885             //Determine using value of text
0886             var dataSelector;
0887             if (sorting.indexOf('value') == 0) {
0888                 dataSelector = function (option) {
0889                     return option.Value;
0890                 };
0891             } else { //assume as text
0892                 dataSelector = function (option) {
0893                     return option.DisplayText;
0894                 };
0895             }
0896 
0897             var compareFunc;
0898             if ($.type(dataSelector(options[0])) == 'string') {
0899                 compareFunc = function (option1, option2) {
0900                     return dataSelector(option1).localeCompare(dataSelector(option2));
0901                 };
0902             } else { //asuume as numeric
0903                 compareFunc = function (option1, option2) {
0904                     return dataSelector(option1) - dataSelector(option2);
0905                 };
0906             }
0907 
0908             if (sorting.indexOf('desc') > 0) {
0909                 options.sort(function (a, b) {
0910                     return compareFunc(b, a);
0911                 });
0912             } else { //assume as asc
0913                 options.sort(function (a, b) {
0914                     return compareFunc(a, b);
0915                 });
0916             }
0917         },
0918 
0919         /* Creates an array of options from given object.
0920         *************************************************************************/
0921         _buildOptionsArrayFromObject: function (options) {
0922             var list = [];
0923 
0924             $.each(options, function (propName, propValue) {
0925                 list.push({
0926                     Value: propName,
0927                     DisplayText: propValue
0928                 });
0929             });
0930 
0931             return list;
0932         },
0933 
0934         /* Creates array of options from giving options array.
0935         *************************************************************************/
0936         _buildOptionsFromArray: function (optionsArray) {
0937             var list = [];
0938 
0939             for (var i = 0; i < optionsArray.length; i++) {
0940                 if ($.isPlainObject(optionsArray[i])) {
0941                     list.push(optionsArray[i]);
0942                 } else { //assumed as primitive type (int, string...)
0943                     list.push({
0944                         Value: optionsArray[i],
0945                         DisplayText: optionsArray[i]
0946                     });
0947                 }
0948             }
0949 
0950             return list;
0951         },
0952 
0953         /* Parses given date string to a javascript Date object.
0954         *  Given string must be formatted one of the samples shown below:
0955         *  /Date(1320259705710)/
0956         *  2011-01-01 20:32:42 (YYYY-MM-DD HH:MM:SS)
0957         *  2011-01-01 (YYYY-MM-DD)
0958         *************************************************************************/
0959         _parseDate: function (dateString) {
0960             if (dateString.indexOf('Date') >= 0) { //Format: /Date(1320259705710)/
0961                 return new Date(
0962                     parseInt(dateString.substr(6), 10)
0963                 );
0964             } else if (dateString.length == 10) { //Format: 2011-01-01
0965                 return new Date(
0966                     parseInt(dateString.substr(0, 4), 10),
0967                     parseInt(dateString.substr(5, 2), 10) - 1,
0968                     parseInt(dateString.substr(8, 2), 10)
0969                 );
0970             } else if (dateString.length == 19) { //Format: 2011-01-01 20:32:42
0971                 return new Date(
0972                     parseInt(dateString.substr(0, 4), 10),
0973                     parseInt(dateString.substr(5, 2), 10) - 1,
0974                     parseInt(dateString.substr(8, 2, 10)),
0975                     parseInt(dateString.substr(11, 2), 10),
0976                     parseInt(dateString.substr(14, 2), 10),
0977                     parseInt(dateString.substr(17, 2), 10)
0978                 );
0979             } else {
0980                 this._logWarn('Given date is not properly formatted: ' + dateString);
0981                 return 'format error!';
0982             }
0983         },
0984 
0985         /* TOOL BAR *************************************************************/
0986 
0987         /* Creates the toolbar.
0988         *************************************************************************/
0989         _createToolBar: function () {
0990             this._$toolbarDiv = $('<div />')
0991             .addClass('jtable-toolbar')
0992             .appendTo(this._$titleDiv);
0993 
0994             for (var i = 0; i < this.options.toolbar.items.length; i++) {
0995                 this._addToolBarItem(this.options.toolbar.items[i]);
0996             }
0997         },
0998 
0999         /* Adds a new item to the toolbar.
1000         *************************************************************************/
1001         _addToolBarItem: function (item) {
1002 
1003             //Check if item is valid
1004             if ((item == undefined) || (item.text == undefined && item.icon == undefined)) {
1005                 this._logWarn('Can not add tool bar item since it is not valid!');
1006                 this._logWarn(item);
1007                 return null;
1008             }
1009 
1010             var $toolBarItem = $('<span></span>')
1011                 .addClass('jtable-toolbar-item')
1012                 .appendTo(this._$toolbarDiv);
1013 
1014             this._jqueryuiThemeAddClass($toolBarItem, 'ui-widget ui-state-default ui-corner-all', 'ui-state-hover');
1015 
1016             //cssClass property
1017             if (item.cssClass) {
1018                 $toolBarItem
1019                     .addClass(item.cssClass);
1020             }
1021 
1022             //tooltip property
1023             if (item.tooltip) {
1024                 $toolBarItem
1025                     .attr('title', item.tooltip);
1026             }
1027 
1028             //icon property
1029             if (item.icon) {
1030                 var $icon = $('<span class="jtable-toolbar-item-icon"></span>').appendTo($toolBarItem);
1031                 if (item.icon === true) {
1032                     //do nothing
1033                 } else if ($.type(item.icon === 'string')) {
1034                     $icon.css('background', 'url("' + item.icon + '")');
1035                 }
1036             }
1037 
1038             //text property
1039             if (item.text) {
1040                 $('<span class=""></span>')
1041                     .html(item.text)
1042                     .addClass('jtable-toolbar-item-text').appendTo($toolBarItem);
1043             }
1044 
1045             //click event
1046             if (item.click) {
1047                 $toolBarItem.click(function () {
1048                     item.click();
1049                 });
1050             }
1051 
1052             //set hover animation parameters
1053             var hoverAnimationDuration = undefined;
1054             var hoverAnimationEasing = undefined;
1055             if (this.options.toolbar.hoverAnimation) {
1056                 hoverAnimationDuration = this.options.toolbar.hoverAnimationDuration;
1057                 hoverAnimationEasing = this.options.toolbar.hoverAnimationEasing;
1058             }
1059 
1060             //change class on hover
1061             $toolBarItem.hover(function () {
1062                 $toolBarItem.addClass('jtable-toolbar-item-hover', hoverAnimationDuration, hoverAnimationEasing);
1063             }, function () {
1064                 $toolBarItem.removeClass('jtable-toolbar-item-hover', hoverAnimationDuration, hoverAnimationEasing);
1065             });
1066 
1067             return $toolBarItem;
1068         },
1069 
1070         /* ERROR DIALOG *********************************************************/
1071 
1072         /* Shows error message dialog with given message.
1073         *************************************************************************/
1074         _showError: function (message) {
1075             this._$errorDialogDiv.html(message).dialog('open');
1076         },
1077 
1078         /* BUSY PANEL ***********************************************************/
1079 
1080         /* Shows busy indicator and blocks table UI.
1081         * TODO: Make this cofigurable and changable
1082         *************************************************************************/
1083         _setBusyTimer: null,
1084         _showBusy: function (message, delay) {
1085             var self = this;  //
1086 
1087             //Show a transparent overlay to prevent clicking to the table
1088             self._$busyDiv
1089                 .width(self._$mainContainer.width())
1090                 .height(self._$mainContainer.height())
1091                 .addClass('jtable-busy-panel-background-invisible')
1092                 .show();
1093 
1094             var makeVisible = function () {
1095                 self._$busyDiv.removeClass('jtable-busy-panel-background-invisible');
1096                 self._$busyMessageDiv.html(message).show();
1097             };
1098 
1099             if (delay) {
1100                 if (self._setBusyTimer) {
1101                     return;
1102                 }
1103 
1104                 self._setBusyTimer = setTimeout(makeVisible, delay);
1105             } else {
1106                 makeVisible();
1107             }
1108         },
1109 
1110         /* Hides busy indicator and unblocks table UI.
1111         *************************************************************************/
1112         _hideBusy: function () {
1113             clearTimeout(this._setBusyTimer);
1114             this._setBusyTimer = null;
1115             this._$busyDiv.hide();
1116             this._$busyMessageDiv.html('').hide();
1117         },
1118 
1119         /* Returns true if jTable is busy.
1120         *************************************************************************/
1121         _isBusy: function () {
1122             return this._$busyMessageDiv.is(':visible');
1123         },
1124 
1125         /* Adds jQueryUI class to an item.
1126         *************************************************************************/
1127         _jqueryuiThemeAddClass: function ($elm, className, hoverClassName) {
1128             if (!this.options.jqueryuiTheme) {
1129                 return;
1130             }
1131 
1132             $elm.addClass(className);
1133 
1134             if (hoverClassName) {
1135                 $elm.hover(function () {
1136                     $elm.addClass(hoverClassName);
1137                 }, function () {
1138                     $elm.removeClass(hoverClassName);
1139                 });
1140             }
1141         },
1142 
1143         /* COMMON METHODS *******************************************************/
1144 
1145         /* Performs an AJAX call to specified URL.
1146         * THIS METHOD IS DEPRECATED AND WILL BE REMOVED FROM FEATURE RELEASES.
1147         * USE _ajax METHOD.
1148         *************************************************************************/
1149         _performAjaxCall: function (url, postData, async, success, error) {
1150             this._ajax({
1151                 url: url,
1152                 data: postData,
1153                 async: async,
1154                 success: success,
1155                 error: error
1156             });
1157         },
1158 
1159         _unAuthorizedRequestHandler: function() {
1160             if (this.options.unAuthorizedRequestRedirectUrl) {
1161                 location.href = this.options.unAuthorizedRequestRedirectUrl;
1162             } else {
1163                 location.reload(true);
1164             }
1165         },
1166 
1167         /* This method is used to perform AJAX calls in jTable instead of direct
1168         * usage of jQuery.ajax method.
1169         *************************************************************************/
1170         _ajax: function (options) {
1171             var self = this;
1172 
1173             //Handlers for HTTP status codes
1174             var opts = {
1175                 statusCode: {
1176                     401: function () { //Unauthorized
1177                         self._unAuthorizedRequestHandler();
1178                     }
1179                 }
1180             };
1181 
1182             opts = $.extend(opts, this.options.ajaxSettings, options);
1183 
1184             //Override success
1185             opts.success = function (data) {
1186                 //Checking for Authorization error
1187                 if (data && data.UnAuthorizedRequest == true) {
1188                     self._unAuthorizedRequestHandler();
1189                 }
1190 
1191                 if (options.success) {
1192                     options.success(data);
1193                 }
1194             };
1195 
1196             //Override error
1197             opts.error = function (jqXHR, textStatus, errorThrown) {
1198                 if (unloadingPage) {
1199                     jqXHR.abort();
1200                     return;
1201                 }
1202                 
1203                 if (options.error) {
1204                     options.error(arguments);
1205                 }
1206             };
1207 
1208             //Override complete
1209             opts.complete = function () {
1210                 if (options.complete) {
1211                     options.complete();
1212                 }
1213             };
1214 
1215             $.ajax(opts);
1216         },
1217 
1218         /* Gets value of key field of a record.
1219         *************************************************************************/
1220         _getKeyValueOfRecord: function (record) {
1221             return record[this._keyField];
1222         },
1223 
1224         /************************************************************************
1225         * COOKIE                                                                *
1226         *************************************************************************/
1227 
1228         /* Sets a cookie with given key.
1229         *************************************************************************/
1230         _setCookie: function (key, value) {
1231             key = this._cookieKeyPrefix + key;
1232 
1233             var expireDate = new Date();
1234             expireDate.setDate(expireDate.getDate() + 30);
1235             document.cookie = encodeURIComponent(key) + '=' + encodeURIComponent(value) + "; expires=" + expireDate.toUTCString();
1236         },
1237 
1238         /* Gets a cookie with given key.
1239         *************************************************************************/
1240         _getCookie: function (key) {
1241             key = this._cookieKeyPrefix + key;
1242 
1243             var equalities = document.cookie.split('; ');
1244             for (var i = 0; i < equalities.length; i++) {
1245                 if (!equalities[i]) {
1246                     continue;
1247                 }
1248 
1249                 var splitted = equalities[i].split('=');
1250                 if (splitted.length != 2) {
1251                     continue;
1252                 }
1253 
1254                 if (decodeURIComponent(splitted[0]) === key) {
1255                     return decodeURIComponent(splitted[1] || '');
1256                 }
1257             }
1258 
1259             return null;
1260         },
1261 
1262         /* Generates a hash key to be prefix for all cookies for this jtable instance.
1263         *************************************************************************/
1264         _generateCookieKeyPrefix: function () {
1265 
1266             var simpleHash = function (value) {
1267                 var hash = 0;
1268                 if (value.length == 0) {
1269                     return hash;
1270                 }
1271 
1272                 for (var i = 0; i < value.length; i++) {
1273                     var ch = value.charCodeAt(i);
1274                     hash = ((hash << 5) - hash) + ch;
1275                     hash = hash & hash;
1276                 }
1277 
1278                 return hash;
1279             };
1280 
1281             var strToHash = '';
1282             if (this.options.tableId) {
1283                 strToHash = strToHash + this.options.tableId + '#';
1284             }
1285 
1286             strToHash = strToHash + this._columnList.join('$') + '#c' + this._$table.find('thead th').length;
1287             return 'jtable#' + simpleHash(strToHash);
1288         },
1289 
1290         /************************************************************************
1291         * EVENT RAISING METHODS                                                 *
1292         *************************************************************************/
1293 
1294         _onLoadingRecords: function () {
1295             this._trigger("loadingRecords", null, {});
1296         },
1297 
1298         _onRecordsLoaded: function (data) {
1299             this._trigger("recordsLoaded", null, { records: data.Records, serverResponse: data });
1300         },
1301 
1302         _onRowInserted: function ($row, isNewRow) {
1303             this._trigger("rowInserted", null, { row: $row, record: $row.data('record'), isNewRow: isNewRow });
1304         },
1305 
1306         _onRowsRemoved: function ($rows, reason) {
1307             this._trigger("rowsRemoved", null, { rows: $rows, reason: reason });
1308         },
1309 
1310         _onCloseRequested: function () {
1311             this._trigger("closeRequested", null, {});
1312         }
1313 
1314     });
1315 
1316 }(jQuery));
1317 
1318 
1319 /************************************************************************
1320 * Some UTULITY methods used by jTable                                   *
1321 *************************************************************************/
1322 (function ($) {
1323 
1324     $.extend(true, $.hik.jtable.prototype, {
1325 
1326         /* Gets property value of an object recursively.
1327         *************************************************************************/
1328         _getPropertyOfObject: function (obj, propName) {
1329             if (propName.indexOf('.') < 0) {
1330                 return obj[propName];
1331             } else {
1332                 var preDot = propName.substring(0, propName.indexOf('.'));
1333                 var postDot = propName.substring(propName.indexOf('.') + 1);
1334                 return this._getPropertyOfObject(obj[preDot], postDot);
1335             }
1336         },
1337 
1338         /* Sets property value of an object recursively.
1339         *************************************************************************/
1340         _setPropertyOfObject: function (obj, propName, value) {
1341             if (propName.indexOf('.') < 0) {
1342                 obj[propName] = value;
1343             } else {
1344                 var preDot = propName.substring(0, propName.indexOf('.'));
1345                 var postDot = propName.substring(propName.indexOf('.') + 1);
1346                 this._setPropertyOfObject(obj[preDot], postDot, value);
1347             }
1348         },
1349 
1350         /* Inserts a value to an array if it does not exists in the array.
1351         *************************************************************************/
1352         _insertToArrayIfDoesNotExists: function (array, value) {
1353             if ($.inArray(value, array) < 0) {
1354                 array.push(value);
1355             }
1356         },
1357 
1358         /* Finds index of an element in an array according to given comparision function
1359         *************************************************************************/
1360         _findIndexInArray: function (value, array, compareFunc) {
1361 
1362             //If not defined, use default comparision
1363             if (!compareFunc) {
1364                 compareFunc = function (a, b) {
1365                     return a == b;
1366                 };
1367             }
1368 
1369             for (var i = 0; i < array.length; i++) {
1370                 if (compareFunc(value, array[i])) {
1371                     return i;
1372                 }
1373             }
1374 
1375             return -1;
1376         },
1377 
1378         /* Normalizes a number between given bounds or sets to a defaultValue
1379         *  if it is undefined
1380         *************************************************************************/
1381         _normalizeNumber: function (number, min, max, defaultValue) {
1382             if (number == undefined || number == null || isNaN(number)) {
1383                 return defaultValue;
1384             }
1385 
1386             if (number < min) {
1387                 return min;
1388             }
1389 
1390             if (number > max) {
1391                 return max;
1392             }
1393 
1394             return number;
1395         },
1396 
1397         /* Formats a string just like string.format in c#.
1398         *  Example:
1399         *  _formatString('Hello {0}','Halil') = 'Hello Halil'
1400         *************************************************************************/
1401         _formatString: function () {
1402             if (arguments.length == 0) {
1403                 return null;
1404             }
1405 
1406             var str = arguments[0];
1407             for (var i = 1; i < arguments.length; i++) {
1408                 var placeHolder = '{' + (i - 1) + '}';
1409                 str = str.replace(placeHolder, arguments[i]);
1410             }
1411 
1412             return str;
1413         },
1414 
1415         /* Checks if given object is a jQuery Deferred object.
1416          */
1417         _isDeferredObject: function (obj) {
1418             return obj.then && obj.done && obj.fail;
1419         },
1420 
1421         //Logging methods ////////////////////////////////////////////////////////
1422 
1423         _logDebug: function (text) {
1424             if (!window.console) {
1425                 return;
1426             }
1427 
1428             console.log('jTable DEBUG: ' + text);
1429         },
1430 
1431         _logInfo: function (text) {
1432             if (!window.console) {
1433                 return;
1434             }
1435 
1436             console.log('jTable INFO: ' + text);
1437         },
1438 
1439         _logWarn: function (text) {
1440             if (!window.console) {
1441                 return;
1442             }
1443 
1444             console.log('jTable WARNING: ' + text);
1445         },
1446 
1447         _logError: function (text) {
1448             if (!window.console) {
1449                 return;
1450             }
1451 
1452             console.log('jTable ERROR: ' + text);
1453         }
1454 
1455     });
1456 
1457     /* Fix for array.indexOf method in IE7.
1458      * This code is taken from http://www.tutorialspoint.com/javascript/array_indexof.htm */
1459     if (!Array.prototype.indexOf) {
1460         Array.prototype.indexOf = function (elt) {
1461             var len = this.length;
1462             var from = Number(arguments[1]) || 0;
1463             from = (from < 0)
1464                  ? Math.ceil(from)
1465                  : Math.floor(from);
1466             if (from < 0)
1467                 from += len;
1468             for (; from < len; from++) {
1469                 if (from in this &&
1470                     this[from] === elt)
1471                     return from;
1472             }
1473             return -1;
1474         };
1475     }
1476 
1477 })(jQuery);
1478 
1479 
1480 /************************************************************************
1481 * FORMS extension for jTable (base for edit/create forms)               *
1482 *************************************************************************/
1483 (function ($) {
1484 
1485     $.extend(true, $.hik.jtable.prototype, {
1486 
1487         /************************************************************************
1488         * PRIVATE METHODS                                                       *
1489         *************************************************************************/
1490 
1491         /* Submits a form asynchronously using AJAX.
1492         *  This method is needed, since form submitting logic can be overrided
1493         *  by extensions.
1494         *************************************************************************/
1495         _submitFormUsingAjax: function (url, formData, success, error) {
1496             this._ajax({
1497                 url: url,
1498                 data: formData,
1499                 success: success,
1500                 error: error
1501             });
1502         },
1503 
1504         /* Creates label for an input element.
1505         *************************************************************************/
1506         _createInputLabelForRecordField: function (fieldName) {
1507             //TODO: May create label tag instead of a div.
1508             return $('<div />')
1509                 .addClass('jtable-input-label')
1510                 .html(this.options.fields[fieldName].inputTitle || this.options.fields[fieldName].title);
1511         },
1512 
1513         /* Creates an input element according to field type.
1514         *************************************************************************/
1515         _createInputForRecordField: function (funcParams) {
1516             var fieldName = funcParams.fieldName,
1517                 value = funcParams.value,
1518                 record = funcParams.record,
1519                 formType = funcParams.formType,
1520                 form = funcParams.form;
1521 
1522             //Get the field
1523             var field = this.options.fields[fieldName];
1524 
1525             //If value if not supplied, use defaultValue of the field
1526             if (value == undefined || value == null) {
1527                 value = field.defaultValue;
1528             }
1529 
1530             //Use custom function if supplied
1531             if (field.input) {
1532                 var $input = $(field.input({
1533                     value: value,
1534                     record: record,
1535                     formType: formType,
1536                     form: form
1537                 }));
1538 
1539                 //Add id attribute if does not exists
1540                 if (!$input.attr('id')) {
1541                     $input.attr('id', 'Edit-' + fieldName);
1542                 }
1543 
1544                 //Wrap input element with div
1545                 return $('<div />')
1546                     .addClass('jtable-input jtable-custom-input')
1547                     .append($input);
1548             }
1549 
1550             //Create input according to field type
1551             if (field.type == 'date') {
1552                 return this._createDateInputForField(field, fieldName, value);
1553             } else if (field.type == 'textarea') {
1554                 return this._createTextAreaForField(field, fieldName, value);
1555             } else if (field.type == 'password') {
1556                 return this._createPasswordInputForField(field, fieldName, value);
1557             } else if (field.type == 'checkbox') {
1558                 return this._createCheckboxForField(field, fieldName, value);
1559             } else if (field.options) {
1560                 if (field.type == 'radiobutton') {
1561                     return this._createRadioButtonListForField(field, fieldName, value, record, formType);
1562                 } else {
1563                     return this._createDropDownListForField(field, fieldName, value, record, formType, form);
1564                 }
1565             } else {
1566                 return this._createTextInputForField(field, fieldName, value);
1567             }
1568         },
1569 
1570         //Creates a hidden input element with given name and value.
1571         _createInputForHidden: function (fieldName, value) {
1572             if (value == undefined) {
1573                 value = "";
1574             }
1575 
1576             return $('<input type="hidden" name="' + fieldName + '" id="Edit-' + fieldName + '"></input>')
1577                 .val(value);
1578         },
1579 
1580         /* Creates a date input for a field.
1581         *************************************************************************/
1582         _createDateInputForField: function (field, fieldName, value) {
1583             var $input = $('<input class="' + field.inputClass + '" id="Edit-' + fieldName + '" type="text" name="' + fieldName + '"></input>');
1584             if(value != undefined) {
1585                 $input.val(value);
1586             }
1587             
1588             var displayFormat = field.displayFormat || this.options.defaultDateFormat;
1589             $input.datepicker({ dateFormat: displayFormat });
1590             return $('<div />')
1591                 .addClass('jtable-input jtable-date-input')
1592                 .append($input);
1593         },
1594 
1595         /* Creates a textarea element for a field.
1596         *************************************************************************/
1597         _createTextAreaForField: function (field, fieldName, value) {
1598             var $textArea = $('<textarea class="' + field.inputClass + '" id="Edit-' + fieldName + '" name="' + fieldName + '"></textarea>');
1599             if (value != undefined) {
1600                 $textArea.val(value);
1601             }
1602             
1603             return $('<div />')
1604                 .addClass('jtable-input jtable-textarea-input')
1605                 .append($textArea);
1606         },
1607 
1608         /* Creates a standart textbox for a field.
1609         *************************************************************************/
1610         _createTextInputForField: function (field, fieldName, value) {
1611             var $input = $('<input class="' + field.inputClass + '" id="Edit-' + fieldName + '" type="text" name="' + fieldName + '"></input>');
1612             if (value != undefined) {
1613                 $input.val(value);
1614             }
1615             
1616             return $('<div />')
1617                 .addClass('jtable-input jtable-text-input')
1618                 .append($input);
1619         },
1620 
1621         /* Creates a password input for a field.
1622         *************************************************************************/
1623         _createPasswordInputForField: function (field, fieldName, value) {
1624             var $input = $('<input class="' + field.inputClass + '" id="Edit-' + fieldName + '" type="password" name="' + fieldName + '"></input>');
1625             if (value != undefined) {
1626                 $input.val(value);
1627             }
1628             
1629             return $('<div />')
1630                 .addClass('jtable-input jtable-password-input')
1631                 .append($input);
1632         },
1633 
1634         /* Creates a checkboxfor a field.
1635         *************************************************************************/
1636         _createCheckboxForField: function (field, fieldName, value) {
1637             var self = this;
1638 
1639             //If value is undefined, get unchecked state's value
1640             if (value == undefined) {
1641                 value = self._getCheckBoxPropertiesForFieldByState(fieldName, false).Value;
1642             }
1643 
1644             //Create a container div
1645             var $containerDiv = $('<div />')
1646                 .addClass('jtable-input jtable-checkbox-input');
1647 
1648             //Create checkbox and check if needed
1649             var $checkBox = $('<input class="' + field.inputClass + '" id="Edit-' + fieldName + '" type="checkbox" name="' + fieldName + '" />')
1650                 .appendTo($containerDiv);
1651             if (value != undefined) {
1652                 $checkBox.val(value);
1653             }
1654 
1655             //Create display text of checkbox for current state
1656             var $textSpan = $('<span>' + (field.formText || self._getCheckBoxTextForFieldByValue(fieldName, value)) + '</span>')
1657                 .appendTo($containerDiv);
1658 
1659             //Check the checkbox if it's value is checked-value
1660             if (self._getIsCheckBoxSelectedForFieldByValue(fieldName, value)) {
1661                 $checkBox.attr('checked', 'checked');
1662             }
1663 
1664             //This method sets checkbox's value and text according to state of the checkbox
1665             var refreshCheckBoxValueAndText = function () {
1666                 var checkboxProps = self._getCheckBoxPropertiesForFieldByState(fieldName, $checkBox.is(':checked'));
1667                 $checkBox.attr('value', checkboxProps.Value);
1668                 $textSpan.html(field.formText || checkboxProps.DisplayText);
1669             };
1670 
1671             //Register to click event to change display text when state of checkbox is changed.
1672             $checkBox.click(function () {
1673                 refreshCheckBoxValueAndText();
1674             });
1675 
1676             //Change checkbox state when clicked to text
1677             if (field.setOnTextClick != false) {
1678                 $textSpan
1679                     .addClass('jtable-option-text-clickable')
1680                     .click(function () {
1681                         if ($checkBox.is(':checked')) {
1682                             $checkBox.attr('checked', false);
1683                         } else {
1684                             $checkBox.attr('checked', true);
1685                         }
1686 
1687                         refreshCheckBoxValueAndText();
1688                     });
1689             }
1690 
1691             return $containerDiv;
1692         },
1693 
1694         /* Creates a drop down list (combobox) input element for a field.
1695         *************************************************************************/
1696         _createDropDownListForField: function (field, fieldName, value, record, source, form) {
1697 
1698             //Create a container div
1699             var $containerDiv = $('<div />')
1700                 .addClass('jtable-input jtable-dropdown-input');
1701 
1702             //Create select element
1703             var $select = $('<select class="' + field.inputClass + '" id="Edit-' + fieldName + '" name="' + fieldName + '"></select>')
1704                 .appendTo($containerDiv);
1705 
1706             //add options
1707             var options = this._getOptionsForField(fieldName, {
1708                 record: record,
1709                 source: source,
1710                 form: form,
1711                 dependedValues: this._createDependedValuesUsingForm(form, field.dependsOn)
1712             });
1713 
1714             this._fillDropDownListWithOptions($select, options, value);
1715 
1716             return $containerDiv;
1717         },
1718         
1719         /* Fills a dropdown list with given options.
1720         *************************************************************************/
1721         _fillDropDownListWithOptions: function ($select, options, value) {
1722             $select.empty();
1723             for (var i = 0; i < options.length; i++) {
1724                 $('<option' + (options[i].Value == value ? ' selected="selected"' : '') + '>' + options[i].DisplayText + '</option>')
1725                     .val(options[i].Value)
1726                     .appendTo($select);
1727             }
1728         },
1729 
1730         /* Creates depended values object from given form.
1731         *************************************************************************/
1732         _createDependedValuesUsingForm: function ($form, dependsOn) {
1733             if (!dependsOn) {
1734                 return {};
1735             }
1736 
1737             var dependedValues = {};
1738 
1739             for (var i = 0; i < dependsOn.length; i++) {
1740                 var dependedField = dependsOn[i];
1741 
1742                 var $dependsOn = $form.find('select[name=' + dependedField + ']');
1743                 if ($dependsOn.length <= 0) {
1744                     continue;
1745                 }
1746 
1747                 dependedValues[dependedField] = $dependsOn.val();
1748             }
1749 
1750 
1751             return dependedValues;
1752         },
1753 
1754         /* Creates a radio button list for a field.
1755         *************************************************************************/
1756         _createRadioButtonListForField: function (field, fieldName, value, record, source) {
1757             var $containerDiv = $('<div />')
1758                 .addClass('jtable-input jtable-radiobuttonlist-input');
1759 
1760             var options = this._getOptionsForField(fieldName, {
1761                 record: record,
1762                 source: source
1763             });
1764 
1765             $.each(options, function(i, option) {
1766                 var $radioButtonDiv = $('<div class=""></div>')
1767                     .addClass('jtable-radio-input')
1768                     .appendTo($containerDiv);
1769 
1770                 var $radioButton = $('<input type="radio" id="Edit-' + fieldName + '-' + i + '" class="' + field.inputClass + '" name="' + fieldName + '"' + ((option.Value == (value + '')) ? ' checked="true"' : '') + ' />')
1771                     .val(option.Value)
1772                     .appendTo($radioButtonDiv);
1773 
1774                 var $textSpan = $('<span></span>')
1775                     .html(option.DisplayText)
1776                     .appendTo($radioButtonDiv);
1777 
1778                 if (field.setOnTextClick != false) {
1779                     $textSpan
1780                         .addClass('jtable-option-text-clickable')
1781                         .click(function () {
1782                             if (!$radioButton.is(':checked')) {
1783                                 $radioButton.attr('checked', true);
1784                             }
1785                         });
1786                 }
1787             });
1788 
1789             return $containerDiv;
1790         },
1791 
1792         /* Gets display text for a checkbox field.
1793         *************************************************************************/
1794         _getCheckBoxTextForFieldByValue: function (fieldName, value) {
1795             return this.options.fields[fieldName].values[value];
1796         },
1797 
1798         /* Returns true if given field's value must be checked state.
1799         *************************************************************************/
1800         _getIsCheckBoxSelectedForFieldByValue: function (fieldName, value) {
1801             return (this._createCheckBoxStateArrayForFieldWithCaching(fieldName)[1].Value.toString() == value.toString());
1802         },
1803 
1804         /* Gets an object for a checkbox field that has Value and DisplayText
1805         *  properties.
1806         *************************************************************************/
1807         _getCheckBoxPropertiesForFieldByState: function (fieldName, checked) {
1808             return this._createCheckBoxStateArrayForFieldWithCaching(fieldName)[(checked ? 1 : 0)];
1809         },
1810 
1811         /* Calls _createCheckBoxStateArrayForField with caching.
1812         *************************************************************************/
1813         _createCheckBoxStateArrayForFieldWithCaching: function (fieldName) {
1814             var cacheKey = 'checkbox_' + fieldName;
1815             if (!this._cache[cacheKey]) {
1816 
1817                 this._cache[cacheKey] = this._createCheckBoxStateArrayForField(fieldName);
1818             }
1819 
1820             return this._cache[cacheKey];
1821         },
1822 
1823         /* Creates a two element array of objects for states of a checkbox field.
1824         *  First element for unchecked state, second for checked state.
1825         *  Each object has two properties: Value and DisplayText
1826         *************************************************************************/
1827         _createCheckBoxStateArrayForField: function (fieldName) {
1828             var stateArray = [];
1829             var currentIndex = 0;
1830             $.each(this.options.fields[fieldName].values, function (propName, propValue) {
1831                 if (currentIndex++ < 2) {
1832                     stateArray.push({ 'Value': propName, 'DisplayText': propValue });
1833                 }
1834             });
1835 
1836             return stateArray;
1837         },
1838 
1839         /* Searches a form for dependend dropdowns and makes them cascaded.
1840         */
1841         _makeCascadeDropDowns: function ($form, record, source) {
1842             var self = this;
1843 
1844             $form.find('select') //for each combobox
1845                 .each(function () {
1846                     var $thisDropdown = $(this);
1847 
1848                     //get field name
1849                     var fieldName = $thisDropdown.attr('name');
1850                     if (!fieldName) {
1851                         return;
1852                     }
1853 
1854                     var field = self.options.fields[fieldName];
1855                     
1856                     //check if this combobox depends on others
1857                     if (!field.dependsOn) {
1858                         return;
1859                     }
1860 
1861                     //for each dependency
1862                     $.each(field.dependsOn, function (index, dependsOnField) {
1863                         //find the depended combobox
1864                         var $dependsOnDropdown = $form.find('select[name=' + dependsOnField + ']');
1865                         //when depended combobox changes
1866                         $dependsOnDropdown.change(function () {
1867 
1868                             //Refresh options
1869                             var funcParams = {
1870                                 record: record,
1871                                 source: source,
1872                                 form: $form,
1873                                 dependedValues: {}
1874                             };
1875                             funcParams.dependedValues = self._createDependedValuesUsingForm($form, field.dependsOn);
1876                             var options = self._getOptionsForField(fieldName, funcParams);
1877 
1878                             //Fill combobox with new options
1879                             self._fillDropDownListWithOptions($thisDropdown, options, undefined);
1880 
1881                             //Thigger change event to refresh multi cascade dropdowns.
1882                             $thisDropdown.change();
1883                         });
1884                     });
1885                 });
1886         },
1887 
1888         /* Updates values of a record from given form
1889         *************************************************************************/
1890         _updateRecordValuesFromForm: function (record, $form) {
1891             for (var i = 0; i < this._fieldList.length; i++) {
1892                 var fieldName = this._fieldList[i];
1893                 var field = this.options.fields[fieldName];
1894 
1895                 //Do not update non-editable fields
1896                 if (field.edit == false) {
1897                     continue;
1898                 }
1899 
1900                 //Get field name and the input element of this field in the form
1901                 var $inputElement = $form.find('[name="' + fieldName + '"]');
1902                 if ($inputElement.length <= 0) {
1903                     continue;
1904                 }
1905 
1906                 //Update field in record according to it's type
1907                 if (field.type == 'date') {
1908                     var dateVal = $inputElement.val();
1909                     if (dateVal) {
1910                         var displayFormat = field.displayFormat || this.options.defaultDateFormat;
1911                         try {
1912                             var date = $.datepicker.parseDate(displayFormat, dateVal);
1913                             record[fieldName] = '/Date(' + date.getTime() + ')/';
1914                         } catch (e) {
1915                             //TODO: Handle incorrect/different date formats
1916                             this._logWarn('Date format is incorrect for field ' + fieldName + ': ' + dateVal);
1917                             record[fieldName] = undefined;
1918                         }
1919                     } else {
1920                         this._logDebug('Date is empty for ' + fieldName);
1921                         record[fieldName] = undefined; //TODO: undefined, null or empty string?
1922                     }
1923                 } else if (field.options && field.type == 'radiobutton') {
1924                     var $checkedElement = $inputElement.filter(':checked');
1925                     if ($checkedElement.length) {
1926                         record[fieldName] = $checkedElement.val();
1927                     } else {
1928                         record[fieldName] = undefined;
1929                     }
1930                 } else {
1931                     record[fieldName] = $inputElement.val();
1932                 }
1933             }
1934         },
1935 
1936         /* Sets enabled/disabled state of a dialog button.
1937         *************************************************************************/
1938         _setEnabledOfDialogButton: function ($button, enabled, buttonText) {
1939             if (!$button) {
1940                 return;
1941             }
1942 
1943             if (enabled != false) {
1944                 $button
1945                     .removeAttr('disabled')
1946                     .removeClass('ui-state-disabled');
1947             } else {
1948                 $button
1949                     .attr('disabled', 'disabled')
1950                     .addClass('ui-state-disabled');
1951             }
1952 
1953             if (buttonText) {
1954                 $button
1955                     .find('span')
1956                     .text(buttonText);
1957             }
1958         }
1959 
1960     });
1961 
1962 })(jQuery);
1963 
1964 
1965 /************************************************************************
1966 * CREATE RECORD extension for jTable                                    *
1967 *************************************************************************/
1968 (function ($) {
1969 
1970     //Reference to base object members
1971     var base = {
1972         _create: $.hik.jtable.prototype._create
1973     };
1974 
1975     //extension members
1976     $.extend(true, $.hik.jtable.prototype, {
1977 
1978         /************************************************************************
1979         * DEFAULT OPTIONS / EVENTS                                              *
1980         *************************************************************************/
1981         options: {
1982 
1983             //Events
1984             recordAdded: function (event, data) { },
1985 
1986             //Localization
1987             messages: {
1988                 addNewRecord: 'Add new record'
1989             }
1990         },
1991 
1992         /************************************************************************
1993         * PRIVATE FIELDS                                                        *
1994         *************************************************************************/
1995 
1996         _$addRecordDiv: null, //Reference to the adding new record dialog div (jQuery object)
1997 
1998         /************************************************************************
1999         * CONSTRUCTOR                                                           *
2000         *************************************************************************/
2001 
2002         /* Overrides base method to do create-specific constructions.
2003         *************************************************************************/
2004         _create: function () {
2005             base._create.apply(this, arguments);
2006 
2007             if (!this.options.actions.createAction) {
2008                 return;
2009             }
2010 
2011             this._createAddRecordDialogDiv();
2012         },
2013 
2014         /* Creates and prepares add new record dialog div
2015         *************************************************************************/
2016         _createAddRecordDialogDiv: function () {
2017             var self = this;
2018 
2019             //Create a div for dialog and add to container element
2020             self._$addRecordDiv = $('<div />')
2021                 .appendTo(self._$mainContainer);
2022 
2023             //Prepare dialog
2024             self._$addRecordDiv.dialog({
2025                 autoOpen: false,
2026                 show: self.options.dialogShowEffect,
2027                 hide: self.options.dialogHideEffect,
2028                 width: 'auto',
2029                 minWidth: '300',
2030                 modal: true,
2031                 title: self.options.messages.addNewRecord,
2032                 buttons:
2033                         [{ //Cancel button
2034                             text: self.options.messages.cancel,
2035                             click: function () {
2036                                 self._$addRecordDiv.dialog('close');
2037                             }
2038                         }, { //Save button
2039                             id: 'AddRecordDialogSaveButton',
2040                             text: self.options.messages.save,
2041                             click: function () {
2042                                 self._onSaveClickedOnCreateForm();
2043                             }
2044                         }],
2045                 close: function () {
2046                     var $addRecordForm = self._$addRecordDiv.find('form').first();
2047                     var $saveButton = self._$addRecordDiv.parent().find('#AddRecordDialogSaveButton');
2048                     self._trigger("formClosed", null, { form: $addRecordForm, formType: 'create' });
2049                     self._setEnabledOfDialogButton($saveButton, true, self.options.messages.save);
2050                     $addRecordForm.remove();
2051                 }
2052             });
2053 
2054             if (self.options.addRecordButton) {
2055                 //If user supplied a button, bind the click event to show dialog form
2056                 self.options.addRecordButton.click(function (e) {
2057                     e.preventDefault();
2058                     self._showAddRecordForm();
2059                 });
2060             } else {
2061                 //If user did not supplied a button, create a 'add record button' toolbar item.
2062                 self._addToolBarItem({
2063                     icon: true,
2064                     cssClass: 'jtable-toolbar-item-add-record',
2065                     text: self.options.messages.addNewRecord,
2066                     click: function () {
2067                         self._showAddRecordForm();
2068                     }
2069                 });
2070             }
2071         },
2072 
2073         _onSaveClickedOnCreateForm: function () {
2074             var self = this;
2075 
2076             var $saveButton = self._$addRecordDiv.parent().find('#AddRecordDialogSaveButton');
2077             var $addRecordForm = self._$addRecordDiv.find('form');
2078 
2079             if (self._trigger("formSubmitting", null, { form: $addRecordForm, formType: 'create' }) != false) {
2080                 self._setEnabledOfDialogButton($saveButton, false, self.options.messages.saving);
2081                 self._saveAddRecordForm($addRecordForm, $saveButton);
2082             }
2083         },
2084 
2085         /************************************************************************
2086         * PUBLIC METHODS                                                        *
2087         *************************************************************************/
2088 
2089         /* Shows add new record dialog form.
2090         *************************************************************************/
2091         showCreateForm: function () {
2092             this._showAddRecordForm();
2093         },
2094 
2095         /* Adds a new record to the table (optionally to the server also)
2096         *************************************************************************/
2097         addRecord: function (options) {
2098             var self = this;
2099             options = $.extend({
2100                 clientOnly: false,
2101                 animationsEnabled: self.options.animationsEnabled,
2102                 success: function () { },
2103                 error: function () { }
2104             }, options);
2105 
2106             if (!options.record) {
2107                 self._logWarn('options parameter in addRecord method must contain a record property.');
2108                 return;
2109             }
2110 
2111             if (options.clientOnly) {
2112                 self._addRow(
2113                     self._createRowFromRecord(options.record), {
2114                         isNewRow: true,
2115                         animationsEnabled: options.animationsEnabled
2116                     });
2117 
2118                 options.success();
2119                 return;
2120             }
2121 
2122             var completeAddRecord = function (data) {
2123                 if (data.Result != 'OK') {
2124                     self._showError(data.Message);
2125                     options.error(data);
2126                     return;
2127                 }
2128 
2129                 if (!data.Record) {
2130                     self._logError('Server must return the created Record object.');
2131                     options.error(data);
2132                     return;
2133                 }
2134 
2135                 self._onRecordAdded(data);
2136                 self._addRow(
2137                     self._createRowFromRecord(data.Record), {
2138                         isNewRow: true,
2139                         animationsEnabled: options.animationsEnabled
2140                     });
2141 
2142                 options.success(data);
2143             };
2144 
2145             //createAction may be a function, check if it is
2146             if (!options.url && $.isFunction(self.options.actions.createAction)) {
2147 
2148                 //Execute the function
2149                 var funcResult = self.options.actions.createAction($.param(options.record));
2150 
2151                 //Check if result is a jQuery Deferred object
2152                 if (self._isDeferredObject(funcResult)) {
2153                     //Wait promise
2154                     funcResult.done(function (data) {
2155                         completeAddRecord(data);
2156                     }).fail(function () {
2157                         self._showError(self.options.messages.serverCommunicationError);
2158                         options.error();
2159                     });
2160                 } else { //assume it returned the creation result
2161                     completeAddRecord(funcResult);
2162                 }
2163 
2164             } else { //Assume it's a URL string
2165 
2166                 //Make an Ajax call to create record
2167                 self._submitFormUsingAjax(
2168                     options.url || self.options.actions.createAction,
2169                     $.param(options.record),
2170                     function (data) {
2171                         completeAddRecord(data);
2172                     },
2173                     function () {
2174                         self._showError(self.options.messages.serverCommunicationError);
2175                         options.error();
2176                     });
2177 
2178             }
2179         },
2180 
2181         /************************************************************************
2182         * PRIVATE METHODS                                                       *
2183         *************************************************************************/
2184 
2185         /* Shows add new record dialog form.
2186         *************************************************************************/
2187         _showAddRecordForm: function () {
2188             var self = this;
2189 
2190             //Create add new record form
2191             var $addRecordForm = $('<form id="jtable-create-form" class="jtable-dialog-form jtable-create-form"></form>');
2192 
2193             //Create input elements
2194             for (var i = 0; i < self._fieldList.length; i++) {
2195 
2196                 var fieldName = self._fieldList[i];
2197                 var field = self.options.fields[fieldName];
2198 
2199                 //Do not create input for fields that is key and not specially marked as creatable
2200                 if (field.key == true && field.create != true) {
2201                     continue;
2202                 }
2203 
2204                 //Do not create input for fields that are not creatable
2205                 if (field.create == false) {
2206                     continue;
2207                 }
2208 
2209                 if (field.type == 'hidden') {
2210                     $addRecordForm.append(self._createInputForHidden(fieldName, field.defaultValue));
2211                     continue;
2212                 }
2213 
2214                 //Create a container div for this input field and add to form
2215                 var $fieldContainer = $('<div />')
2216                     .addClass('jtable-input-field-container')
2217                     .appendTo($addRecordForm);
2218 
2219                 //Create a label for input
2220                 $fieldContainer.append(self._createInputLabelForRecordField(fieldName));
2221 
2222                 //Create input element
2223                 $fieldContainer.append(
2224                     self._createInputForRecordField({
2225                         fieldName: fieldName,
2226                         formType: 'create',
2227                         form: $addRecordForm
2228                     }));
2229             }
2230 
2231             self._makeCascadeDropDowns($addRecordForm, undefined, 'create');
2232 
2233             $addRecordForm.submit(function () {
2234                 self._onSaveClickedOnCreateForm();
2235                 return false;
2236             });
2237 
2238             //Open the form
2239             self._$addRecordDiv.append($addRecordForm).dialog('open');
2240             self._trigger("formCreated", null, { form: $addRecordForm, formType: 'create' });
2241         },
2242 
2243         /* Saves new added record to the server and updates table.
2244         *************************************************************************/
2245         _saveAddRecordForm: function ($addRecordForm, $saveButton) {
2246             var self = this;
2247 
2248             var completeAddRecord = function (data) {
2249                 if (data.Result != 'OK') {
2250                     self._showError(data.Message);
2251                     self._setEnabledOfDialogButton($saveButton, true, self.options.messages.save);
2252                     return;
2253                 }
2254 
2255                 if (!data.Record) {
2256                     self._logError('Server must return the created Record object.');
2257                     self._setEnabledOfDialogButton($saveButton, true, self.options.messages.save);
2258                     return;
2259                 }
2260 
2261                 self._onRecordAdded(data);
2262                 self._addRow(
2263                     self._createRowFromRecord(data.Record), {
2264                         isNewRow: true
2265                     });
2266                 self._$addRecordDiv.dialog("close");
2267             };
2268 
2269             $addRecordForm.data('submitting', true); //TODO: Why it's used, can remove? Check it.
2270 
2271             //createAction may be a function, check if it is
2272             if ($.isFunction(self.options.actions.createAction)) {
2273 
2274                 //Execute the function
2275                 var funcResult = self.options.actions.createAction($addRecordForm.serialize());
2276 
2277                 //Check if result is a jQuery Deferred object
2278                 if (self._isDeferredObject(funcResult)) {
2279                     //Wait promise
2280                     funcResult.done(function (data) {
2281                         completeAddRecord(data);
2282                     }).fail(function () {
2283                         self._showError(self.options.messages.serverCommunicationError);
2284                         self._setEnabledOfDialogButton($saveButton, true, self.options.messages.save);
2285                     });
2286                 } else { //assume it returned the creation result
2287                     completeAddRecord(funcResult);
2288                 }
2289 
2290             } else { //Assume it's a URL string
2291 
2292                 //Make an Ajax call to create record
2293                 self._submitFormUsingAjax(
2294                     self.options.actions.createAction,
2295                     $addRecordForm.serialize(),
2296                     function (data) {
2297                         completeAddRecord(data);
2298                     },
2299                     function () {
2300                         self._showError(self.options.messages.serverCommunicationError);
2301                         self._setEnabledOfDialogButton($saveButton, true, self.options.messages.save);
2302                     });
2303             }
2304         },
2305 
2306         _onRecordAdded: function (data) {
2307             this._trigger("recordAdded", null, { record: data.Record, serverResponse: data });
2308         }
2309 
2310     });
2311 
2312 })(jQuery);
2313 
2314 
2315 /************************************************************************
2316 * EDIT RECORD extension for jTable                                      *
2317 *************************************************************************/
2318 (function ($) {
2319 
2320     //Reference to base object members
2321     var base = {
2322         _create: $.hik.jtable.prototype._create,
2323         _addColumnsToHeaderRow: $.hik.jtable.prototype._addColumnsToHeaderRow,
2324         _addCellsToRowUsingRecord: $.hik.jtable.prototype._addCellsToRowUsingRecord
2325     };
2326 
2327     //extension members
2328     $.extend(true, $.hik.jtable.prototype, {
2329 
2330         /************************************************************************
2331         * DEFAULT OPTIONS / EVENTS                                              *
2332         *************************************************************************/
2333         options: {
2334 
2335             //Events
2336             recordUpdated: function (event, data) { },
2337             rowUpdated: function (event, data) { },
2338 
2339             //Localization
2340             messages: {
2341                 editRecord: 'Edit Record'
2342             }
2343         },
2344 
2345         /************************************************************************
2346         * PRIVATE FIELDS                                                        *
2347         *************************************************************************/
2348 
2349         _$editDiv: null, //Reference to the editing dialog div (jQuery object)
2350         _$editingRow: null, //Reference to currently editing row (jQuery object)
2351 
2352         /************************************************************************
2353         * CONSTRUCTOR AND INITIALIZATION METHODS                                *
2354         *************************************************************************/
2355 
2356         /* Overrides base method to do editing-specific constructions.
2357         *************************************************************************/
2358         _create: function () {
2359             base._create.apply(this, arguments);
2360             
2361             if (!this.options.actions.updateAction) {
2362                 return;
2363             }
2364             
2365             this._createEditDialogDiv();
2366         },
2367 
2368         /* Creates and prepares edit dialog div
2369         *************************************************************************/
2370         _createEditDialogDiv: function () {
2371             var self = this;
2372 
2373             //Create a div for dialog and add to container element
2374             self._$editDiv = $('<div></div>')
2375                 .appendTo(self._$mainContainer);
2376 
2377             //Prepare dialog
2378             self._$editDiv.dialog({
2379                 autoOpen: false,
2380                 show: self.options.dialogShowEffect,
2381                 hide: self.options.dialogHideEffect,
2382                 width: 'auto',
2383                 minWidth: '300',
2384                 modal: true,
2385                 title: self.options.messages.editRecord,
2386                 buttons:
2387                         [{  //cancel button
2388                             text: self.options.messages.cancel,
2389                             click: function () {
2390                                 self._$editDiv.dialog('close');
2391                             }
2392                         }, { //save button
2393                             id: 'EditDialogSaveButton',
2394                             text: self.options.messages.save,
2395                             click: function () {
2396                                 self._onSaveClickedOnEditForm();
2397                             }
2398                         }],
2399                 close: function () {
2400                     var $editForm = self._$editDiv.find('form:first');
2401                     var $saveButton = self._$editDiv.parent().find('#EditDialogSaveButton');
2402                     self._trigger("formClosed", null, { form: $editForm, formType: 'edit', row: self._$editingRow });
2403                     self._setEnabledOfDialogButton($saveButton, true, self.options.messages.save);
2404                     $editForm.remove();
2405                 }
2406             });
2407         },
2408 
2409         /* Saves editing form to server.
2410         *************************************************************************/
2411         _onSaveClickedOnEditForm: function () {
2412             var self = this;
2413             
2414             //row maybe removed by another source, if so, do nothing
2415             if (self._$editingRow.hasClass('jtable-row-removed')) {
2416                 self._$editDiv.dialog('close');
2417                 return;
2418             }
2419 
2420             var $saveButton = self._$editDiv.parent().find('#EditDialogSaveButton');
2421             var $editForm = self._$editDiv.find('form');
2422             if (self._trigger("formSubmitting", null, { form: $editForm, formType: 'edit', row: self._$editingRow }) != false) {
2423                 self._setEnabledOfDialogButton($saveButton, false, self.options.messages.saving);
2424                 self._saveEditForm($editForm, $saveButton);
2425             }
2426         },
2427 
2428         /************************************************************************
2429         * PUBLIC METHODS                                                        *
2430         *************************************************************************/
2431 
2432         /* Updates a record on the table (optionally on the server also)
2433         *************************************************************************/
2434         updateRecord: function (options) {
2435             var self = this;
2436             options = $.extend({
2437                 clientOnly: false,
2438                 animationsEnabled: self.options.animationsEnabled,
2439                 success: function () { },
2440                 error: function () { }
2441             }, options);
2442 
2443             if (!options.record) {
2444                 self._logWarn('options parameter in updateRecord method must contain a record property.');
2445                 return;
2446             }
2447 
2448             var key = self._getKeyValueOfRecord(options.record);
2449             if (key == undefined || key == null) {
2450                 self._logWarn('options parameter in updateRecord method must contain a record that contains the key field property.');
2451                 return;
2452             }
2453 
2454             var $updatingRow = self.getRowByKey(key);
2455             if ($updatingRow == null) {
2456                 self._logWarn('Can not found any row by key "' + key + '" on the table. Updating row must be visible on the table.');
2457                 return;
2458             }
2459 
2460             if (options.clientOnly) {
2461                 $.extend($updatingRow.data('record'), options.record);
2462                 self._updateRowTexts($updatingRow);
2463                 self._onRecordUpdated($updatingRow, null);
2464                 if (options.animationsEnabled) {
2465                     self._showUpdateAnimationForRow($updatingRow);
2466                 }
2467 
2468                 options.success();
2469                 return;
2470             }
2471 
2472             var completeEdit = function (data) {
2473                 if (data.Result != 'OK') {
2474                     self._showError(data.Message);
2475                     options.error(data);
2476                     return;
2477                 }
2478 
2479                 $.extend($updatingRow.data('record'), options.record);
2480                 self._updateRecordValuesFromServerResponse($updatingRow.data('record'), data);
2481 
2482                 self._updateRowTexts($updatingRow);
2483                 self._onRecordUpdated($updatingRow, data);
2484                 if (options.animationsEnabled) {
2485                     self._showUpdateAnimationForRow($updatingRow);
2486                 }
2487 
2488                 options.success(data);
2489             };
2490 
2491             //updateAction may be a function, check if it is
2492             if (!options.url && $.isFunction(self.options.actions.updateAction)) {
2493 
2494                 //Execute the function
2495                 var funcResult = self.options.actions.updateAction($.param(options.record));
2496 
2497                 //Check if result is a jQuery Deferred object
2498                 if (self._isDeferredObject(funcResult)) {
2499                     //Wait promise
2500                     funcResult.done(function (data) {
2501                         completeEdit(data);
2502                     }).fail(function () {
2503                         self._showError(self.options.messages.serverCommunicationError);
2504                         options.error();
2505                     });
2506                 } else { //assume it returned the creation result
2507                     completeEdit(funcResult);
2508                 }
2509 
2510             } else { //Assume it's a URL string
2511 
2512                 //Make an Ajax call to create record
2513                 self._submitFormUsingAjax(
2514                     options.url || self.options.actions.updateAction,
2515                     $.param(options.record),
2516                     function (data) {
2517                         completeEdit(data);
2518                     },
2519                     function () {
2520                         self._showError(self.options.messages.serverCommunicationError);
2521                         options.error();
2522                     });
2523 
2524             }
2525         },
2526 
2527         /************************************************************************
2528         * OVERRIDED METHODS                                                     *
2529         *************************************************************************/
2530 
2531         /* Overrides base method to add a 'editing column cell' to header row.
2532         *************************************************************************/
2533         _addColumnsToHeaderRow: function ($tr) {
2534             base._addColumnsToHeaderRow.apply(this, arguments);
2535             if (this.options.actions.updateAction != undefined) {
2536                 $tr.append(this._createEmptyCommandHeader());
2537             }
2538         },
2539 
2540         /* Overrides base method to add a 'edit command cell' to a row.
2541         *************************************************************************/
2542         _addCellsToRowUsingRecord: function ($row) {
2543             var self = this;
2544             base._addCellsToRowUsingRecord.apply(this, arguments);
2545 
2546             if (self.options.actions.updateAction != undefined) {
2547                 var $span = $('<span></span>').html(self.options.messages.editRecord);
2548                 var $button = $('<button title="' + self.options.messages.editRecord + '"></button>')
2549                     .addClass('jtable-command-button jtable-edit-command-button')
2550                     .append($span)
2551                     .click(function (e) {
2552                         e.preventDefault();
2553                         e.stopPropagation();
2554                         self._showEditForm($row);
2555                     });
2556                 $('<td></td>')
2557                     .addClass('jtable-command-column')
2558                     .append($button)
2559                     .appendTo($row);
2560             }
2561         },
2562 
2563         /************************************************************************
2564         * PRIVATE METHODS                                                       *
2565         *************************************************************************/
2566 
2567         /* Shows edit form for a row.
2568         *************************************************************************/
2569         _showEditForm: function ($tableRow) {
2570             var self = this;
2571             var record = $tableRow.data('record');
2572 
2573             //Create edit form
2574             var $editForm = $('<form id="jtable-edit-form" class="jtable-dialog-form jtable-edit-form"></form>');
2575 
2576             //Create input fields
2577             for (var i = 0; i < self._fieldList.length; i++) {
2578 
2579                 var fieldName = self._fieldList[i];
2580                 var field = self.options.fields[fieldName];
2581                 var fieldValue = record[fieldName];
2582 
2583                 if (field.key == true) {
2584                     if (field.edit != true) {
2585                         //Create hidden field for key
2586                         $editForm.append(self._createInputForHidden(fieldName, fieldValue));
2587                         continue;
2588                     } else {
2589                         //Create a special hidden field for key (since key is be editable)
2590                         $editForm.append(self._createInputForHidden('jtRecordKey', fieldValue));
2591                     }
2592                 }
2593 
2594                 //Do not create element for non-editable fields
2595                 if (field.edit == false) {
2596                     continue;
2597                 }
2598 
2599                 //Hidden field
2600                 if (field.type == 'hidden') {
2601                     $editForm.append(self._createInputForHidden(fieldName, fieldValue));
2602                     continue;
2603                 }
2604 
2605                 //Create a container div for this input field and add to form
2606                 var $fieldContainer = $('<div class="jtable-input-field-container"></div>').appendTo($editForm);
2607 
2608                 //Create a label for input
2609                 $fieldContainer.append(self._createInputLabelForRecordField(fieldName));
2610 
2611                 //Create input element with it's current value
2612                 var currentValue = self._getValueForRecordField(record, fieldName);
2613                 $fieldContainer.append(
2614                     self._createInputForRecordField({
2615                         fieldName: fieldName,
2616                         value: currentValue,
2617                         record: record,
2618                         formType: 'edit',
2619                         form: $editForm
2620                     }));
2621             }
2622             
2623             self._makeCascadeDropDowns($editForm, record, 'edit');
2624 
2625             $editForm.submit(function () {
2626                 self._onSaveClickedOnEditForm();
2627                 return false;
2628             });
2629 
2630             //Open dialog
2631             self._$editingRow = $tableRow;
2632             self._$editDiv.append($editForm).dialog('open');
2633             self._trigger("formCreated", null, { form: $editForm, formType: 'edit', record: record, row: $tableRow });
2634         },
2635 
2636         /* Saves editing form to the server and updates the record on the table.
2637         *************************************************************************/
2638         _saveEditForm: function ($editForm, $saveButton) {
2639             var self = this;
2640             
2641             var completeEdit = function (data) {
2642                 if (data.Result != 'OK') {
2643                     self._showError(data.Message);
2644                     self._setEnabledOfDialogButton($saveButton, true, self.options.messages.save);
2645                     return;
2646                 }
2647 
2648                 var record = self._$editingRow.data('record');
2649 
2650                 self._updateRecordValuesFromForm(record, $editForm);
2651                 self._updateRecordValuesFromServerResponse(record, data);
2652                 self._updateRowTexts(self._$editingRow);
2653 
2654                 self._$editingRow.attr('data-record-key', self._getKeyValueOfRecord(record));
2655 
2656                 self._onRecordUpdated(self._$editingRow, data);
2657 
2658                 if (self.options.animationsEnabled) {
2659                     self._showUpdateAnimationForRow(self._$editingRow);
2660                 }
2661 
2662                 self._$editDiv.dialog("close");
2663             };
2664 
2665 
2666             //updateAction may be a function, check if it is
2667             if ($.isFunction(self.options.actions.updateAction)) {
2668 
2669                 //Execute the function
2670                 var funcResult = self.options.actions.updateAction($editForm.serialize());
2671 
2672                 //Check if result is a jQuery Deferred object
2673                 if (self._isDeferredObject(funcResult)) {
2674                     //Wait promise
2675                     funcResult.done(function (data) {
2676                         completeEdit(data);
2677                     }).fail(function () {
2678                         self._showError(self.options.messages.serverCommunicationError);
2679                         self._setEnabledOfDialogButton($saveButton, true, self.options.messages.save);
2680                     });
2681                 } else { //assume it returned the creation result
2682                     completeEdit(funcResult);
2683                 }
2684 
2685             } else { //Assume it's a URL string
2686 
2687                 //Make an Ajax call to update record
2688                 self._submitFormUsingAjax(
2689                     self.options.actions.updateAction,
2690                     $editForm.serialize(),
2691                     function(data) {
2692                         completeEdit(data);
2693                     },
2694                     function() {
2695                         self._showError(self.options.messages.serverCommunicationError);
2696                         self._setEnabledOfDialogButton($saveButton, true, self.options.messages.save);
2697                     });
2698             }
2699 
2700         },
2701 
2702         /* This method ensures updating of current record with server response,
2703         * if server sends a Record object as response to updateAction.
2704         *************************************************************************/
2705         _updateRecordValuesFromServerResponse: function (record, serverResponse) {
2706             if (!serverResponse || !serverResponse.Record) {
2707                 return;
2708             }
2709 
2710             $.extend(true, record, serverResponse.Record);
2711         },
2712 
2713         /* Gets text for a field of a record according to it's type.
2714         *************************************************************************/
2715         _getValueForRecordField: function (record, fieldName) {
2716             var field = this.options.fields[fieldName];
2717             var fieldValue = record[fieldName];
2718             if (field.type == 'date') {
2719                 return this._getDisplayTextForDateRecordField(field, fieldValue);
2720             } else {
2721                 return fieldValue;
2722             }
2723         },
2724 
2725         /* Updates cells of a table row's text values from row's record values.
2726         *************************************************************************/
2727         _updateRowTexts: function ($tableRow) {
2728             var record = $tableRow.data('record');
2729             var $columns = $tableRow.find('td');
2730             for (var i = 0; i < this._columnList.length; i++) {
2731                 var displayItem = this._getDisplayTextForRecordField(record, this._columnList[i]);
2732                 if ((displayItem != "") && (displayItem == 0)) displayItem = "0";
2733                 $columns.eq(this._firstDataColumnOffset + i).html(displayItem || '');
2734             }
2735 
2736             this._onRowUpdated($tableRow);
2737         },
2738 
2739         /* Shows 'updated' animation for a table row.
2740         *************************************************************************/
2741         _showUpdateAnimationForRow: function ($tableRow) {
2742             var className = 'jtable-row-updated';
2743             if (this.options.jqueryuiTheme) {
2744                 className = className + ' ui-state-highlight';
2745             }
2746 
2747             $tableRow.stop(true, true).addClass(className, 'slow', '', function () {
2748                 $tableRow.removeClass(className, 5000);
2749             });
2750         },
2751 
2752         /************************************************************************
2753         * EVENT RAISING METHODS                                                 *
2754         *************************************************************************/
2755 
2756         _onRowUpdated: function ($row) {
2757             this._trigger("rowUpdated", null, { row: $row, record: $row.data('record') });
2758         },
2759 
2760         _onRecordUpdated: function ($row, data) {
2761             this._trigger("recordUpdated", null, { record: $row.data('record'), row: $row, serverResponse: data });
2762         }
2763 
2764     });
2765 
2766 })(jQuery);
2767 
2768 
2769 /************************************************************************
2770 * DELETION extension for jTable                                         *
2771 *************************************************************************/
2772 (function ($) {
2773 
2774     //Reference to base object members
2775     var base = {
2776         _create: $.hik.jtable.prototype._create,
2777         _addColumnsToHeaderRow: $.hik.jtable.prototype._addColumnsToHeaderRow,
2778         _addCellsToRowUsingRecord: $.hik.jtable.prototype._addCellsToRowUsingRecord
2779     };
2780 
2781     //extension members
2782     $.extend(true, $.hik.jtable.prototype, {
2783 
2784         /************************************************************************
2785         * DEFAULT OPTIONS / EVENTS                                              *
2786         *************************************************************************/
2787         options: {
2788 
2789             //Options
2790             deleteConfirmation: true,
2791 
2792             //Events
2793             recordDeleted: function (event, data) { },
2794 
2795             //Localization
2796             messages: {
2797                 deleteConfirmation: 'This record will be deleted. Are you sure?',
2798                 deleteText: 'Delete',
2799                 deleting: 'Deleting',
2800                 canNotDeletedRecords: 'Can not delete {0} of {1} records!',
2801                 deleteProggress: 'Deleting {0} of {1} records, processing...'
2802             }
2803         },
2804 
2805         /************************************************************************
2806         * PRIVATE FIELDS                                                        *
2807         *************************************************************************/
2808 
2809         _$deleteRecordDiv: null, //Reference to the adding new record dialog div (jQuery object)
2810         _$deletingRow: null, //Reference to currently deleting row (jQuery object)
2811 
2812         /************************************************************************
2813         * CONSTRUCTOR                                                           *
2814         *************************************************************************/
2815 
2816         /* Overrides base method to do deletion-specific constructions.
2817         *************************************************************************/
2818         _create: function () {
2819             base._create.apply(this, arguments);
2820             this._createDeleteDialogDiv();
2821         },
2822 
2823         /* Creates and prepares delete record confirmation dialog div.
2824         *************************************************************************/
2825         _createDeleteDialogDiv: function () {
2826             var self = this;
2827 
2828             //Check if deleteAction is supplied
2829             if (!self.options.actions.deleteAction) {
2830                 return;
2831             }
2832 
2833             //Create div element for delete confirmation dialog
2834             self._$deleteRecordDiv = $('<div><p><span class="ui-icon ui-icon-alert" style="float:left; margin:0 7px 20px 0;"></span><span class="jtable-delete-confirm-message"></span></p></div>').appendTo(self._$mainContainer);
2835 
2836             //Prepare dialog
2837             self._$deleteRecordDiv.dialog({
2838                 autoOpen: false,
2839                 show: self.options.dialogShowEffect,
2840                 hide: self.options.dialogHideEffect,
2841                 modal: true,
2842                 title: self.options.messages.areYouSure,
2843                 buttons:
2844                         [{  //cancel button
2845                             text: self.options.messages.cancel,
2846                             click: function () {
2847                                 self._$deleteRecordDiv.dialog("close");
2848                             }
2849                         }, {//delete button
2850                             id: 'DeleteDialogButton',
2851                             text: self.options.messages.deleteText,
2852                             click: function () {
2853 
2854                                 //row maybe removed by another source, if so, do nothing
2855                                 if (self._$deletingRow.hasClass('jtable-row-removed')) {
2856                                     self._$deleteRecordDiv.dialog('close');
2857                                     return;
2858                                 }
2859 
2860                                 var $deleteButton = self._$deleteRecordDiv.parent().find('#DeleteDialogButton');
2861                                 self._setEnabledOfDialogButton($deleteButton, false, self.options.messages.deleting);
2862                                 self._deleteRecordFromServer(
2863                                     self._$deletingRow,
2864                                     function () {
2865                                         self._removeRowsFromTableWithAnimation(self._$deletingRow);
2866                                         self._$deleteRecordDiv.dialog('close');
2867                                     },
2868                                     function (message) { //error
2869                                         self._showError(message);
2870                                         self._setEnabledOfDialogButton($deleteButton, true, self.options.messages.deleteText);
2871                                     }
2872                                 );
2873                             }
2874                         }],
2875                 close: function () {
2876                     var $deleteButton = self._$deleteRecordDiv.parent().find('#DeleteDialogButton');
2877                     self._setEnabledOfDialogButton($deleteButton, true, self.options.messages.deleteText);
2878                 }
2879             });
2880         },
2881 
2882         /************************************************************************
2883         * PUBLIC METHODS                                                        *
2884         *************************************************************************/
2885 
2886         /* This method is used to delete one or more rows from server and the table.
2887         *************************************************************************/
2888         deleteRows: function ($rows) {
2889             var self = this;
2890 
2891             if ($rows.length <= 0) {
2892                 self._logWarn('No rows specified to jTable deleteRows method.');
2893                 return;
2894             }
2895 
2896             if (self._isBusy()) {
2897                 self._logWarn('Can not delete rows since jTable is busy!');
2898                 return;
2899             }
2900 
2901             //Deleting just one row
2902             if ($rows.length == 1) {
2903                 self._deleteRecordFromServer(
2904                     $rows,
2905                     function () { //success
2906                         self._removeRowsFromTableWithAnimation($rows);
2907                     },
2908                     function (message) { //error
2909                         self._showError(message);
2910                     }
2911                 );
2912 
2913                 return;
2914             }
2915 
2916             //Deleting multiple rows
2917             self._showBusy(self._formatString(self.options.messages.deleteProggress, 0, $rows.length));
2918 
2919             //This method checks if deleting of all records is completed
2920             var completedCount = 0;
2921             var isCompleted = function () {
2922                 return (completedCount >= $rows.length);
2923             };
2924 
2925             //This method is called when deleting of all records completed
2926             var completed = function () {
2927                 var $deletedRows = $rows.filter('.jtable-row-ready-to-remove');
2928                 if ($deletedRows.length < $rows.length) {
2929                     self._showError(self._formatString(self.options.messages.canNotDeletedRecords, $rows.length - $deletedRows.length, $rows.length));
2930                 }
2931 
2932                 if ($deletedRows.length > 0) {
2933                     self._removeRowsFromTableWithAnimation($deletedRows);
2934                 }
2935 
2936                 self._hideBusy();
2937             };
2938 
2939             //Delete all rows
2940             var deletedCount = 0;
2941             $rows.each(function () {
2942                 var $row = $(this);
2943                 self._deleteRecordFromServer(
2944                     $row,
2945                     function () { //success
2946                         ++deletedCount; ++completedCount;
2947                         $row.addClass('jtable-row-ready-to-remove');
2948                         self._showBusy(self._formatString(self.options.messages.deleteProggress, deletedCount, $rows.length));
2949                         if (isCompleted()) {
2950                             completed();
2951                         }
2952                     },
2953                     function () { //error
2954                         ++completedCount;
2955                         if (isCompleted()) {
2956                             completed();
2957                         }
2958                     }
2959                 );
2960             });
2961         },
2962 
2963         /* Deletes a record from the table (optionally from the server also).
2964         *************************************************************************/
2965         deleteRecord: function (options) {
2966             var self = this;
2967             options = $.extend({
2968                 clientOnly: false,
2969                 animationsEnabled: self.options.animationsEnabled,
2970                 url: self.options.actions.deleteAction,
2971                 success: function () { },
2972                 error: function () { }
2973             }, options);
2974 
2975             if (options.key == undefined) {
2976                 self._logWarn('options parameter in deleteRecord method must contain a key property.');
2977                 return;
2978             }
2979 
2980             var $deletingRow = self.getRowByKey(options.key);
2981             if ($deletingRow == null) {
2982                 self._logWarn('Can not found any row by key: ' + options.key);
2983                 return;
2984             }
2985 
2986             if (options.clientOnly) {
2987                 self._removeRowsFromTableWithAnimation($deletingRow, options.animationsEnabled);
2988                 options.success();
2989                 return;
2990             }
2991 
2992             self._deleteRecordFromServer(
2993                     $deletingRow,
2994                     function (data) { //success
2995                         self._removeRowsFromTableWithAnimation($deletingRow, options.animationsEnabled);
2996                         options.success(data);
2997                     },
2998                     function (message) { //error
2999                         self._showError(message);
3000                         options.error(message);
3001                     },
3002                     options.url
3003                 );
3004         },
3005 
3006         /************************************************************************
3007         * OVERRIDED METHODS                                                     *
3008         *************************************************************************/
3009 
3010         /* Overrides base method to add a 'deletion column cell' to header row.
3011         *************************************************************************/
3012         _addColumnsToHeaderRow: function ($tr) {
3013             base._addColumnsToHeaderRow.apply(this, arguments);
3014             if (this.options.actions.deleteAction != undefined) {
3015                 $tr.append(this._createEmptyCommandHeader());
3016             }
3017         },
3018 
3019         /* Overrides base method to add a 'delete command cell' to a row.
3020         *************************************************************************/
3021         _addCellsToRowUsingRecord: function ($row) {
3022             base._addCellsToRowUsingRecord.apply(this, arguments);
3023 
3024             var self = this;
3025             if (self.options.actions.deleteAction != undefined) {
3026                 var $span = $('<span></span>').html(self.options.messages.deleteText);
3027                 var $button = $('<button title="' + self.options.messages.deleteText + '"></button>')
3028                     .addClass('jtable-command-button jtable-delete-command-button')
3029                     .append($span)
3030                     .click(function (e) {
3031                         e.preventDefault();
3032                         e.stopPropagation();
3033                         self._deleteButtonClickedForRow($row);
3034                     });
3035                 $('<td></td>')
3036                     .addClass('jtable-command-column')
3037                     .append($button)
3038                     .appendTo($row);
3039             }
3040         },
3041 
3042         /************************************************************************
3043         * PRIVATE METHODS                                                       *
3044         *************************************************************************/
3045 
3046         /* This method is called when user clicks delete button on a row.
3047         *************************************************************************/
3048         _deleteButtonClickedForRow: function ($row) {
3049             var self = this;
3050 
3051             var deleteConfirm;
3052             var deleteConfirmMessage = self.options.messages.deleteConfirmation;
3053 
3054             //If options.deleteConfirmation is function then call it
3055             if ($.isFunction(self.options.deleteConfirmation)) {
3056                 var data = { row: $row, record: $row.data('record'), deleteConfirm: true, deleteConfirmMessage: deleteConfirmMessage, cancel: false, cancelMessage: null };
3057                 self.options.deleteConfirmation(data);
3058 
3059                 //If delete progress is cancelled
3060                 if (data.cancel) {
3061 
3062                     //If a canlellation reason is specified
3063                     if (data.cancelMessage) {
3064                         self._showError(data.cancelMessage); //TODO: show warning/stop message instead of error (also show warning/error ui icon)!
3065                     }
3066 
3067                     return;
3068                 }
3069 
3070                 deleteConfirmMessage = data.deleteConfirmMessage;
3071                 deleteConfirm = data.deleteConfirm;
3072             } else {
3073                 deleteConfirm = self.options.deleteConfirmation;
3074             }
3075 
3076             if (deleteConfirm != false) {
3077                 //Confirmation
3078                 self._$deleteRecordDiv.find('.jtable-delete-confirm-message').html(deleteConfirmMessage);
3079                 self._showDeleteDialog($row);
3080             } else {
3081                 //No confirmation
3082                 self._deleteRecordFromServer(
3083                     $row,
3084                     function () { //success
3085                         self._removeRowsFromTableWithAnimation($row);
3086                     },
3087                     function (message) { //error
3088                         self._showError(message);
3089                     }
3090                 );
3091             }
3092         },
3093 
3094         /* Shows delete comfirmation dialog.
3095         *************************************************************************/
3096         _showDeleteDialog: function ($row) {
3097             this._$deletingRow = $row;
3098             this._$deleteRecordDiv.dialog('open');
3099         },
3100 
3101         /* Performs an ajax call to server to delete record
3102         *  and removes row of the record from table if ajax call success.
3103         *************************************************************************/
3104         _deleteRecordFromServer: function ($row, success, error, url) {
3105             var self = this;
3106 
3107             var completeDelete = function(data) {
3108                 if (data.Result != 'OK') {
3109                     $row.data('deleting', false);
3110                     if (error) {
3111                         error(data.Message);
3112                     }
3113 
3114                     return;
3115                 }
3116 
3117                 self._trigger("recordDeleted", null, { record: $row.data('record'), row: $row, serverResponse: data });
3118 
3119                 if (success) {
3120                     success(data);
3121                 }
3122             };
3123 
3124             //Check if it is already being deleted right now
3125             if ($row.data('deleting') == true) {
3126                 return;
3127             }
3128 
3129             $row.data('deleting', true);
3130 
3131             var postData = {};
3132             postData[self._keyField] = self._getKeyValueOfRecord($row.data('record'));
3133             
3134             //deleteAction may be a function, check if it is
3135             if (!url && $.isFunction(self.options.actions.deleteAction)) {
3136 
3137                 //Execute the function
3138                 var funcResult = self.options.actions.deleteAction(postData);
3139 
3140                 //Check if result is a jQuery Deferred object
3141                 if (self._isDeferredObject(funcResult)) {
3142                     //Wait promise
3143                     funcResult.done(function (data) {
3144                         completeDelete(data);
3145                     }).fail(function () {
3146                         $row.data('deleting', false);
3147                         if (error) {
3148                             error(self.options.messages.serverCommunicationError);
3149                         }
3150                     });
3151                 } else { //assume it returned the deletion result
3152                     completeDelete(funcResult);
3153                 }
3154 
3155             } else { //Assume it's a URL string
3156                 //Make ajax call to delete the record from server
3157                 this._ajax({
3158                     url: (url || self.options.actions.deleteAction),
3159                     data: postData,
3160                     success: function (data) {
3161                         completeDelete(data);
3162                     },
3163                     error: function () {
3164                         $row.data('deleting', false);
3165                         if (error) {
3166                             error(self.options.messages.serverCommunicationError);
3167                         }
3168                     }
3169                 });
3170 
3171             }
3172         },
3173 
3174         /* Removes a row from table after a 'deleting' animation.
3175         *************************************************************************/
3176         _removeRowsFromTableWithAnimation: function ($rows, animationsEnabled) {
3177             var self = this;
3178 
3179             if (animationsEnabled == undefined) {
3180                 animationsEnabled = self.options.animationsEnabled;
3181             }
3182 
3183             if (animationsEnabled) {
3184                 var className = 'jtable-row-deleting';
3185                 if (this.options.jqueryuiTheme) {
3186                     className = className + ' ui-state-disabled';
3187                 }
3188 
3189                 //Stop current animation (if does exists) and begin 'deleting' animation.
3190                 $rows.stop(true, true).addClass(className, 'slow', '').promise().done(function () {
3191                     self._removeRowsFromTable($rows, 'deleted');
3192                 });
3193             } else {
3194                 self._removeRowsFromTable($rows, 'deleted');
3195             }
3196         }
3197 
3198     });
3199 
3200 })(jQuery);
3201 
3202 
3203 /************************************************************************
3204 * SELECTING extension for jTable                                        *
3205 *************************************************************************/
3206 (function ($) {
3207 
3208     //Reference to base object members
3209     var base = {
3210         _create: $.hik.jtable.prototype._create,
3211         _addColumnsToHeaderRow: $.hik.jtable.prototype._addColumnsToHeaderRow,
3212         _addCellsToRowUsingRecord: $.hik.jtable.prototype._addCellsToRowUsingRecord,
3213         _onLoadingRecords: $.hik.jtable.prototype._onLoadingRecords,
3214         _onRecordsLoaded: $.hik.jtable.prototype._onRecordsLoaded,
3215         _onRowsRemoved: $.hik.jtable.prototype._onRowsRemoved
3216     };
3217 
3218     //extension members
3219     $.extend(true, $.hik.jtable.prototype, {
3220 
3221         /************************************************************************
3222         * DEFAULT OPTIONS / EVENTS                                              *
3223         *************************************************************************/
3224         options: {
3225 
3226             //Options
3227             selecting: false,
3228             multiselect: false,
3229             selectingCheckboxes: false,
3230             selectOnRowClick: true,
3231 
3232             //Events
3233             selectionChanged: function (event, data) { }
3234         },
3235 
3236         /************************************************************************
3237         * PRIVATE FIELDS                                                        *
3238         *************************************************************************/
3239 
3240         _selectedRecordIdsBeforeLoad: null, //This array is used to store selected row Id's to restore them after a page refresh (string array).
3241         _$selectAllCheckbox: null, //Reference to the 'select/deselect all' checkbox (jQuery object)
3242         _shiftKeyDown: false, //True, if shift key is currently down.
3243 
3244         /************************************************************************
3245         * CONSTRUCTOR                                                           *
3246         *************************************************************************/
3247 
3248         /* Overrides base method to do selecting-specific constructions.
3249         *************************************************************************/
3250         _create: function () {
3251             if (this.options.selecting && this.options.selectingCheckboxes) {
3252                 ++this._firstDataColumnOffset;
3253                 this._bindKeyboardEvents();
3254             }
3255 
3256             //Call base method
3257             base._create.apply(this, arguments);
3258         },
3259 
3260         /* Registers to keyboard events those are needed for selection
3261         *************************************************************************/
3262         _bindKeyboardEvents: function () {
3263             var self = this;
3264             //Register to events to set _shiftKeyDown value
3265             $(document)
3266                 .keydown(function (event) {
3267                     switch (event.which) {
3268                         case 16:
3269                             self._shiftKeyDown = true;
3270                             break;
3271                     }
3272                 })
3273                 .keyup(function (event) {
3274                     switch (event.which) {
3275                         case 16:
3276                             self._shiftKeyDown = false;
3277                             break;
3278                     }
3279                 });
3280         },
3281 
3282         /************************************************************************
3283         * PUBLIC METHODS                                                        *
3284         *************************************************************************/
3285 
3286         /* Gets jQuery selection for currently selected rows.
3287         *************************************************************************/
3288         selectedRows: function () {
3289             return this._getSelectedRows();
3290         },
3291 
3292         /* Makes row/rows 'selected'.
3293         *************************************************************************/
3294         selectRows: function ($rows) {
3295             this._selectRows($rows);
3296             this._onSelectionChanged(); //TODO: trigger only if selected rows changes?
3297         },
3298 
3299         /************************************************************************
3300         * OVERRIDED METHODS                                                     *
3301         *************************************************************************/
3302 
3303         /* Overrides base method to add a 'select column' to header row.
3304         *************************************************************************/
3305         _addColumnsToHeaderRow: function ($tr) {
3306             if (this.options.selecting && this.options.selectingCheckboxes) {
3307                 if (this.options.multiselect) {
3308                     $tr.append(this._createSelectAllHeader());
3309                 } else {
3310                     $tr.append(this._createEmptyCommandHeader());
3311                 }
3312             }
3313 
3314             base._addColumnsToHeaderRow.apply(this, arguments);
3315         },
3316 
3317         /* Overrides base method to add a 'delete command cell' to a row.
3318         *************************************************************************/
3319         _addCellsToRowUsingRecord: function ($row) {
3320             if (this.options.selecting) {
3321                 this._makeRowSelectable($row);
3322             }
3323 
3324             base._addCellsToRowUsingRecord.apply(this, arguments);
3325         },
3326 
3327         /* Overrides base event to store selection list
3328         *************************************************************************/
3329         _onLoadingRecords: function () {
3330             if (this.options.selecting) {
3331                 this._storeSelectionList();
3332             }
3333 
3334             base._onLoadingRecords.apply(this, arguments);
3335         },
3336 
3337         /* Overrides base event to restore selection list
3338         *************************************************************************/
3339         _onRecordsLoaded: function () {
3340             if (this.options.selecting) {
3341                 this._restoreSelectionList();
3342             }
3343 
3344             base._onRecordsLoaded.apply(this, arguments);
3345         },
3346 
3347         /* Overrides base event to check is any selected row is being removed.
3348         *************************************************************************/
3349         _onRowsRemoved: function ($rows, reason) {
3350             if (this.options.selecting && (reason != 'reloading') && ($rows.filter('.jtable-row-selected').length > 0)) {
3351                 this._onSelectionChanged();
3352             }
3353 
3354             base._onRowsRemoved.apply(this, arguments);
3355         },
3356 
3357         /************************************************************************
3358         * PRIVATE METHODS                                                       *
3359         *************************************************************************/
3360 
3361         /* Creates a header column to select/deselect all rows.
3362         *************************************************************************/
3363         _createSelectAllHeader: function () {
3364             var self = this;
3365 
3366             var $columnHeader = $('<th class=""></th>')
3367                 .addClass('jtable-command-column-header jtable-column-header-selecting');
3368             this._jqueryuiThemeAddClass($columnHeader, 'ui-state-default');
3369 
3370             var $headerContainer = $('<div />')
3371                 .addClass('jtable-column-header-container')
3372                 .appendTo($columnHeader);
3373 
3374             self._$selectAllCheckbox = $('<input type="checkbox" />')
3375                 .appendTo($headerContainer)
3376                 .click(function () {
3377                     if (self._$tableRows.length <= 0) {
3378                         self._$selectAllCheckbox.attr('checked', false);
3379                         return;
3380                     }
3381 
3382                     var allRows = self._$tableBody.find('>tr.jtable-data-row');
3383                     if (self._$selectAllCheckbox.is(':checked')) {
3384                         self._selectRows(allRows);
3385                     } else {
3386                         self._deselectRows(allRows);
3387                     }
3388 
3389                     self._onSelectionChanged();
3390                 });
3391 
3392             return $columnHeader;
3393         },
3394 
3395         /* Stores Id's of currently selected records to _selectedRecordIdsBeforeLoad.
3396         *************************************************************************/
3397         _storeSelectionList: function () {
3398             var self = this;
3399 
3400             if (!self.options.selecting) {
3401                 return;
3402             }
3403 
3404             self._selectedRecordIdsBeforeLoad = [];
3405             self._getSelectedRows().each(function () {
3406                 self._selectedRecordIdsBeforeLoad.push(self._getKeyValueOfRecord($(this).data('record')));
3407             });
3408         },
3409 
3410         /* Selects rows whose Id is in _selectedRecordIdsBeforeLoad;
3411         *************************************************************************/
3412         _restoreSelectionList: function () {
3413             var self = this;
3414 
3415             if (!self.options.selecting) {
3416                 return;
3417             }
3418 
3419             var selectedRowCount = 0;
3420             for (var i = 0; i < self._$tableRows.length; ++i) {
3421                 var recordId = self._getKeyValueOfRecord(self._$tableRows[i].data('record'));
3422                 if ($.inArray(recordId, self._selectedRecordIdsBeforeLoad) > -1) {
3423                     self._selectRows(self._$tableRows[i]);
3424                     ++selectedRowCount;
3425                 }
3426             }
3427 
3428             if (self._selectedRecordIdsBeforeLoad.length > 0 && self._selectedRecordIdsBeforeLoad.length != selectedRowCount) {
3429                 self._onSelectionChanged();
3430             }
3431 
3432             self._selectedRecordIdsBeforeLoad = [];
3433             self._refreshSelectAllCheckboxState();
3434         },
3435 
3436         /* Gets all selected rows.
3437         *************************************************************************/
3438         _getSelectedRows: function () {
3439             return this._$tableBody
3440                 .find('>tr.jtable-row-selected');
3441         },
3442 
3443         /* Adds selectable feature to a row.
3444         *************************************************************************/
3445         _makeRowSelectable: function ($row) {
3446             var self = this;
3447 
3448             //Select/deselect on row click
3449             if (self.options.selectOnRowClick) {
3450                 $row.click(function () {
3451                     self._invertRowSelection($row);
3452                 });
3453             }
3454 
3455             //'select/deselect' checkbox column
3456             if (self.options.selectingCheckboxes) {
3457                 var $cell = $('<td></td>').addClass('jtable-selecting-column');
3458                 var $selectCheckbox = $('<input type="checkbox" />').appendTo($cell);
3459                 if (!self.options.selectOnRowClick) {
3460                     $selectCheckbox.click(function () {
3461                         self._invertRowSelection($row);
3462                     });
3463                 }
3464 
3465                 $row.append($cell);
3466             }
3467         },
3468 
3469         /* Inverts selection state of a single row.
3470         *************************************************************************/
3471         _invertRowSelection: function ($row) {
3472             if ($row.hasClass('jtable-row-selected')) {
3473                 this._deselectRows($row);
3474             } else {
3475                 //Shift key?
3476                 if (this._shiftKeyDown) {
3477                     var rowIndex = this._findRowIndex($row);
3478                     //try to select row and above rows until first selected row
3479                     var beforeIndex = this._findFirstSelectedRowIndexBeforeIndex(rowIndex) + 1;
3480                     if (beforeIndex > 0 && beforeIndex < rowIndex) {
3481                         this._selectRows(this._$tableBody.find('tr').slice(beforeIndex, rowIndex + 1));
3482                     } else {
3483                         //try to select row and below rows until first selected row
3484                         var afterIndex = this._findFirstSelectedRowIndexAfterIndex(rowIndex) - 1;
3485                         if (afterIndex > rowIndex) {
3486                             this._selectRows(this._$tableBody.find('tr').slice(rowIndex, afterIndex + 1));
3487                         } else {
3488                             //just select this row
3489                             this._selectRows($row);
3490                         }
3491                     }
3492                 } else {
3493                     this._selectRows($row);
3494                 }
3495             }
3496 
3497             this._onSelectionChanged();
3498         },
3499 
3500         /* Search for a selected row (that is before given row index) to up and returns it's index 
3501         *************************************************************************/
3502         _findFirstSelectedRowIndexBeforeIndex: function (rowIndex) {
3503             for (var i = rowIndex - 1; i >= 0; --i) {
3504                 if (this._$tableRows[i].hasClass('jtable-row-selected')) {
3505                     return i;
3506                 }
3507             }
3508 
3509             return -1;
3510         },
3511 
3512         /* Search for a selected row (that is after given row index) to down and returns it's index 
3513         *************************************************************************/
3514         _findFirstSelectedRowIndexAfterIndex: function (rowIndex) {
3515             for (var i = rowIndex + 1; i < this._$tableRows.length; ++i) {
3516                 if (this._$tableRows[i].hasClass('jtable-row-selected')) {
3517                     return i;
3518                 }
3519             }
3520 
3521             return -1;
3522         },
3523 
3524         /* Makes row/rows 'selected'.
3525         *************************************************************************/
3526         _selectRows: function ($rows) {
3527             if (!this.options.multiselect) {
3528                 this._deselectRows(this._getSelectedRows());
3529             }
3530 
3531             $rows.addClass('jtable-row-selected');
3532             this._jqueryuiThemeAddClass($rows, 'ui-state-highlight');
3533 
3534             if (this.options.selectingCheckboxes) {
3535                 $rows.find('>td.jtable-selecting-column >input').prop('checked', true);
3536             }
3537 
3538             this._refreshSelectAllCheckboxState();
3539         },
3540 
3541         /* Makes row/rows 'non selected'.
3542         *************************************************************************/
3543         _deselectRows: function ($rows) {
3544             $rows.removeClass('jtable-row-selected ui-state-highlight');
3545             if (this.options.selectingCheckboxes) {
3546                 $rows.find('>td.jtable-selecting-column >input').prop('checked', false);
3547             }
3548 
3549             this._refreshSelectAllCheckboxState();
3550         },
3551 
3552         /* Updates state of the 'select/deselect' all checkbox according to count of selected rows.
3553         *************************************************************************/
3554         _refreshSelectAllCheckboxState: function () {
3555             if (!this.options.selectingCheckboxes || !this.options.multiselect) {
3556                 return;
3557             }
3558 
3559             var totalRowCount = this._$tableRows.length;
3560             var selectedRowCount = this._getSelectedRows().length;
3561 
3562             if (selectedRowCount == 0) {
3563                 this._$selectAllCheckbox.prop('indeterminate', false);
3564                 this._$selectAllCheckbox.attr('checked', false);
3565             } else if (selectedRowCount == totalRowCount) {
3566                 this._$selectAllCheckbox.prop('indeterminate', false);
3567                 this._$selectAllCheckbox.attr('checked', true);
3568             } else {
3569                 this._$selectAllCheckbox.attr('checked', false);
3570                 this._$selectAllCheckbox.prop('indeterminate', true);
3571             }
3572         },
3573 
3574         /************************************************************************
3575         * EVENT RAISING METHODS                                                 *
3576         *************************************************************************/
3577 
3578         _onSelectionChanged: function () {
3579             this._trigger("selectionChanged", null, {});
3580         }
3581 
3582     });
3583 
3584 })(jQuery);
3585 
3586 
3587 /************************************************************************
3588 * PAGING extension for jTable                                           *
3589 *************************************************************************/
3590 (function ($) {
3591 
3592     //Reference to base object members
3593     var base = {
3594         load: $.hik.jtable.prototype.load,
3595         _create: $.hik.jtable.prototype._create,
3596         _setOption: $.hik.jtable.prototype._setOption,
3597         _createRecordLoadUrl: $.hik.jtable.prototype._createRecordLoadUrl,
3598         _createJtParamsForLoading: $.hik.jtable.prototype._createJtParamsForLoading,
3599         _addRowToTable: $.hik.jtable.prototype._addRowToTable,
3600         _addRow: $.hik.jtable.prototype._addRow,
3601         _removeRowsFromTable: $.hik.jtable.prototype._removeRowsFromTable,
3602         _onRecordsLoaded: $.hik.jtable.prototype._onRecordsLoaded
3603     };
3604 
3605     //extension members
3606     $.extend(true, $.hik.jtable.prototype, {
3607 
3608         /************************************************************************
3609         * DEFAULT OPTIONS / EVENTS                                              *
3610         *************************************************************************/
3611         options: {
3612             paging: false,
3613             pageList: 'normal', //possible values: 'minimal', 'normal'
3614             pageSize: 10,
3615             pageSizes: [10, 25, 50, 100, 250, 500],
3616             pageSizeChangeArea: true,
3617             gotoPageArea: 'combobox', //possible values: 'textbox', 'combobox', 'none'
3618 
3619             messages: {
3620                 pagingInfo: 'Showing {0}-{1} of {2}',
3621                 pageSizeChangeLabel: 'Row count',
3622                 gotoPageLabel: 'Go to page'
3623             }
3624         },
3625 
3626         /************************************************************************
3627         * PRIVATE FIELDS                                                        *
3628         *************************************************************************/
3629 
3630         _$bottomPanel: null, //Reference to the panel at the bottom of the table (jQuery object)
3631         _$pagingListArea: null, //Reference to the page list area in to bottom panel (jQuery object)
3632         _$pageSizeChangeArea: null, //Reference to the page size change area in to bottom panel (jQuery object)
3633         _$pageInfoSpan: null, //Reference to the paging info area in to bottom panel (jQuery object)
3634         _$gotoPageArea: null, //Reference to 'Go to page' input area in to bottom panel (jQuery object)
3635         _$gotoPageInput: null, //Reference to 'Go to page' input in to bottom panel (jQuery object)
3636         _totalRecordCount: 0, //Total count of records on all pages
3637         _currentPageNo: 1, //Current page number
3638 
3639         /************************************************************************
3640         * CONSTRUCTOR AND INITIALIZING METHODS                                  *
3641         *************************************************************************/
3642 
3643         /* Overrides base method to do paging-specific constructions.
3644         *************************************************************************/
3645         _create: function() {
3646             base._create.apply(this, arguments);
3647             if (this.options.paging) {
3648                 this._loadPagingSettings();
3649                 this._createBottomPanel();
3650                 this._createPageListArea();
3651                 this._createGotoPageInput();
3652                 this._createPageSizeSelection();
3653             }
3654         },
3655 
3656         /* Loads user preferences for paging.
3657         *************************************************************************/
3658         _loadPagingSettings: function() {
3659             if (!this.options.saveUserPreferences) {
3660                 return;
3661             }
3662 
3663             var pageSize = this._getCookie('page-size');
3664             if (pageSize) {
3665                 this.options.pageSize = this._normalizeNumber(pageSize, 1, 1000000, this.options.pageSize);
3666             }
3667         },
3668 
3669         /* Creates bottom panel and adds to the page.
3670         *************************************************************************/
3671         _createBottomPanel: function() {
3672             this._$bottomPanel = $('<div />')
3673                 .addClass('jtable-bottom-panel')
3674                 .insertAfter(this._$table);
3675 
3676             this._jqueryuiThemeAddClass(this._$bottomPanel, 'ui-state-default');
3677 
3678             $('<div />').addClass('jtable-left-area').appendTo(this._$bottomPanel);
3679             $('<div />').addClass('jtable-right-area').appendTo(this._$bottomPanel);
3680         },
3681 
3682         /* Creates page list area.
3683         *************************************************************************/
3684         _createPageListArea: function() {
3685             this._$pagingListArea = $('<span></span>')
3686                 .addClass('jtable-page-list')
3687                 .appendTo(this._$bottomPanel.find('.jtable-left-area'));
3688 
3689             this._$pageInfoSpan = $('<span></span>')
3690                 .addClass('jtable-page-info')
3691                 .appendTo(this._$bottomPanel.find('.jtable-right-area'));
3692         },
3693 
3694         /* Creates page list change area.
3695         *************************************************************************/
3696         _createPageSizeSelection: function() {
3697             var self = this;
3698 
3699             if (!self.options.pageSizeChangeArea) {
3700                 return;
3701             }
3702 
3703             //Add current page size to page sizes list if not contains it
3704             if (self._findIndexInArray(self.options.pageSize, self.options.pageSizes) < 0) {
3705                 self.options.pageSizes.push(parseInt(self.options.pageSize));
3706                 self.options.pageSizes.sort(function(a, b) { return a - b; });
3707             }
3708 
3709             //Add a span to contain page size change items
3710             self._$pageSizeChangeArea = $('<span></span>')
3711                 .addClass('jtable-page-size-change')
3712                 .appendTo(self._$bottomPanel.find('.jtable-left-area'));
3713 
3714             //Page size label
3715             self._$pageSizeChangeArea.append('<span>' + self.options.messages.pageSizeChangeLabel + ': </span>');
3716 
3717             //Page size change combobox
3718             var $pageSizeChangeCombobox = $('<select></select>').appendTo(self._$pageSizeChangeArea);
3719 
3720             //Add page sizes to the combobox
3721             for (var i = 0; i < self.options.pageSizes.length; i++) {
3722                 $pageSizeChangeCombobox.append('<option value="' + self.options.pageSizes[i] + '">' + self.options.pageSizes[i] + '</option>');
3723             }
3724 
3725             //Select current page size
3726             $pageSizeChangeCombobox.val(self.options.pageSize);
3727 
3728             //Change page size on combobox change
3729             $pageSizeChangeCombobox.change(function() {
3730                 self._changePageSize(parseInt($(this).val()));
3731             });
3732         },
3733 
3734         /* Creates go to page area.
3735         *************************************************************************/
3736         _createGotoPageInput: function() {
3737             var self = this;
3738 
3739             if (!self.options.gotoPageArea || self.options.gotoPageArea == 'none') {
3740                 return;
3741             }
3742 
3743             //Add a span to contain goto page items
3744             this._$gotoPageArea = $('<span></span>')
3745                 .addClass('jtable-goto-page')
3746                 .appendTo(self._$bottomPanel.find('.jtable-left-area'));
3747 
3748             //Goto page label
3749             this._$gotoPageArea.append('<span>' + self.options.messages.gotoPageLabel + ': </span>');
3750 
3751             //Goto page input
3752             if (self.options.gotoPageArea == 'combobox') {
3753 
3754                 self._$gotoPageInput = $('<select></select>')
3755                     .appendTo(this._$gotoPageArea)
3756                     .data('pageCount', 1)
3757                     .change(function() {
3758                         self._changePage(parseInt($(this).val()));
3759                     });
3760                 self._$gotoPageInput.append('<option value="1">1</option>');
3761 
3762             } else { //textbox
3763 
3764                 self._$gotoPageInput = $('<input type="text" maxlength="10" value="' + self._currentPageNo + '" />')
3765                     .appendTo(this._$gotoPageArea)
3766                     .keypress(function(event) {
3767                         if (event.which == 13) { //enter
3768                             event.preventDefault();
3769                             self._changePage(parseInt(self._$gotoPageInput.val()));
3770                         } else if (event.which == 43) { // +
3771                             event.preventDefault();
3772                             self._changePage(parseInt(self._$gotoPageInput.val()) + 1);
3773                         } else if (event.which == 45) { // -
3774                             event.preventDefault();
3775                             self._changePage(parseInt(self._$gotoPageInput.val()) - 1);
3776                         } else {
3777                             //Allow only digits
3778                             var isValid = (
3779                                 (47 < event.keyCode && event.keyCode < 58 && event.shiftKey == false && event.altKey == false)
3780                                     || (event.keyCode == 8)
3781                                     || (event.keyCode == 9)
3782                             );
3783 
3784                             if (!isValid) {
3785                                 event.preventDefault();
3786                             }
3787                         }
3788                     });
3789 
3790             }
3791         },
3792 
3793         /* Refreshes the 'go to page' input.
3794         *************************************************************************/
3795         _refreshGotoPageInput: function() {
3796             if (!this.options.gotoPageArea || this.options.gotoPageArea == 'none') {
3797                 return;
3798             }
3799 
3800             if (this._totalRecordCount <= 0) {
3801                 this._$gotoPageArea.hide();
3802             } else {
3803                 this._$gotoPageArea.show();
3804             }
3805 
3806             if (this.options.gotoPageArea == 'combobox') {
3807                 var oldPageCount = this._$gotoPageInput.data('pageCount');
3808                 var currentPageCount = this._calculatePageCount();
3809                 if (oldPageCount != currentPageCount) {
3810                     this._$gotoPageInput.empty();
3811 
3812                     //Skip some pages is there are too many pages
3813                     var pageStep = 1;
3814                     if (currentPageCount > 10000) {
3815                         pageStep = 100;
3816                     } else if (currentPageCount > 5000) {
3817                         pageStep = 10;
3818                     } else if (currentPageCount > 2000) {
3819                         pageStep = 5;
3820                     } else if (currentPageCount > 1000) {
3821                         pageStep = 2;
3822                     }
3823 
3824                     for (var i = pageStep; i <= currentPageCount; i += pageStep) {
3825                         this._$gotoPageInput.append('<option value="' + i + '">' + i + '</option>');
3826                     }
3827 
3828                     this._$gotoPageInput.data('pageCount', currentPageCount);
3829                 }
3830             }
3831 
3832             //same for 'textbox' and 'combobox'
3833             this._$gotoPageInput.val(this._currentPageNo);
3834         },
3835 
3836         /************************************************************************
3837         * OVERRIDED METHODS                                                     *
3838         *************************************************************************/
3839 
3840         /* Overrides load method to set current page to 1.
3841         *************************************************************************/
3842         load: function() {
3843             this._currentPageNo = 1;
3844 
3845             base.load.apply(this, arguments);
3846         },
3847 
3848         /* Used to change options dynamically after initialization.
3849         *************************************************************************/
3850         _setOption: function(key, value) {
3851             base._setOption.apply(this, arguments);
3852 
3853             if (key == 'pageSize') {
3854                 this._changePageSize(parseInt(value));
3855             }
3856         },
3857 
3858         /* Changes current page size with given value.
3859         *************************************************************************/
3860         _changePageSize: function(pageSize) {
3861             if (pageSize == this.options.pageSize) {
3862                 return;
3863             }
3864 
3865             this.options.pageSize = pageSize;
3866 
3867             //Normalize current page
3868             var pageCount = this._calculatePageCount();
3869             if (this._currentPageNo > pageCount) {
3870                 this._currentPageNo = pageCount;
3871             }
3872             if (this._currentPageNo <= 0) {
3873                 this._currentPageNo = 1;
3874             }
3875 
3876             //if user sets one of the options on the combobox, then select it.
3877             var $pageSizeChangeCombobox = this._$bottomPanel.find('.jtable-page-size-change select');
3878             if ($pageSizeChangeCombobox.length > 0) {
3879                 if (parseInt($pageSizeChangeCombobox.val()) != pageSize) {
3880                     var selectedOption = $pageSizeChangeCombobox.find('option[value=' + pageSize + ']');
3881                     if (selectedOption.length > 0) {
3882                         $pageSizeChangeCombobox.val(pageSize);
3883                     }
3884                 }
3885             }
3886 
3887             this._savePagingSettings();
3888             this._reloadTable();
3889         },
3890 
3891         /* Saves user preferences for paging
3892         *************************************************************************/
3893         _savePagingSettings: function() {
3894             if (!this.options.saveUserPreferences) {
3895                 return;
3896             }
3897 
3898             this._setCookie('page-size', this.options.pageSize);
3899         },
3900 
3901         /* Overrides _createRecordLoadUrl method to add paging info to URL.
3902         *************************************************************************/
3903         _createRecordLoadUrl: function() {
3904             var loadUrl = base._createRecordLoadUrl.apply(this, arguments);
3905             loadUrl = this._addPagingInfoToUrl(loadUrl, this._currentPageNo);
3906             return loadUrl;
3907         },
3908 
3909         /* Overrides _createJtParamsForLoading method to add paging parameters to jtParams object.
3910         *************************************************************************/
3911         _createJtParamsForLoading: function () {
3912             var jtParams = base._createJtParamsForLoading.apply(this, arguments);
3913             
3914             if (this.options.paging) {
3915                 jtParams.jtStartIndex = (this._currentPageNo - 1) * this.options.pageSize;
3916                 jtParams.jtPageSize = this.options.pageSize;
3917             }
3918 
3919             return jtParams;
3920         },
3921 
3922         /* Overrides _addRowToTable method to re-load table when a new row is created.
3923         * NOTE: THIS METHOD IS DEPRECATED AND WILL BE REMOVED FROM FEATURE RELEASES.
3924         * USE _addRow METHOD.
3925         *************************************************************************/
3926         _addRowToTable: function ($tableRow, index, isNewRow) {
3927             if (isNewRow && this.options.paging) {
3928                 this._reloadTable();
3929                 return;
3930             }
3931 
3932             base._addRowToTable.apply(this, arguments);
3933         },
3934 
3935         /* Overrides _addRow method to re-load table when a new row is created.
3936         *************************************************************************/
3937         _addRow: function ($row, options) {
3938             if (options && options.isNewRow && this.options.paging) {
3939                 this._reloadTable();
3940                 return;
3941             }
3942 
3943             base._addRow.apply(this, arguments);
3944         },
3945 
3946         /* Overrides _removeRowsFromTable method to re-load table when a row is removed from table.
3947         *************************************************************************/
3948         _removeRowsFromTable: function ($rows, reason) {
3949             base._removeRowsFromTable.apply(this, arguments);
3950 
3951             if (this.options.paging) {
3952                 if (this._$tableRows.length <= 0 && this._currentPageNo > 1) {
3953                     --this._currentPageNo;
3954                 }
3955 
3956                 this._reloadTable();
3957             }
3958         },
3959 
3960         /* Overrides _onRecordsLoaded method to to do paging specific tasks.
3961         *************************************************************************/
3962         _onRecordsLoaded: function (data) {
3963             if (this.options.paging) {
3964                 this._totalRecordCount = data.TotalRecordCount;
3965                 this._createPagingList();
3966                 this._createPagingInfo();
3967                 this._refreshGotoPageInput();
3968             }
3969 
3970             base._onRecordsLoaded.apply(this, arguments);
3971         },
3972 
3973         /************************************************************************
3974         * PRIVATE METHODS                                                       *
3975         *************************************************************************/
3976 
3977         /* Adds jtStartIndex and jtPageSize parameters to a URL as query string.
3978         *************************************************************************/
3979         _addPagingInfoToUrl: function (url, pageNumber) {
3980             if (!this.options.paging) {
3981                 return url;
3982             }
3983 
3984             var jtStartIndex = (pageNumber - 1) * this.options.pageSize;
3985             var jtPageSize = this.options.pageSize;
3986 
3987             return (url + (url.indexOf('?') < 0 ? '?' : '&') + 'jtStartIndex=' + jtStartIndex + '&jtPageSize=' + jtPageSize);
3988         },
3989 
3990         /* Creates and shows the page list.
3991         *************************************************************************/
3992         _createPagingList: function () {
3993             if (this.options.pageSize <= 0) {
3994                 return;
3995             }
3996 
3997             this._$pagingListArea.empty();
3998             if (this._totalRecordCount <= 0) {
3999                 return;
4000             }
4001 
4002             var pageCount = this._calculatePageCount();
4003 
4004             this._createFirstAndPreviousPageButtons();
4005             if (this.options.pageList == 'normal') {
4006                 this._createPageNumberButtons(this._calculatePageNumbers(pageCount));
4007             }
4008             this._createLastAndNextPageButtons(pageCount);
4009             this._bindClickEventsToPageNumberButtons();
4010         },
4011 
4012         /* Creates and shows previous and first page links.
4013         *************************************************************************/
4014         _createFirstAndPreviousPageButtons: function () {
4015             var $first = $('<span></span>')
4016                 .addClass('jtable-page-number-first')
4017                 .html('&lt&lt')
4018                 .data('pageNumber', 1)
4019                 .appendTo(this._$pagingListArea);
4020 
4021             var $previous = $('<span></span>')
4022                 .addClass('jtable-page-number-previous')
4023                 .html('&lt')
4024                 .data('pageNumber', this._currentPageNo - 1)
4025                 .appendTo(this._$pagingListArea);
4026 
4027             this._jqueryuiThemeAddClass($first, 'ui-button ui-state-default', 'ui-state-hover');
4028             this._jqueryuiThemeAddClass($previous, 'ui-button ui-state-default', 'ui-state-hover');
4029 
4030             if (this._currentPageNo <= 1) {
4031                 $first.addClass('jtable-page-number-disabled');
4032                 $previous.addClass('jtable-page-number-disabled');
4033                 this._jqueryuiThemeAddClass($first, 'ui-state-disabled');
4034                 this._jqueryuiThemeAddClass($previous, 'ui-state-disabled');
4035             }
4036         },
4037 
4038         /* Creates and shows next and last page links.
4039         *************************************************************************/
4040         _createLastAndNextPageButtons: function (pageCount) {
4041             var $next = $('<span></span>')
4042                 .addClass('jtable-page-number-next')
4043                 .html('&gt')
4044                 .data('pageNumber', this._currentPageNo + 1)
4045                 .appendTo(this._$pagingListArea);
4046             var $last = $('<span></span>')
4047                 .addClass('jtable-page-number-last')
4048                 .html('&gt&gt')
4049                 .data('pageNumber', pageCount)
4050                 .appendTo(this._$pagingListArea);
4051 
4052             this._jqueryuiThemeAddClass($next, 'ui-button ui-state-default', 'ui-state-hover');
4053             this._jqueryuiThemeAddClass($last, 'ui-button ui-state-default', 'ui-state-hover');
4054 
4055             if (this._currentPageNo >= pageCount) {
4056                 $next.addClass('jtable-page-number-disabled');
4057                 $last.addClass('jtable-page-number-disabled');
4058                 this._jqueryuiThemeAddClass($next, 'ui-state-disabled');
4059                 this._jqueryuiThemeAddClass($last, 'ui-state-disabled');
4060             }
4061         },
4062 
4063         /* Creates and shows page number links for given number array.
4064         *************************************************************************/
4065         _createPageNumberButtons: function (pageNumbers) {
4066             var previousNumber = 0;
4067             for (var i = 0; i < pageNumbers.length; i++) {
4068                 //Create "..." between page numbers if needed
4069                 if ((pageNumbers[i] - previousNumber) > 1) {
4070                     $('<span></span>')
4071                         .addClass('jtable-page-number-space')
4072                         .html('...')
4073                         .appendTo(this._$pagingListArea);
4074                 }
4075 
4076                 this._createPageNumberButton(pageNumbers[i]);
4077                 previousNumber = pageNumbers[i];
4078             }
4079         },
4080 
4081         /* Creates a page number link and adds to paging area.
4082         *************************************************************************/
4083         _createPageNumberButton: function (pageNumber) {
4084             var $pageNumber = $('<span></span>')
4085                 .addClass('jtable-page-number')
4086                 .html(pageNumber)
4087                 .data('pageNumber', pageNumber)
4088                 .appendTo(this._$pagingListArea);
4089 
4090             this._jqueryuiThemeAddClass($pageNumber, 'ui-button ui-state-default', 'ui-state-hover');
4091             
4092             if (this._currentPageNo == pageNumber) {
4093                 $pageNumber.addClass('jtable-page-number-active jtable-page-number-disabled');
4094                 this._jqueryuiThemeAddClass($pageNumber, 'ui-state-active');
4095             }
4096         },
4097 
4098         /* Calculates total page count according to page size and total record count.
4099         *************************************************************************/
4100         _calculatePageCount: function () {
4101             var pageCount = Math.floor(this._totalRecordCount / this.options.pageSize);
4102             if (this._totalRecordCount % this.options.pageSize != 0) {
4103                 ++pageCount;
4104             }
4105 
4106             return pageCount;
4107         },
4108 
4109         /* Calculates page numbers and returns an array of these numbers.
4110         *************************************************************************/
4111         _calculatePageNumbers: function (pageCount) {
4112             if (pageCount <= 4) {
4113                 //Show all pages
4114                 var pageNumbers = [];
4115                 for (var i = 1; i <= pageCount; ++i) {
4116                     pageNumbers.push(i);
4117                 }
4118 
4119                 return pageNumbers;
4120             } else {
4121                 //show first three, last three, current, previous and next page numbers
4122                 var shownPageNumbers = [1, 2, pageCount - 1, pageCount];
4123                 var previousPageNo = this._normalizeNumber(this._currentPageNo - 1, 1, pageCount, 1);
4124                 var nextPageNo = this._normalizeNumber(this._currentPageNo + 1, 1, pageCount, 1);
4125 
4126                 this._insertToArrayIfDoesNotExists(shownPageNumbers, previousPageNo);
4127                 this._insertToArrayIfDoesNotExists(shownPageNumbers, this._currentPageNo);
4128                 this._insertToArrayIfDoesNotExists(shownPageNumbers, nextPageNo);
4129 
4130                 shownPageNumbers.sort(function (a, b) { return a - b; });
4131                 return shownPageNumbers;
4132             }
4133         },
4134 
4135         /* Creates and shows paging informations.
4136         *************************************************************************/
4137         _createPagingInfo: function () {
4138             if (this._totalRecordCount <= 0) {
4139                 this._$pageInfoSpan.empty();
4140                 return;
4141             }
4142 
4143             var startNo = (this._currentPageNo - 1) * this.options.pageSize + 1;
4144             var endNo = this._currentPageNo * this.options.pageSize;
4145             endNo = this._normalizeNumber(endNo, startNo, this._totalRecordCount, 0);
4146 
4147             if (endNo >= startNo) {
4148                 var pagingInfoMessage = this._formatString(this.options.messages.pagingInfo, startNo, endNo, this._totalRecordCount);
4149                 this._$pageInfoSpan.html(pagingInfoMessage);
4150             }
4151         },
4152 
4153         /* Binds click events of all page links to change the page.
4154         *************************************************************************/
4155         _bindClickEventsToPageNumberButtons: function () {
4156             var self = this;
4157             self._$pagingListArea
4158                 .find('.jtable-page-number,.jtable-page-number-previous,.jtable-page-number-next,.jtable-page-number-first,.jtable-page-number-last')
4159                 .not('.jtable-page-number-disabled')
4160                 .click(function (e) {
4161                     e.preventDefault();
4162                     self._changePage($(this).data('pageNumber'));
4163                 });
4164         },
4165 
4166         /* Changes current page to given value.
4167         *************************************************************************/
4168         _changePage: function (pageNo) {
4169             pageNo = this._normalizeNumber(pageNo, 1, this._calculatePageCount(), 1);
4170             if (pageNo == this._currentPageNo) {
4171                 this._refreshGotoPageInput();
4172                 return;
4173             }
4174 
4175             this._currentPageNo = pageNo;
4176             this._reloadTable();
4177         }
4178 
4179     });
4180 
4181 })(jQuery);
4182 
4183 
4184 /************************************************************************
4185 * SORTING extension for jTable                                          *
4186 *************************************************************************/
4187 (function ($) {
4188 
4189     //Reference to base object members
4190     var base = {
4191         _initializeFields: $.hik.jtable.prototype._initializeFields,
4192         _normalizeFieldOptions: $.hik.jtable.prototype._normalizeFieldOptions,
4193         _createHeaderCellForField: $.hik.jtable.prototype._createHeaderCellForField,
4194         _createRecordLoadUrl: $.hik.jtable.prototype._createRecordLoadUrl,
4195         _createJtParamsForLoading: $.hik.jtable.prototype._createJtParamsForLoading
4196     };
4197 
4198     //extension members
4199     $.extend(true, $.hik.jtable.prototype, {
4200 
4201         /************************************************************************
4202         * DEFAULT OPTIONS / EVENTS                                              *
4203         *************************************************************************/
4204         options: {
4205             sorting: false,
4206             multiSorting: false,
4207             defaultSorting: ''
4208         },
4209 
4210         /************************************************************************
4211         * PRIVATE FIELDS                                                        *
4212         *************************************************************************/
4213 
4214         _lastSorting: null, //Last sorting of the table
4215 
4216         /************************************************************************
4217         * OVERRIDED METHODS                                                     *
4218         *************************************************************************/
4219 
4220         /* Overrides base method to create sorting array.
4221         *************************************************************************/
4222         _initializeFields: function () {
4223             base._initializeFields.apply(this, arguments);
4224 
4225             this._lastSorting = [];
4226             if (this.options.sorting) {
4227                 this._buildDefaultSortingArray();
4228             }
4229         },
4230 
4231         /* Overrides _normalizeFieldOptions method to normalize sorting option for fields.
4232         *************************************************************************/
4233         _normalizeFieldOptions: function (fieldName, props) {
4234             base._normalizeFieldOptions.apply(this, arguments);
4235             props.sorting = (props.sorting != false);
4236         },
4237 
4238         /* Overrides _createHeaderCellForField to make columns sortable.
4239         *************************************************************************/
4240         _createHeaderCellForField: function (fieldName, field) {
4241             var $headerCell = base._createHeaderCellForField.apply(this, arguments);
4242             if (this.options.sorting && field.sorting) {
4243                 this._makeColumnSortable($headerCell, fieldName);
4244             }
4245 
4246             return $headerCell;
4247         },
4248 
4249         /* Overrides _createRecordLoadUrl to add sorting specific info to URL.
4250         *************************************************************************/
4251         _createRecordLoadUrl: function () {
4252             var loadUrl = base._createRecordLoadUrl.apply(this, arguments);
4253             loadUrl = this._addSortingInfoToUrl(loadUrl);
4254             return loadUrl;
4255         },
4256 
4257         /************************************************************************
4258         * PRIVATE METHODS                                                       *
4259         *************************************************************************/
4260 
4261         /* Builds the sorting array according to defaultSorting string
4262         *************************************************************************/
4263         _buildDefaultSortingArray: function () {
4264             var self = this;
4265 
4266             $.each(self.options.defaultSorting.split(","), function (orderIndex, orderValue) {
4267                 $.each(self.options.fields, function (fieldName, fieldProps) {
4268                     if (fieldProps.sorting) {
4269                         var colOffset = orderValue.indexOf(fieldName);
4270                         if (colOffset > -1) {
4271                             if (orderValue.toUpperCase().indexOf(' DESC', colOffset) > -1) {
4272                                 self._lastSorting.push({
4273                                     fieldName: fieldName,
4274                                     sortOrder: 'DESC'
4275                                 });
4276                             } else {
4277                                 self._lastSorting.push({
4278                                     fieldName: fieldName,
4279                                     sortOrder: 'ASC'
4280                                 });
4281                             }
4282                         }
4283                     }
4284                 });
4285             });
4286         },
4287 
4288         /* Makes a column sortable.
4289         *************************************************************************/
4290         _makeColumnSortable: function ($columnHeader, fieldName) {
4291             var self = this;
4292             
4293             $columnHeader
4294                 .addClass('jtable-column-header-sortable')
4295                 .click(function (e) {
4296                     e.preventDefault();
4297 
4298                     if (!self.options.multiSorting || !e.ctrlKey) {
4299                         self._lastSorting = []; //clear previous sorting
4300                     }
4301                     
4302                     self._sortTableByColumn($columnHeader);
4303                 });
4304 
4305             //Set default sorting
4306             $.each(this._lastSorting, function (sortIndex, sortField) {
4307                 if (sortField.fieldName == fieldName) {
4308                     if (sortField.sortOrder == 'DESC') {
4309                         $columnHeader.addClass('jtable-column-header-sorted-desc');
4310                     } else {
4311                         $columnHeader.addClass('jtable-column-header-sorted-asc');
4312                     }
4313                 }
4314             });
4315         },
4316 
4317         /* Sorts table according to a column header.
4318         *************************************************************************/
4319         _sortTableByColumn: function ($columnHeader) {
4320             //Remove sorting styles from all columns except this one
4321             if (this._lastSorting.length == 0) {
4322                 $columnHeader.siblings().removeClass('jtable-column-header-sorted-asc jtable-column-header-sorted-desc');
4323             }
4324 
4325             //If current sorting list includes this column, remove it from the list
4326             for (var i = 0; i < this._lastSorting.length; i++) {
4327                 if (this._lastSorting[i].fieldName == $columnHeader.data('fieldName')) {
4328                     this._lastSorting.splice(i--, 1);
4329                 }
4330             }
4331 
4332             //Sort ASC or DESC according to current sorting state
4333             if ($columnHeader.hasClass('jtable-column-header-sorted-asc')) {
4334                 $columnHeader.removeClass('jtable-column-header-sorted-asc').addClass('jtable-column-header-sorted-desc');
4335                 this._lastSorting.push({
4336                     'fieldName': $columnHeader.data('fieldName'),
4337                     sortOrder: 'DESC'
4338                 });
4339             } else {
4340                 $columnHeader.removeClass('jtable-column-header-sorted-desc').addClass('jtable-column-header-sorted-asc');
4341                 this._lastSorting.push({
4342                     'fieldName': $columnHeader.data('fieldName'),
4343                     sortOrder: 'ASC'
4344                 });
4345             }
4346 
4347             //Load current page again
4348             this._reloadTable();
4349         },
4350 
4351         /* Adds jtSorting parameter to a URL as query string.
4352         *************************************************************************/
4353         _addSortingInfoToUrl: function (url) {
4354             if (!this.options.sorting || this._lastSorting.length == 0) {
4355                 return url;
4356             }
4357 
4358             var sorting = [];
4359             $.each(this._lastSorting, function (idx, value) {
4360                 sorting.push(value.fieldName + ' ' + value.sortOrder);
4361             });
4362 
4363             return (url + (url.indexOf('?') < 0 ? '?' : '&') + 'jtSorting=' + sorting.join(","));
4364         },
4365 
4366         /* Overrides _createJtParamsForLoading method to add sorging parameters to jtParams object.
4367         *************************************************************************/
4368         _createJtParamsForLoading: function () {
4369             var jtParams = base._createJtParamsForLoading.apply(this, arguments);
4370 
4371             if (this.options.sorting && this._lastSorting.length) {
4372                 var sorting = [];
4373                 $.each(this._lastSorting, function (idx, value) {
4374                     sorting.push(value.fieldName + ' ' + value.sortOrder);
4375                 });
4376 
4377                 jtParams.jtSorting = sorting.join(",");
4378             }
4379 
4380             return jtParams;
4381         }
4382 
4383     });
4384 
4385 })(jQuery);
4386 
4387 /************************************************************************
4388 * DYNAMIC COLUMNS extension for jTable                                  *
4389 * (Show/hide/resize columns)                                            *
4390 *************************************************************************/
4391 (function ($) {
4392 
4393     //Reference to base object members
4394     var base = {
4395         _create: $.hik.jtable.prototype._create,
4396         _normalizeFieldOptions: $.hik.jtable.prototype._normalizeFieldOptions,
4397         _createHeaderCellForField: $.hik.jtable.prototype._createHeaderCellForField,
4398         _createCellForRecordField: $.hik.jtable.prototype._createCellForRecordField
4399     };
4400 
4401     //extension members
4402     $.extend(true, $.hik.jtable.prototype, {
4403 
4404         /************************************************************************
4405         * DEFAULT OPTIONS / EVENTS                                              *
4406         *************************************************************************/
4407 
4408         options: {
4409             tableId: undefined,
4410             columnResizable: true,
4411             columnSelectable: true
4412         },
4413 
4414         /************************************************************************
4415         * PRIVATE FIELDS                                                        *
4416         *************************************************************************/
4417 
4418         _$columnSelectionDiv: null,
4419         _$columnResizeBar: null,
4420         _cookieKeyPrefix: null,
4421         _currentResizeArgs: null,
4422 
4423         /************************************************************************
4424         * OVERRIDED METHODS                                                     *
4425         *************************************************************************/
4426 
4427         /* Overrides _addRowToTableHead method.
4428         *************************************************************************/
4429 
4430         _create: function () {
4431             base._create.apply(this, arguments);
4432 
4433             this._createColumnResizeBar();
4434             this._createColumnSelection();
4435 
4436             if (this.options.saveUserPreferences) {
4437                 this._loadColumnSettings();
4438             }
4439 
4440             this._normalizeColumnWidths();
4441         },
4442 
4443         /* Normalizes some options for a field (sets default values).
4444         *************************************************************************/
4445         _normalizeFieldOptions: function (fieldName, props) {
4446             base._normalizeFieldOptions.apply(this, arguments);
4447 
4448             //columnResizable
4449             if (this.options.columnResizable) {
4450                 props.columnResizable = (props.columnResizable != false);
4451             }
4452 
4453             //visibility
4454             if (!props.visibility) {
4455                 props.visibility = 'visible';
4456             }
4457         },
4458 
4459         /* Overrides _createHeaderCellForField to make columns dynamic.
4460         *************************************************************************/
4461         _createHeaderCellForField: function (fieldName, field) {
4462             var $headerCell = base._createHeaderCellForField.apply(this, arguments);
4463 
4464             //Make data columns resizable except the last one
4465             if (this.options.columnResizable && field.columnResizable && (fieldName != this._columnList[this._columnList.length - 1])) {
4466                 this._makeColumnResizable($headerCell);
4467             }
4468 
4469             //Hide column if needed
4470             if (field.visibility == 'hidden') {
4471                 $headerCell.hide();
4472             }
4473 
4474             return $headerCell;
4475         },
4476 
4477         /* Overrides _createHeaderCellForField to decide show or hide a column.
4478         *************************************************************************/
4479         _createCellForRecordField: function (record, fieldName) {
4480             var $column = base._createCellForRecordField.apply(this, arguments);
4481 
4482             var field = this.options.fields[fieldName];
4483             if (field.visibility == 'hidden') {
4484                 $column.hide();
4485             }
4486 
4487             return $column;
4488         },
4489 
4490         /************************************************************************
4491         * PUBLIC METHODS                                                        *
4492         *************************************************************************/
4493 
4494         /* Changes visibility of a column.
4495         *************************************************************************/
4496         changeColumnVisibility: function (columnName, visibility) {
4497             this._changeColumnVisibilityInternal(columnName, visibility);
4498             this._normalizeColumnWidths();
4499             if (this.options.saveUserPreferences) {
4500                 this._saveColumnSettings();
4501             }
4502         },
4503 
4504         /************************************************************************
4505         * PRIVATE METHODS                                                       *
4506         *************************************************************************/
4507 
4508         /* Changes visibility of a column.
4509         *************************************************************************/
4510         _changeColumnVisibilityInternal: function (columnName, visibility) {
4511             //Check if there is a column with given name
4512             var columnIndex = this._columnList.indexOf(columnName);
4513             if (columnIndex < 0) {
4514                 this._logWarn('Column "' + columnName + '" does not exist in fields!');
4515                 return;
4516             }
4517 
4518             //Check if visibility value is valid
4519             if (['visible', 'hidden', 'fixed'].indexOf(visibility) < 0) {
4520                 this._logWarn('Visibility value is not valid: "' + visibility + '"! Options are: visible, hidden, fixed.');
4521                 return;
4522             }
4523 
4524             //Get the field
4525             var field = this.options.fields[columnName];
4526             if (field.visibility == visibility) {
4527                 return; //No action if new value is same as old one.
4528             }
4529 
4530             //Hide or show the column if needed
4531             var columnIndexInTable = this._firstDataColumnOffset + columnIndex + 1;
4532             if (field.visibility != 'hidden' && visibility == 'hidden') {
4533                 this._$table
4534                     .find('>thead >tr >th:nth-child(' + columnIndexInTable + '),>tbody >tr >td:nth-child(' + columnIndexInTable + ')')
4535                     .hide();
4536             } else if (field.visibility == 'hidden' && visibility != 'hidden') {
4537                 this._$table
4538                     .find('>thead >tr >th:nth-child(' + columnIndexInTable + '),>tbody >tr >td:nth-child(' + columnIndexInTable + ')')
4539                     .show()
4540                     .css('display', 'table-cell');
4541             }
4542 
4543             field.visibility = visibility;
4544         },
4545 
4546         /* Prepares dialog to change settings.
4547         *************************************************************************/
4548         _createColumnSelection: function () {
4549             var self = this;
4550 
4551             //Create a div for dialog and add to container element
4552             this._$columnSelectionDiv = $('<div />')
4553                 .addClass('jtable-column-selection-container')
4554                 .appendTo(self._$mainContainer);
4555             
4556             this._$table.children('thead').bind('contextmenu', function (e) {
4557                 if (!self.options.columnSelectable) {
4558                     return;
4559                 }
4560                 
4561                 e.preventDefault();
4562 
4563                 //Make an overlay div to disable page clicks
4564                 $('<div />')
4565                     .addClass('jtable-contextmenu-overlay')
4566                     .click(function () {
4567                         $(this).remove();
4568                         self._$columnSelectionDiv.hide();
4569                     })
4570                     .bind('contextmenu', function () { return false; })
4571                     .appendTo(document.body);
4572 
4573                 self._fillColumnSelection();
4574                 
4575                 //Calculate position of column selection list and show it
4576 
4577                 var containerOffset = self._$mainContainer.offset();
4578                 var selectionDivTop = e.pageY - containerOffset.top;
4579                 var selectionDivLeft = e.pageX - containerOffset.left;
4580                 
4581                 var selectionDivMinWidth = 100; //in pixels
4582                 var containerWidth = self._$mainContainer.width();
4583 
4584                 //If user clicks right area of header of the table, show list at a little left
4585                 if ((containerWidth > selectionDivMinWidth) && (selectionDivLeft > (containerWidth - selectionDivMinWidth))) {
4586                     selectionDivLeft = containerWidth - selectionDivMinWidth;
4587                 }
4588 
4589                 self._$columnSelectionDiv.css({
4590                     left: selectionDivLeft,
4591                     top: selectionDivTop,
4592                     'min-width': selectionDivMinWidth + 'px'
4593                 }).show();
4594             });
4595         },
4596         
4597         /* Prepares content of settings dialog.
4598         *************************************************************************/
4599         _fillColumnSelection: function () {
4600             var self = this;
4601 
4602             var $columnsUl = $('<ul></ul>')
4603                 .addClass('jtable-column-select-list');
4604             for (var i = 0; i < this._columnList.length; i++) {
4605                 var columnName = this._columnList[i];
4606                 var field = this.options.fields[columnName];
4607 
4608                 //Crete li element
4609                 var $columnLi = $('<li></li>').appendTo($columnsUl);
4610 
4611                 //Create label for the checkbox
4612                 var $label = $('<label for="' + columnName + '"></label>')
4613                     .append($('<span>' + (field.title || columnName) + '</span>'))
4614                     .appendTo($columnLi);
4615 
4616                 //Create checkbox
4617                 var $checkbox = $('<input type="checkbox" name="' + columnName + '">')
4618                     .prependTo($label)
4619                     .click(function () {
4620                         var $clickedCheckbox = $(this);
4621                         var clickedColumnName = $clickedCheckbox.attr('name');
4622                         var clickedField = self.options.fields[clickedColumnName];
4623                         if (clickedField.visibility == 'fixed') {
4624                             return;
4625                         }
4626 
4627                         self.changeColumnVisibility(clickedColumnName, $clickedCheckbox.is(':checked') ? 'visible' : 'hidden');
4628                     });
4629 
4630                 //Check, if column if shown
4631                 if (field.visibility != 'hidden') {
4632                     $checkbox.attr('checked', 'checked');
4633                 }
4634 
4635                 //Disable, if column is fixed
4636                 if (field.visibility == 'fixed') {
4637                     $checkbox.attr('disabled', 'disabled');
4638                 }
4639             }
4640 
4641             this._$columnSelectionDiv.html($columnsUl);
4642         },
4643 
4644         /* creates a vertical bar that is shown while resizing columns.
4645         *************************************************************************/
4646         _createColumnResizeBar: function () {
4647             this._$columnResizeBar = $('<div />')
4648                 .addClass('jtable-column-resize-bar')
4649                 .appendTo(this._$mainContainer)
4650                 .hide();
4651         },
4652 
4653         /* Makes a column sortable.
4654         *************************************************************************/
4655         _makeColumnResizable: function ($columnHeader) {
4656             var self = this;
4657 
4658             //Create a handler to handle mouse click event
4659             $('<div />')
4660                 .addClass('jtable-column-resize-handler')
4661                 .appendTo($columnHeader.find('.jtable-column-header-container')) //Append the handler to the column
4662                 .mousedown(function (downevent) { //handle mousedown event for the handler
4663                     downevent.preventDefault();
4664                     downevent.stopPropagation();
4665 
4666                     var mainContainerOffset = self._$mainContainer.offset();
4667 
4668                     //Get a reference to the next column
4669                     var $nextColumnHeader = $columnHeader.nextAll('th.jtable-column-header:visible:first');
4670                     if (!$nextColumnHeader.length) {
4671                         return;
4672                     }
4673 
4674                     //Store some information to be used on resizing
4675                     var minimumColumnWidth = 10; //A column's width can not be smaller than 10 pixel.
4676                     self._currentResizeArgs = {
4677                         currentColumnStartWidth: $columnHeader.outerWidth(),
4678                         minWidth: minimumColumnWidth,
4679                         maxWidth: $columnHeader.outerWidth() + $nextColumnHeader.outerWidth() - minimumColumnWidth,
4680                         mouseStartX: downevent.pageX,
4681                         minResizeX: function () { return this.mouseStartX - (this.currentColumnStartWidth - this.minWidth); },
4682                         maxResizeX: function () { return this.mouseStartX + (this.maxWidth - this.currentColumnStartWidth); }
4683                     };
4684 
4685                     //Handle mouse move event to move resizing bar
4686                     var resizeonmousemove = function (moveevent) {
4687                         if (!self._currentResizeArgs) {
4688                             return;
4689                         }
4690 
4691                         var resizeBarX = self._normalizeNumber(moveevent.pageX, self._currentResizeArgs.minResizeX(), self._currentResizeArgs.maxResizeX());
4692                         self._$columnResizeBar.css('left', (resizeBarX - mainContainerOffset.left) + 'px');
4693                     };
4694 
4695                     //Handle mouse up event to finish resizing of the column
4696                     var resizeonmouseup = function (upevent) {
4697                         if (!self._currentResizeArgs) {
4698                             return;
4699                         }
4700 
4701                         $(document).unbind('mousemove', resizeonmousemove);
4702                         $(document).unbind('mouseup', resizeonmouseup);
4703 
4704                         self._$columnResizeBar.hide();
4705 
4706                         //Calculate new widths in pixels
4707                         var mouseChangeX = upevent.pageX - self._currentResizeArgs.mouseStartX;
4708                         var currentColumnFinalWidth = self._normalizeNumber(self._currentResizeArgs.currentColumnStartWidth + mouseChangeX, self._currentResizeArgs.minWidth, self._currentResizeArgs.maxWidth);
4709                         var nextColumnFinalWidth = $nextColumnHeader.outerWidth() + (self._currentResizeArgs.currentColumnStartWidth - currentColumnFinalWidth);
4710 
4711                         //Calculate widths as percent
4712                         var pixelToPercentRatio = $columnHeader.data('width-in-percent') / self._currentResizeArgs.currentColumnStartWidth;
4713                         $columnHeader.data('width-in-percent', currentColumnFinalWidth * pixelToPercentRatio);
4714                         $nextColumnHeader.data('width-in-percent', nextColumnFinalWidth * pixelToPercentRatio);
4715 
4716                         //Set new widths to columns (resize!)
4717                         $columnHeader.css('width', $columnHeader.data('width-in-percent') + '%');
4718                         $nextColumnHeader.css('width', $nextColumnHeader.data('width-in-percent') + '%');
4719 
4720                         //Normalize all column widths
4721                         self._normalizeColumnWidths();
4722 
4723                         //Finish resizing
4724                         self._currentResizeArgs = null;
4725 
4726                         //Save current preferences
4727                         if (self.options.saveUserPreferences) {
4728                             self._saveColumnSettings();
4729                         }
4730                     };
4731 
4732                     //Show vertical resize bar
4733                     self._$columnResizeBar
4734                         .show()
4735                         .css({
4736                             top: ($columnHeader.offset().top - mainContainerOffset.top) + 'px',
4737                             left: (downevent.pageX - mainContainerOffset.left) + 'px',
4738                             height: (self._$table.outerHeight()) + 'px'
4739                         });
4740 
4741                     //Bind events
4742                     $(document).bind('mousemove', resizeonmousemove);
4743                     $(document).bind('mouseup', resizeonmouseup);
4744                 });
4745         },
4746 
4747         /* Normalizes column widths as percent for current view.
4748         *************************************************************************/
4749         _normalizeColumnWidths: function () {
4750 
4751             //Set command column width
4752             var commandColumnHeaders = this._$table
4753                 .find('>thead th.jtable-command-column-header')
4754                 .data('width-in-percent', 1)
4755                 .css('width', '1%');
4756 
4757             //Find data columns
4758             var headerCells = this._$table.find('>thead th.jtable-column-header');
4759 
4760             //Calculate total width of data columns
4761             var totalWidthInPixel = 0;
4762             headerCells.each(function () {
4763                 var $cell = $(this);
4764                 if ($cell.is(':visible')) {
4765                     totalWidthInPixel += $cell.outerWidth();
4766                 }
4767             });
4768 
4769             //Calculate width of each column
4770             var columnWidhts = {};
4771             var availableWidthInPercent = 100.0 - commandColumnHeaders.length;
4772             headerCells.each(function () {
4773                 var $cell = $(this);
4774                 if ($cell.is(':visible')) {
4775                     var fieldName = $cell.data('fieldName');
4776                     var widthInPercent = $cell.outerWidth() * availableWidthInPercent / totalWidthInPixel;
4777                     columnWidhts[fieldName] = widthInPercent;
4778                 }
4779             });
4780 
4781             //Set width of each column
4782             headerCells.each(function () {
4783                 var $cell = $(this);
4784                 if ($cell.is(':visible')) {
4785                     var fieldName = $cell.data('fieldName');
4786                     $cell.data('width-in-percent', columnWidhts[fieldName]).css('width', columnWidhts[fieldName] + '%');
4787                 }
4788             });
4789         },
4790 
4791         /* Saves field setting to cookie.
4792         *  Saved setting will be a string like that:
4793         * fieldName1=visible;23|fieldName2=hidden;17|...
4794         *************************************************************************/
4795         _saveColumnSettings: function () {
4796             var self = this;
4797             var fieldSettings = '';
4798             this._$table.find('>thead >tr >th.jtable-column-header').each(function () {
4799                 var $cell = $(this);
4800                 var fieldName = $cell.data('fieldName');
4801                 var columnWidth = $cell.data('width-in-percent');
4802                 var fieldVisibility = self.options.fields[fieldName].visibility;
4803                 var fieldSetting = fieldName + "=" + fieldVisibility + ';' + columnWidth;
4804                 fieldSettings = fieldSettings + fieldSetting + '|';
4805             });
4806 
4807             this._setCookie('column-settings', fieldSettings.substr(0, fieldSettings.length - 1));
4808         },
4809 
4810         /* Loads field settings from cookie that is saved by _saveFieldSettings method.
4811         *************************************************************************/
4812         _loadColumnSettings: function () {
4813             var self = this;
4814             var columnSettingsCookie = this._getCookie('column-settings');
4815             if (!columnSettingsCookie) {
4816                 return;
4817             }
4818 
4819             var columnSettings = {};
4820             $.each(columnSettingsCookie.split('|'), function (inx, fieldSetting) {
4821                 var splitted = fieldSetting.split('=');
4822                 var fieldName = splitted[0];
4823                 var settings = splitted[1].split(';');
4824                 columnSettings[fieldName] = {
4825                     columnVisibility: settings[0],
4826                     columnWidth: settings[1]
4827                 };
4828             });
4829 
4830             var headerCells = this._$table.find('>thead >tr >th.jtable-column-header');
4831             headerCells.each(function () {
4832                 var $cell = $(this);
4833                 var fieldName = $cell.data('fieldName');
4834                 var field = self.options.fields[fieldName];
4835                 if (columnSettings[fieldName]) {
4836                     if (field.visibility != 'fixed') {
4837                         self._changeColumnVisibilityInternal(fieldName, columnSettings[fieldName].columnVisibility);
4838                     }
4839 
4840                     $cell.data('width-in-percent', columnSettings[fieldName].columnWidth).css('width', columnSettings[fieldName].columnWidth + '%');
4841                 }
4842             });
4843         }
4844 
4845     });
4846 
4847 })(jQuery);
4848 
4849 
4850 /************************************************************************
4851 * MASTER/CHILD tables extension for jTable                              *
4852 *************************************************************************/
4853 (function ($) {
4854 
4855     //Reference to base object members
4856     var base = {
4857         _removeRowsFromTable: $.hik.jtable.prototype._removeRowsFromTable
4858     };
4859 
4860     //extension members
4861     $.extend(true, $.hik.jtable.prototype, {
4862 
4863         /************************************************************************
4864         * DEFAULT OPTIONS / EVENTS                                              *
4865         *************************************************************************/
4866         options: {
4867             openChildAsAccordion: false
4868         },
4869 
4870         /************************************************************************
4871         * PUBLIC METHODS                                                        *
4872         *************************************************************************/
4873 
4874         /* Creates and opens a new child table for given row.
4875         *************************************************************************/
4876         openChildTable: function ($row, tableOptions, opened) {
4877             var self = this;
4878 
4879             //Apply theming as same as parent table unless explicitily set
4880             if (tableOptions.jqueryuiTheme == undefined) {
4881                 tableOptions.jqueryuiTheme = self.options.jqueryuiTheme;
4882             }
4883 
4884             //Show close button as default
4885             tableOptions.showCloseButton = (tableOptions.showCloseButton != false);
4886 
4887             //Close child table when close button is clicked (default behavior)
4888             if (tableOptions.showCloseButton && !tableOptions.closeRequested) {
4889                 tableOptions.closeRequested = function () {
4890                     self.closeChildTable($row);
4891                 };
4892             }
4893 
4894             //If accordion style, close open child table (if it does exists)
4895             if (self.options.openChildAsAccordion) {
4896                 $row.siblings('.jtable-data-row').each(function () {
4897                     self.closeChildTable($(this));
4898                 });
4899             }
4900 
4901             //Close child table for this row and open new one for child table
4902             self.closeChildTable($row, function () {
4903                 var $childRowColumn = self.getChildRow($row).children('td').empty();
4904                 var $childTableContainer = $('<div />')
4905                     .addClass('jtable-child-table-container')
4906                     .appendTo($childRowColumn);
4907                 $childRowColumn.data('childTable', $childTableContainer);
4908                 $childTableContainer.jtable(tableOptions);
4909                 self.openChildRow($row);
4910                 $childTableContainer.hide().slideDown('fast', function () {
4911                     if (opened) {
4912                         opened({
4913                              childTable: $childTableContainer
4914                         });
4915                     }
4916                 });
4917             });
4918         },
4919 
4920         /* Closes child table for given row.
4921         *************************************************************************/
4922         closeChildTable: function ($row, closed) {
4923             var self = this;
4924             
4925             var $childRowColumn = this.getChildRow($row).children('td');
4926             var $childTable = $childRowColumn.data('childTable');
4927             if (!$childTable) {
4928                 if (closed) {
4929                     closed();
4930                 }
4931 
4932                 return;
4933             }
4934 
4935             $childRowColumn.data('childTable', null);
4936             $childTable.slideUp('fast', function () {
4937                 $childTable.jtable('destroy');
4938                 $childTable.remove();
4939                 self.closeChildRow($row);
4940                 if (closed) {
4941                     closed();
4942                 }
4943             });
4944         },
4945 
4946         /* Returns a boolean value indicates that if a child row is open for given row.
4947         *************************************************************************/
4948         isChildRowOpen: function ($row) {
4949             return (this.getChildRow($row).is(':visible'));
4950         },
4951 
4952         /* Gets child row for given row, opens it if it's closed (Creates if needed).
4953         *************************************************************************/
4954         getChildRow: function ($row) {
4955             return $row.data('childRow') || this._createChildRow($row);
4956         },
4957 
4958         /* Creates and opens child row for given row.
4959         *************************************************************************/
4960         openChildRow: function ($row) {
4961             var $childRow = this.getChildRow($row);
4962             if (!$childRow.is(':visible')) {
4963                 $childRow.show();
4964             }
4965 
4966             return $childRow;
4967         },
4968 
4969         /* Closes child row if it's open.
4970         *************************************************************************/
4971         closeChildRow: function ($row) {
4972             var $childRow = this.getChildRow($row);
4973             if ($childRow.is(':visible')) {
4974                 $childRow.hide();
4975             }
4976         },
4977 
4978         /************************************************************************
4979         * OVERRIDED METHODS                                                     *
4980         *************************************************************************/
4981 
4982         /* Overrides _removeRowsFromTable method to remove child rows of deleted rows.
4983         *************************************************************************/
4984         _removeRowsFromTable: function ($rows, reason) {
4985             //var self = this;
4986 
4987             if (reason == 'deleted') {
4988                 $rows.each(function () {
4989                     var $row = $(this);
4990                     var $childRow = $row.data('childRow');
4991                     if ($childRow) {
4992                         //self.closeChildTable($row); //Removed since it causes "Uncaught Error: cannot call methods on jtable prior to initialization; attempted to call method 'destroy'"
4993                         $childRow.remove();
4994                     }
4995                 });
4996             }
4997 
4998             base._removeRowsFromTable.apply(this, arguments);
4999         },
5000 
5001         /************************************************************************
5002         * PRIVATE METHODS                                                       *
5003         *************************************************************************/
5004 
5005         /* Creates a child row for a row, hides and returns it.
5006         *************************************************************************/
5007         _createChildRow: function ($row) {
5008             var totalColumnCount = this._$table.find('thead th').length;
5009             var $childRow = $('<tr></tr>')
5010                 .addClass('jtable-child-row')
5011                 .append('<td colspan="' + totalColumnCount + '"></td>');
5012             $row.after($childRow);
5013             $row.data('childRow', $childRow);
5014             $childRow.hide();
5015             return $childRow;
5016         }
5017 
5018     });
5019 
5020 })(jQuery);
5021