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('<<') 4018 .data('pageNumber', 1) 4019 .appendTo(this._$pagingListArea); 4020 4021 var $previous = $('<span></span>') 4022 .addClass('jtable-page-number-previous') 4023 .html('<') 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('>') 4044 .data('pageNumber', this._currentPageNo + 1) 4045 .appendTo(this._$pagingListArea); 4046 var $last = $('<span></span>') 4047 .addClass('jtable-page-number-last') 4048 .html('>>') 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