summaryrefslogtreecommitdiffstats
path: root/dev
diff options
context:
space:
mode:
Diffstat (limited to 'dev')
-rw-r--r--dev/jquery.jtable.build.txt13
-rw-r--r--dev/jquery.jtable.core.js800
-rw-r--r--dev/jquery.jtable.creation.js272
-rw-r--r--dev/jquery.jtable.deletion.js387
-rw-r--r--dev/jquery.jtable.dynamiccolumns.js529
-rw-r--r--dev/jquery.jtable.editing.js394
-rw-r--r--dev/jquery.jtable.forms.js376
-rw-r--r--dev/jquery.jtable.header.txt28
-rw-r--r--dev/jquery.jtable.masterchild.js166
-rw-r--r--dev/jquery.jtable.paging.js292
-rw-r--r--dev/jquery.jtable.selecting.js377
-rw-r--r--dev/jquery.jtable.sorting.js123
-rw-r--r--dev/jquery.jtable.utils.js133
13 files changed, 3890 insertions, 0 deletions
diff --git a/dev/jquery.jtable.build.txt b/dev/jquery.jtable.build.txt
new file mode 100644
index 0000000..d561e39
--- /dev/null
+++ b/dev/jquery.jtable.build.txt
@@ -0,0 +1,13 @@
+create ..\jquery.jtable.js
+add jquery.jtable.header.txt
+add jquery.jtable.core.js
+add jquery.jtable.utils.js
+add jquery.jtable.forms.js
+add jquery.jtable.creation.js
+add jquery.jtable.editing.js
+add jquery.jtable.deletion.js
+add jquery.jtable.selecting.js
+add jquery.jtable.paging.js
+add jquery.jtable.sorting.js
+add jquery.jtable.dynamiccolumns.js
+add jquery.jtable.masterchild.js
diff --git a/dev/jquery.jtable.core.js b/dev/jquery.jtable.core.js
new file mode 100644
index 0000000..6e5a091
--- /dev/null
+++ b/dev/jquery.jtable.core.js
@@ -0,0 +1,800 @@
+/************************************************************************
+* CORE jTable module *
+*************************************************************************/
+(function ($) {
+
+ $.widget("hik.jtable", {
+
+ /************************************************************************
+ * DEFAULT OPTIONS / EVENTS *
+ *************************************************************************/
+ options: {
+
+ //Options
+ actions: {},
+ fields: {},
+ animationsEnabled: true,
+ defaultDateFormat: 'yy-mm-dd',
+ dialogShowEffect: 'fade',
+ dialogHideEffect: 'fade',
+ showCloseButton: false,
+ loadingAnimationDelay: 500,
+ ajaxSettings: {
+ type: 'POST',
+ dataType: 'json'
+ },
+
+ //Events
+ closeRequested: function (event, data) { },
+ formCreated: function (event, data) { },
+ formSubmitting: function (event, data) { },
+ formClosed: function (event, data) { },
+ loadingRecords: function (event, data) { },
+ recordsLoaded: function (event, data) { },
+ rowInserted: function (event, data) { },
+ rowsRemoved: function (event, data) { },
+
+ //Localization
+ messages: {
+ serverCommunicationError: 'An error occured while communicating to the server.',
+ loadingMessage: 'Loading records...',
+ noDataAvailable: 'No data available!',
+ areYouSure: 'Are you sure?',
+ save: 'Save',
+ saving: 'Saving',
+ cancel: 'Cancel',
+ error: 'Error',
+ close: 'Close',
+ cannotLoadOptionsFor: 'Can not load options for field {0}'
+ }
+ },
+
+ /************************************************************************
+ * PRIVATE FIELDS *
+ *************************************************************************/
+
+ _$mainContainer: null, //Reference to the main container of all elements that are created by this plug-in (jQuery object)
+
+ _$table: null, //Reference to the main <table> (jQuery object)
+ _$tableBody: null, //Reference to <body> in the table (jQuery object)
+ _$tableRows: null, //Array of all <tr> in the table (except "no data" row) (jQuery object array)
+
+ _$bottomPanel: null, //Reference to the panel at the bottom of the table (jQuery object)
+
+ _$busyDiv: null, //Reference to the div that is used to block UI while busy (jQuery object)
+ _$busyMessageDiv: null, //Reference to the div that is used to show some message when UI is blocked (jQuery object)
+ _$errorDialogDiv: null, //Reference to the error dialog div (jQuery object)
+
+ _columnList: null, //Name of all data columns in the table (select column and command columns are not included) (string array)
+ _fieldList: null, //Name of all fields of a record (defined in fields option) (string array)
+ _keyField: null, //Name of the key field of a record (that is defined as 'key: true' in the fields option) (string)
+
+ _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)
+ _lastPostData: null, //Last posted data on load method (object)
+
+ _cache: null, //General purpose cache dictionary (object)
+
+ /************************************************************************
+ * CONSTRUCTOR AND INITIALIZATION METHODS *
+ *************************************************************************/
+
+ /* Contructor.
+ *************************************************************************/
+ _create: function () {
+
+ //Initialization
+ this._normalizeFieldsOptions();
+ this._initializeFields();
+ this._createFieldAndColumnList();
+
+ //Creating DOM elements
+ this._createMainContainer();
+ this._createTableTitle();
+ this._createTable();
+ this._createBottomPanel();
+ this._createBusyPanel();
+ this._createErrorDialogDiv();
+ this._addNoDataRow();
+ },
+
+ /* Normalizes some options for all fields (sets default values).
+ *************************************************************************/
+ _normalizeFieldsOptions: function () {
+ var self = this;
+ $.each(self.options.fields, function (fieldName, props) {
+ self._normalizeFieldOptions(fieldName, props);
+ });
+ },
+
+ /* Normalizes some options for a field (sets default values).
+ *************************************************************************/
+ _normalizeFieldOptions: function (fieldName, props) {
+ props.listClass = props.listClass || '';
+ props.inputClass = props.inputClass || '';
+ },
+
+ /* Intializes some private variables.
+ *************************************************************************/
+ _initializeFields: function () {
+ this._lastPostData = {};
+ this._$tableRows = [];
+ this._columnList = [];
+ this._fieldList = [];
+ this._cache = [];
+ },
+
+ /* Fills _fieldList, _columnList arrays and sets _keyField variable.
+ *************************************************************************/
+ _createFieldAndColumnList: function () {
+ var self = this;
+
+ $.each(self.options.fields, function (name, props) {
+
+ //Add field to the field list
+ self._fieldList.push(name);
+
+ //Check if this field is the key field
+ if (props.key == true) {
+ self._keyField = name;
+ }
+
+ //Add field to column list if it is shown in the table
+ if (props.list != false && props.type != 'hidden') {
+ self._columnList.push(name);
+ }
+ });
+ },
+
+ /* Creates the main container div.
+ *************************************************************************/
+ _createMainContainer: function () {
+ this._$mainContainer = $('<div />')
+ .addClass('jtable-main-container')
+ .appendTo(this.element);
+ },
+
+ /* Creates title of the table if a title supplied in options.
+ *************************************************************************/
+ _createTableTitle: function () {
+ var self = this;
+
+ if (!self.options.title) {
+ return;
+ }
+
+ var $titleDiv = $('<div />')
+ .addClass('jtable-title')
+ .appendTo(self._$mainContainer);
+
+ $('<div />')
+ .addClass('jtable-title-text')
+ .appendTo($titleDiv)
+ .append(self.options.title);
+
+ if (self.options.showCloseButton) {
+
+ var $textSpan = $('<span />')
+ .html(self.options.messages.close);
+
+ $('<button></button>')
+ .addClass('jtable-command-button jtable-close-button')
+ .attr('title', self.options.messages.close)
+ .append($textSpan)
+ .appendTo($titleDiv)
+ .click(function (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ self._onCloseRequested();
+ });
+ }
+ },
+
+ /* Creates the table.
+ *************************************************************************/
+ _createTable: function () {
+ this._$table = $('<table></table>')
+ .addClass('jtable')
+ .appendTo(this._$mainContainer);
+
+ this._createTableHead();
+ this._createTableBody();
+ },
+
+ /* Creates header (all column headers) of the table.
+ *************************************************************************/
+ _createTableHead: function () {
+ var $thead = $('<thead></thead>')
+ .appendTo(this._$table);
+
+ this._addRowToTableHead($thead);
+ },
+
+ /* Adds tr element to given thead element
+ *************************************************************************/
+ _addRowToTableHead: function ($thead) {
+ var $tr = $('<tr></tr>')
+ .appendTo($thead);
+
+ this._addColumnsToHeaderRow($tr);
+ },
+
+ /* Adds column header cells to given tr element.
+ *************************************************************************/
+ _addColumnsToHeaderRow: function ($tr) {
+ for (var i = 0; i < this._columnList.length; i++) {
+ var fieldName = this._columnList[i];
+ var $headerCell = this._createHeaderCellForField(fieldName, this.options.fields[fieldName]);
+ $headerCell.appendTo($tr);
+ }
+ },
+
+ /* Creates a header cell for given field.
+ * Returns th jQuery object.
+ *************************************************************************/
+ _createHeaderCellForField: function (fieldName, field) {
+ field.width = field.width || '10%'; //default column width: 10%.
+
+ var $headerTextSpan = $('<span />')
+ .addClass('jtable-column-header-text')
+ .html(field.title);
+
+ var $headerContainerDiv = $('<div />')
+ .addClass('jtable-column-header-container')
+ .append($headerTextSpan);
+
+ var $th = $('<th></th>')
+ .addClass('jtable-column-header')
+ .css('width', field.width)
+ .data('fieldName', fieldName)
+ .append($headerContainerDiv);
+
+ return $th;
+ },
+
+ /* Creates an empty header cell that can be used as command column headers.
+ *************************************************************************/
+ _createEmptyCommandHeader: function () {
+ return $('<th></th>')
+ .addClass('jtable-command-column-header')
+ .css('width', '1%');
+ },
+
+ /* Creates tbody tag and adds to the table.
+ *************************************************************************/
+ _createTableBody: function () {
+ this._$tableBody = $('<tbody></tbody>').appendTo(this._$table);
+ },
+
+ /* Creates bottom panel and adds to the page.
+ *************************************************************************/
+ _createBottomPanel: function () {
+ this._$bottomPanel = $('<div />')
+ .addClass('jtable-bottom-panel')
+ .appendTo(this._$mainContainer);
+
+ $('<div />').addClass('jtable-left-area').appendTo(this._$bottomPanel);
+ $('<div />').addClass('jtable-right-area').appendTo(this._$bottomPanel);
+ },
+
+ /* Creates a div to block UI while jTable is busy.
+ *************************************************************************/
+ _createBusyPanel: function () {
+ this._$busyMessageDiv = $('<div />').addClass('jtable-busy-message').prependTo(this._$mainContainer);
+ this._$busyDiv = $('<div />').addClass('jtable-busy-panel-background').prependTo(this._$mainContainer);
+ this._hideBusy();
+ },
+
+ /* Creates and prepares error dialog div.
+ *************************************************************************/
+ _createErrorDialogDiv: function () {
+ var self = this;
+
+ self._$errorDialogDiv = $('<div></div>').appendTo(self._$mainContainer);
+ self._$errorDialogDiv.dialog({
+ autoOpen: false,
+ show: self.options.dialogShowEffect,
+ hide: self.options.dialogHideEffect,
+ modal: true,
+ title: self.options.messages.error,
+ buttons: [{
+ text: self.options.messages.close,
+ click: function () {
+ self._$errorDialogDiv.dialog('close');
+ }
+ }]
+ });
+ },
+
+ /************************************************************************
+ * PUBLIC METHODS *
+ *************************************************************************/
+
+ /* Loads data using AJAX call, clears table and fills with new data.
+ *************************************************************************/
+ load: function (postData, completeCallback) {
+ this._lastPostData = postData;
+ this._reloadTable(completeCallback);
+ },
+
+ /* Refreshes (re-loads) table data with last postData.
+ *************************************************************************/
+ reload: function (completeCallback) {
+ this._reloadTable(completeCallback);
+ },
+
+ /* Gets a jQuery row object according to given record key
+ *************************************************************************/
+ getRowByKey: function (key) {
+ for (var i = 0; i < this._$tableRows.length; i++) {
+ if (key == this._getKeyValueOfRecord(this._$tableRows[i].data('record'))) {
+ return this._$tableRows[i];
+ }
+ }
+
+ return null;
+ },
+
+ /* Completely removes the table from it's container.
+ *************************************************************************/
+ destroy: function () {
+ this.element.empty();
+ $.Widget.prototype.destroy.call(this);
+ },
+
+ /************************************************************************
+ * PRIVATE METHODS *
+ *************************************************************************/
+
+ /* LOADING RECORDS *****************************************************/
+
+ /* Performs an AJAX call to reload data of the table.
+ *************************************************************************/
+ _reloadTable: function (completeCallback) {
+ var self = this;
+
+ //Disable table since it's busy
+ self._showBusy(self.options.messages.loadingMessage, self.options.loadingAnimationDelay);
+
+ //Generate URL (with query string parameters) to load records
+ var loadUrl = self._createRecordLoadUrl();
+
+ //Load data from server
+ self._onLoadingRecords();
+ self._ajax({
+ url: loadUrl,
+ data: self._lastPostData,
+ success: function (data) {
+ self._hideBusy();
+
+ //Show the error message if server returns error
+ if (data.Result != 'OK') {
+ self._showError(data.Message);
+ return;
+ }
+
+ //Re-generate table rows
+ self._removeAllRows('reloading');
+ self._addRecordsToTable(data.Records);
+
+ self._onRecordsLoaded(data);
+
+ //Call complete callback
+ if (completeCallback) {
+ completeCallback();
+ }
+ },
+ error: function () {
+ self._hideBusy();
+ self._showError(self.options.messages.serverCommunicationError);
+ }
+ });
+ },
+
+ /* Creates URL to load records.
+ *************************************************************************/
+ _createRecordLoadUrl: function () {
+ return this.options.actions.listAction;
+ },
+
+ /* TABLE MANIPULATION METHODS *******************************************/
+
+ /* Creates a row from given record
+ *************************************************************************/
+ _createRowFromRecord: function (record) {
+ var $tr = $('<tr></tr>')
+ .addClass('jtable-data-row')
+ .attr('data-record-key', this._getKeyValueOfRecord(record))
+ .data('record', record);
+
+ this._addCellsToRowUsingRecord($tr);
+ return $tr;
+ },
+
+ /* Adds all cells to given row.
+ *************************************************************************/
+ _addCellsToRowUsingRecord: function ($row) {
+ var record = $row.data('record');
+ for (var i = 0; i < this._columnList.length; i++) {
+ this._createCellForRecordField(record, this._columnList[i])
+ .appendTo($row);
+ }
+ },
+
+ /* Create a cell for given field.
+ *************************************************************************/
+ _createCellForRecordField: function (record, fieldName) {
+ return $('<td></td>')
+ .addClass(this.options.fields[fieldName].listClass)
+ .append((this._getDisplayTextForRecordField(record, fieldName) || ''));
+ },
+
+ /* Adds a list of records to the table.
+ *************************************************************************/
+ _addRecordsToTable: function (records) {
+ var self = this;
+
+ $.each(records, function (index, record) {
+ self._addRow(self._createRowFromRecord(record));
+ });
+
+ self._refreshRowStyles();
+ },
+
+ /* Adds a single row to the table.
+ * NOTE: THIS METHOD IS DEPRECATED AND WILL BE REMOVED FROM FEATURE RELEASES.
+ * USE _addRow METHOD.
+ *************************************************************************/
+ _addRowToTable: function ($tableRow, index, isNewRow, animationsEnabled) {
+ var options = {
+ index: this._normalizeNumber(index, 0, this._$tableRows.length, this._$tableRows.length)
+ };
+
+ if (isNewRow == true) {
+ options.isNewRow = true;
+ }
+
+ if (animationsEnabled == false) {
+ options.animationsEnabled = false;
+ }
+
+ this._addRow($tableRow, options);
+ },
+
+ /* Adds a single row to the table.
+ *************************************************************************/
+ _addRow: function ($row, options) {
+ //Set defaults
+ options = $.extend({
+ index: this._$tableRows.length,
+ isNewRow: false,
+ animationsEnabled: true
+ }, options);
+
+ //Remove 'no data' row if this is first row
+ if (this._$tableRows.length <= 0) {
+ this._removeNoDataRow();
+ }
+
+ //Add new row to the table according to it's index
+ options.index = this._normalizeNumber(options.index, 0, this._$tableRows.length, this._$tableRows.length);
+ if (options.index == this._$tableRows.length) {
+ //add as last row
+ this._$tableBody.append($row);
+ this._$tableRows.push($row);
+ } else if (options.index == 0) {
+ //add as first row
+ this._$tableBody.prepend($row);
+ this._$tableRows.unshift($row);
+ } else {
+ //insert to specified index
+ this._$tableRows[options.index - 1].after($row);
+ this._$tableRows.splice(options.index, 0, $row);
+ }
+
+ this._onRowInserted($row, options.isNewRow);
+
+ //Show animation if needed
+ if (options.isNewRow) {
+ this._refreshRowStyles();
+ if (this.options.animationsEnabled && options.animationsEnabled) {
+ this._showNewRowAnimation($row);
+ }
+ }
+ },
+
+ /* Shows created animation for a table row
+ * TODO: Make this animation cofigurable and changable
+ *************************************************************************/
+ _showNewRowAnimation: function ($tableRow) {
+ $tableRow.addClass('jtable-row-created', 'slow', '', function () {
+ $tableRow.removeClass('jtable-row-created', 5000);
+ });
+ },
+
+ /* Removes a row or rows (jQuery selection) from table.
+ *************************************************************************/
+ _removeRowsFromTable: function ($rows, reason) {
+ var self = this;
+
+ //Check if any row specified
+ if ($rows.length <= 0) {
+ return;
+ }
+
+ //remove from DOM
+ $rows.remove();
+
+ //remove from _$tableRows array
+ $rows.each(function () {
+ self._$tableRows.splice(self._findRowIndex($(this)), 1);
+ });
+
+ self._onRowsRemoved($rows, reason);
+
+ //Add 'no data' row if all rows removed from table
+ if (self._$tableRows.length == 0) {
+ self._addNoDataRow();
+ }
+
+ self._refreshRowStyles();
+ },
+
+ /* Finds index of a row in table.
+ *************************************************************************/
+ _findRowIndex: function ($row) {
+ return this._findIndexInArray($row, this._$tableRows, function ($row1, $row2) {
+ return $row1.data('record') == $row2.data('record');
+ });
+ },
+
+ /* Removes all rows in the table and adds 'no data' row.
+ *************************************************************************/
+ _removeAllRows: function (reason) {
+ //If no rows does exists, do nothing
+ if (this._$tableRows.length <= 0) {
+ return;
+ }
+
+ //Select all rows (to pass it on raising _onRowsRemoved event)
+ var $rows = this._$tableBody.find('tr.jtable-data-row');
+
+ //Remove all rows from DOM and the _$tableRows array
+ this._$tableBody.empty();
+ this._$tableRows = [];
+
+ this._onRowsRemoved($rows, reason);
+
+ //Add 'no data' row since we removed all rows
+ this._addNoDataRow();
+ },
+
+ /* Adds "no data available" row to the table.
+ *************************************************************************/
+ _addNoDataRow: function () {
+ var $tr = $('<tr></tr>')
+ .addClass('jtable-no-data-row')
+ .appendTo(this._$tableBody);
+
+ var totalColumnCount = this._$table.find('thead th').length;
+ $('<td></td>')
+ .attr('colspan', totalColumnCount)
+ .html(this.options.messages.noDataAvailable)
+ .appendTo($tr);
+ },
+
+ /* Removes "no data available" row from the table.
+ *************************************************************************/
+ _removeNoDataRow: function () {
+ this._$tableBody.find('.jtable-no-data-row').remove();
+ },
+
+ /* Refreshes styles of all rows in the table
+ *************************************************************************/
+ _refreshRowStyles: function () {
+ for (var i = 0; i < this._$tableRows.length; i++) {
+ if (i % 2 == 0) {
+ this._$tableRows[i].addClass('jtable-row-even');
+ } else {
+ this._$tableRows[i].removeClass('jtable-row-even');
+ }
+ }
+ },
+
+ /* RENDERING FIELD VALUES ***********************************************/
+
+ /* Gets text for a field of a record according to it's type.
+ *************************************************************************/
+ _getDisplayTextForRecordField: function (record, fieldName) {
+ var field = this.options.fields[fieldName];
+ var fieldValue = record[fieldName];
+
+ //if this is a custom field, call display function
+ if (field.display) {
+ return field.display({ record: record });
+ }
+
+ if (field.type == 'date') {
+ return this._getDisplayTextForDateRecordField(field, fieldValue);
+ } else if (field.type == 'checkbox') {
+ return this._getCheckBoxTextForFieldByValue(fieldName, fieldValue);
+ } else if (field.options) {
+ return this._getOptionsWithCaching(fieldName)[fieldValue];
+ } else {
+ return fieldValue;
+ }
+ },
+
+ /* Gets text for a date field.
+ *************************************************************************/
+ _getDisplayTextForDateRecordField: function (field, fieldValue) {
+ if (!fieldValue) {
+ return '';
+ }
+
+ var displayFormat = field.displayFormat || this.options.defaultDateFormat;
+ var date = this._parseDate(fieldValue);
+ return $.datepicker.formatDate(displayFormat, date);
+ },
+
+ /* Parses given date string to a javascript Date object.
+ * Given string must be formatted one of the samples shown below:
+ * /Date(1320259705710)/
+ * 2011-01-01 20:32:42 (YYYY-MM-DD HH:MM:SS)
+ * 2011-01-01 (YYYY-MM-DD)
+ *************************************************************************/
+ _parseDate: function (dateString) {
+ if (dateString.indexOf('Date') >= 0) { //Format: /Date(1320259705710)/
+ return new Date(
+ parseInt(dateString.substr(6))
+ );
+ } else if (dateString.length == 10) { //Format: 2011-01-01
+ return new Date(
+ parseInt(dateString.substr(0, 4)),
+ parseInt(dateString.substr(5, 2)) - 1,
+ parseInt(dateString.substr(8, 2))
+ );
+ } else if (dateString.length == 19) { //Format: 2011-01-01 20:32:42
+ return new Date(
+ parseInt(dateString.substr(0, 4)),
+ parseInt(dateString.substr(5, 2)) - 1,
+ parseInt(dateString.substr(8, 2)),
+ parseInt(dateString.substr(11, 2)),
+ parseInt(dateString.substr(14, 2)),
+ parseInt(dateString.substr(17, 2))
+ );
+ } else {
+ this._logWarn('Given date is not properly formatted: ' + dateString);
+ return 'format error!';
+ }
+ },
+
+ /* ERROR DIALOG *********************************************************/
+
+ /* Shows error message dialog with given message.
+ *************************************************************************/
+ _showError: function (message) {
+ this._$errorDialogDiv.html(message).dialog('open');
+ },
+
+ /* BUSY PANEL ***********************************************************/
+
+ /* Shows busy indicator and blocks table UI.
+ * TODO: Make this cofigurable and changable
+ *************************************************************************/
+ _setBusyTimer: null, //TODO: Think for a better way!
+ _showBusy: function (message, delay) {
+ var self = this;
+
+ var show = function () {
+ if (!self._$busyMessageDiv.is(':visible')) {
+ self._$busyDiv.width(self._$mainContainer.width());
+ self._$busyDiv.height(self._$mainContainer.height());
+ self._$busyDiv.show();
+ self._$busyMessageDiv.show();
+ }
+
+ self._$busyMessageDiv.html(message);
+ };
+
+ //TODO: Put an overlay always (without color) to not allow to click the table
+ //TODO: and change it visible when timeout occurs.
+ if (delay) {
+ self._setBusyTimer = setTimeout(show, delay);
+ } else {
+ show();
+ }
+ },
+
+ /* Hides busy indicator and unblocks table UI.
+ *************************************************************************/
+ _hideBusy: function () {
+ clearTimeout(this._setBusyTimer);
+ this._$busyDiv.hide();
+ this._$busyMessageDiv.html('').hide();
+ },
+
+ /* Returns true if jTable is busy.
+ *************************************************************************/
+ _isBusy: function () {
+ return this._$busyMessageDiv.is(':visible');
+ },
+
+ /* COMMON METHODS *******************************************************/
+
+ /* Performs an AJAX call to specified URL.
+ * THIS METHOD IS DEPRECATED AND WILL BE REMOVED FROM FEATURE RELEASES.
+ * USE _ajax METHOD.
+ *************************************************************************/
+ _performAjaxCall: function (url, postData, async, success, error) {
+ this._ajax({
+ url: url,
+ data: postData,
+ async: async,
+ success: success,
+ error: error
+ });
+ },
+
+ /* This method is used to perform AJAX calls in jTable instead of direct
+ * usage of jQuery.ajax method.
+ *************************************************************************/
+ _ajax: function (options) {
+ var opts = $.extend({}, this.options.ajaxSettings, options);
+
+ //Override success
+ opts.success = function (data) {
+ if (options.success) {
+ options.success(data);
+ }
+ };
+
+ //Override error
+ opts.error = function () {
+ if (options.error) {
+ options.error();
+ }
+ };
+
+ //Override complete
+ opts.complete = function () {
+ if (options.complete) {
+ options.complete();
+ }
+ };
+
+ $.ajax(opts);
+ },
+
+ /* Gets value of key field of a record.
+ *************************************************************************/
+ _getKeyValueOfRecord: function (record) {
+ return record[this._keyField];
+ },
+
+ /************************************************************************
+ * EVENT RAISING METHODS *
+ *************************************************************************/
+
+ _onLoadingRecords: function () {
+ this._trigger("loadingRecords", null, {});
+ },
+
+ _onRecordsLoaded: function (data) {
+ this._trigger("recordsLoaded", null, { records: data.Records, serverResponse: data });
+ },
+
+ _onRowInserted: function ($row, isNewRow) {
+ this._trigger("rowInserted", null, { row: $row, record: $row.data('record'), isNewRow: isNewRow });
+ },
+
+ _onRowsRemoved: function ($rows, reason) {
+ this._trigger("rowsRemoved", null, { rows: $rows, reason: reason });
+ },
+
+ _onCloseRequested: function () {
+ this._trigger("closeRequested", null, {});
+ }
+
+ });
+
+}(jQuery));
diff --git a/dev/jquery.jtable.creation.js b/dev/jquery.jtable.creation.js
new file mode 100644
index 0000000..eb45dac
--- /dev/null
+++ b/dev/jquery.jtable.creation.js
@@ -0,0 +1,272 @@
+/************************************************************************
+* CREATE RECORD extension for jTable *
+*************************************************************************/
+(function ($) {
+
+ //Reference to base object members
+ var base = {
+ _create: $.hik.jtable.prototype._create
+ };
+
+ //extension members
+ $.extend(true, $.hik.jtable.prototype, {
+
+ /************************************************************************
+ * DEFAULT OPTIONS / EVENTS *
+ *************************************************************************/
+ options: {
+
+ //Events
+ recordAdded: function (event, data) { },
+
+ //Localization
+ messages: {
+ addNewRecord: '+ Add new record'
+ }
+ },
+
+ /************************************************************************
+ * PRIVATE FIELDS *
+ *************************************************************************/
+
+ _$addRecordDiv: null, //Reference to the adding new record dialog div (jQuery object)
+
+ /************************************************************************
+ * CONSTRUCTOR *
+ *************************************************************************/
+
+ /* Overrides base method to do create-specific constructions.
+ *************************************************************************/
+ _create: function () {
+ base._create.apply(this, arguments);
+ this._createAddRecordDialogDiv();
+ },
+
+ /* Creates and prepares add new record dialog div
+ *************************************************************************/
+ _createAddRecordDialogDiv: function () {
+ var self = this;
+
+ //Check if createAction is supplied
+ if (!self.options.actions.createAction) {
+ return;
+ }
+
+ //Create a div for dialog and add to container element
+ self._$addRecordDiv = $('<div />')
+ .appendTo(self._$mainContainer);
+
+ //Prepare dialog
+ self._$addRecordDiv.dialog({
+ autoOpen: false,
+ show: self.options.dialogShowEffect,
+ hide: self.options.dialogHideEffect,
+ width: 'auto',
+ minWidth: '300',
+ modal: true,
+ title: self.options.messages.addNewRecord,
+ buttons:
+ [{ //Cancel button
+ text: self.options.messages.cancel,
+ click: function () {
+ self._$addRecordDiv.dialog('close');
+ }
+ }, { //Save button
+ id: 'AddRecordDialogSaveButton',
+ text: self.options.messages.save,
+ click: function () {
+ var $saveButton = $('#AddRecordDialogSaveButton');
+ var $addRecordForm = self._$addRecordDiv.find('form');
+
+ if (self._trigger("formSubmitting", null, { form: $addRecordForm, formType: 'create' }) != false) {
+ self._setEnabledOfDialogButton($saveButton, false, self.options.messages.saving);
+ self._saveAddRecordForm($addRecordForm, $saveButton);
+ }
+ }
+ }],
+ close: function () {
+ var $addRecordForm = self._$addRecordDiv.find('form').first();
+ var $saveButton = $('#AddRecordDialogSaveButton');
+ self._trigger("formClosed", null, { form: $addRecordForm, formType: 'create' });
+ self._setEnabledOfDialogButton($saveButton, true, self.options.messages.save);
+ $addRecordForm.remove();
+ }
+ });
+
+ //If not 'add record button' supplied, create a new one.
+ if (!self.options.addRecordButton) {
+ self.options.addRecordButton = self._createAddRecordButton();
+ }
+
+ //Bind click event to show dialog form
+ self.options.addRecordButton.click(function (e) {
+ e.preventDefault();
+ self._showAddRecordForm();
+ });
+ },
+
+ /* Creates and returns 'add new record' button/link.
+ *************************************************************************/
+ _createAddRecordButton: function () {
+ var $link = $('<a></a>')
+ .attr('href', '#')
+ .html(this.options.messages.addNewRecord);
+ return $('<span></span>')
+ .addClass('jtable-add-record')
+ .append($link)
+ .appendTo(this._$bottomPanel.find('.jtable-right-area'));
+ },
+
+ /************************************************************************
+ * PUBLIC METHODS *
+ *************************************************************************/
+
+ /* Shows add new record dialog form.
+ *************************************************************************/
+ showCreateForm: function () {
+ this._showAddRecordForm();
+ },
+
+ /* Adds a new record to the table (optionally to the server also)
+ *************************************************************************/
+ addRecord: function (options) {
+ var self = this;
+ options = $.extend({
+ clientOnly: false,
+ animationsEnabled: self.options.animationsEnabled,
+ url: self.options.actions.createAction,
+ success: function () { },
+ error: function () { }
+ }, options);
+
+ if (!options.record) {
+ self._logWarn('options parameter in addRecord method must contain a record property.');
+ return;
+ }
+
+ if (options.clientOnly) {
+ self._addRow(
+ self._createRowFromRecord(options.record), {
+ isNewRow: true,
+ animationsEnabled: options.animationsEnabled
+ });
+
+ options.success();
+ return;
+ }
+
+ self._submitFormUsingAjax(
+ options.url,
+ $.param(options.record),
+ function (data) {
+ if (data.Result != 'OK') {
+ self._showError(data.Message);
+ options.error(data);
+ return;
+ }
+
+ self._onRecordAdded(data);
+
+ self._addRow(
+ self._createRowFromRecord(data.Record), {
+ isNewRow: true,
+ animationsEnabled: options.animationsEnabled
+ });
+
+ options.success(data);
+ },
+ function () {
+ self._showError(self.options.messages.serverCommunicationError);
+ options.error();
+ });
+ },
+
+ /************************************************************************
+ * PRIVATE METHODS *
+ *************************************************************************/
+
+ /* Shows add new record dialog form.
+ *************************************************************************/
+ _showAddRecordForm: function () {
+ var self = this;
+
+ //Create add new record form
+ var $addRecordForm = $('<form id="jtable-create-form" class="jtable-dialog-form jtable-create-form" action="' + self.options.actions.createAction + '" method="POST"></form>');
+
+ //Create input elements
+ for (var i = 0; i < self._fieldList.length; i++) {
+
+ var fieldName = self._fieldList[i];
+ var field = self.options.fields[fieldName];
+
+ //Do not create input for fields that is key and not specially marked as editable
+ if (field.key == true && field.edit != true) {
+ continue;
+ }
+
+ //Do not create input for fields that are not creatable
+ if (field.create == false) {
+ continue;
+ }
+
+ if (field.type == 'hidden') {
+ $addRecordForm.append(self._createInputForHidden(fieldName, field.defaultValue));
+ continue;
+ }
+
+ //Create a container div for this input field and add to form
+ var $fieldContainer = $('<div />')
+ .addClass('jtable-input-field-container')
+ .appendTo($addRecordForm);
+
+ //Create a label for input
+ $fieldContainer.append(self._createInputLabelForRecordField(fieldName));
+
+ //Create input element
+ $fieldContainer.append(self._createInputForRecordField(fieldName));
+ }
+
+ //Open the form
+ self._$addRecordDiv.append($addRecordForm).dialog('open');
+ self._trigger("formCreated", null, { form: $addRecordForm, formType: 'create' });
+ },
+
+ /* Saves new added record to the server and updates table.
+ *************************************************************************/
+ _saveAddRecordForm: function ($addRecordForm, $saveButton) {
+ var self = this;
+
+ //Make an Ajax call to update record
+ $addRecordForm.data('submitting', true);
+
+ self._submitFormUsingAjax(
+ $addRecordForm.attr('action'),
+ $addRecordForm.serialize(),
+ function (data) {
+ //Check for errors
+ if (data.Result != 'OK') {
+ self._showError(data.Message);
+ self._setEnabledOfDialogButton($saveButton, true, self.options.messages.save);
+ return;
+ }
+
+ self._onRecordAdded(data);
+ self._addRow(
+ self._createRowFromRecord(data.Record), {
+ isNewRow: true
+ });
+ self._$addRecordDiv.dialog("close");
+ },
+ function () {
+ self._showError(self.options.messages.serverCommunicationError);
+ self._setEnabledOfDialogButton($saveButton, true, self.options.messages.save);
+ });
+ },
+
+ _onRecordAdded: function (data) {
+ this._trigger("recordAdded", null, { record: data.Record, serverResponse: data });
+ }
+
+ });
+
+})(jQuery);
diff --git a/dev/jquery.jtable.deletion.js b/dev/jquery.jtable.deletion.js
new file mode 100644
index 0000000..3e94f5a
--- /dev/null
+++ b/dev/jquery.jtable.deletion.js
@@ -0,0 +1,387 @@
+/************************************************************************
+* DELETION extension for jTable *
+*************************************************************************/
+(function ($) {
+
+ //Reference to base object members
+ var base = {
+ _create: $.hik.jtable.prototype._create,
+ _addColumnsToHeaderRow: $.hik.jtable.prototype._addColumnsToHeaderRow,
+ _addCellsToRowUsingRecord: $.hik.jtable.prototype._addCellsToRowUsingRecord
+ };
+
+ //extension members
+ $.extend(true, $.hik.jtable.prototype, {
+
+ /************************************************************************
+ * DEFAULT OPTIONS / EVENTS *
+ *************************************************************************/
+ options: {
+
+ //Options
+ deleteConfirmation: true,
+
+ //Events
+ recordDeleted: function (event, data) { },
+
+ //Localization
+ messages: {
+ deleteConfirmation: 'This record will be deleted. Are you sure?',
+ deleteText: 'Delete',
+ deleting: 'Deleting',
+ canNotDeletedRecords: 'Can not deleted {0} of {1} records!',
+ deleteProggress: 'Deleted {0} of {1} records, processing...'
+ }
+ },
+
+ /************************************************************************
+ * PRIVATE FIELDS *
+ *************************************************************************/
+
+ _$deleteRecordDiv: null, //Reference to the adding new record dialog div (jQuery object)
+ _$deletingRow: null, //Reference to currently deleting row (jQuery object)
+
+ /************************************************************************
+ * CONSTRUCTOR *
+ *************************************************************************/
+
+ /* Overrides base method to do deletion-specific constructions.
+ *************************************************************************/
+ _create: function () {
+ base._create.apply(this, arguments);
+ this._createDeleteDialogDiv();
+ },
+
+ /* Creates and prepares delete record confirmation dialog div.
+ *************************************************************************/
+ _createDeleteDialogDiv: function () {
+ var self = this;
+
+ //Create div element for delete confirmation dialog
+ 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);
+
+ //Prepare dialog
+ self._$deleteRecordDiv.dialog({
+ autoOpen: false,
+ show: self.options.dialogShowEffect,
+ hide: self.options.dialogHideEffect,
+ modal: true,
+ title: self.options.messages.areYouSure,
+ buttons:
+ [{ //cancel button
+ text: self.options.messages.cancel,
+ click: function () {
+ self._$deleteRecordDiv.dialog("close");
+ }
+ }, {//delete button
+ id: 'DeleteDialogButton',
+ text: self.options.messages.deleteText,
+ click: function () {
+ var $deleteButton = $('#DeleteDialogButton');
+ self._setEnabledOfDialogButton($deleteButton, false, self.options.messages.deleting);
+ self._deleteRecordFromServer(
+ self._$deletingRow,
+ function () {
+ self._removeRowsFromTableWithAnimation(self._$deletingRow);
+ self._$deleteRecordDiv.dialog('close');
+ },
+ function (message) { //error
+ self._showError(message);
+ self._setEnabledOfDialogButton($deleteButton, true, self.options.messages.deleteText);
+ }
+ );
+ }
+ }],
+ close: function () {
+ var $deleteButton = $('#DeleteDialogButton');
+ self._setEnabledOfDialogButton($deleteButton, true, self.options.messages.deleteText);
+ }
+ });
+ },
+
+ /************************************************************************
+ * PUBLIC METHODS *
+ *************************************************************************/
+
+ /* This method is used to delete one or more rows from server and the table.
+ *************************************************************************/
+ deleteRows: function ($rows) {
+ var self = this;
+
+ if ($rows.length <= 0) {
+ self._logWarn('No rows specified to jTable deleteRows method.');
+ return;
+ }
+
+ if (self._isBusy()) {
+ self._logWarn('Can not delete rows since jTable is busy!');
+ return;
+ }
+
+ //Deleting just one row
+ if ($rows.length == 1) {
+ self._deleteRecordFromServer(
+ $rows,
+ function () { //success
+ self._removeRowsFromTableWithAnimation($rows);
+ },
+ function (message) { //error
+ self._showError(message);
+ }
+ );
+
+ return;
+ }
+
+ //Deleting multiple rows
+ self._showBusy(self._formatString(self.options.messages.deleteProggress, 0, $rows.length));
+
+ //This method checks if deleting of all records is completed
+ var completedCount = 0;
+ var isCompleted = function () {
+ return (completedCount >= $rows.length);
+ };
+
+ //This method is called when deleting of all records completed
+ var completed = function () {
+ var $deletedRows = $rows.filter('.jtable-row-ready-to-remove');
+ if ($deletedRows.length < $rows.length) {
+ self._showError(self._formatString(self.options.messages.canNotDeletedRecords, $rows.length - $deletedRows.length, $rows.length));
+ }
+
+ if ($deletedRows.length > 0) {
+ self._removeRowsFromTableWithAnimation($deletedRows);
+ }
+
+ self._hideBusy();
+ };
+
+ //Delete all rows
+ var deletedCount = 0;
+ $rows.each(function () {
+ var $row = $(this);
+ self._deleteRecordFromServer(
+ $row,
+ function () { //success
+ ++deletedCount; ++completedCount;
+ $row.addClass('jtable-row-ready-to-remove');
+ self._showBusy(self._formatString(self.options.messages.deleteProggress, deletedCount, $rows.length));
+ if (isCompleted()) {
+ completed();
+ }
+ },
+ function () { //error
+ ++completedCount;
+ if (isCompleted()) {
+ completed();
+ }
+ }
+ );
+ });
+ },
+
+ /* Deletes a record from the table (optionally from the server also).
+ *************************************************************************/
+ deleteRecord: function (options) {
+ var self = this;
+ options = $.extend({
+ clientOnly: false,
+ animationsEnabled: self.options.animationsEnabled,
+ url: self.options.actions.deleteAction,
+ success: function () { },
+ error: function () { }
+ }, options);
+
+ if (options.key == undefined) {
+ self._logWarn('options parameter in deleteRecord method must contain a key property.');
+ return;
+ }
+
+ var $deletingRow = self.getRowByKey(options.key);
+ if ($deletingRow == null) {
+ self._logWarn('Can not found any row by key: ' + options.key);
+ return;
+ }
+
+ if (options.clientOnly) {
+ self._removeRowsFromTableWithAnimation($deletingRow, options.animationsEnabled);
+ options.success();
+ return;
+ }
+
+ self._deleteRecordFromServer(
+ $deletingRow,
+ function (data) { //success
+ self._removeRowsFromTableWithAnimation($deletingRow, options.animationsEnabled);
+ options.success(data);
+ },
+ function (message) { //error
+ self._showError(message);
+ options.error(message);
+ },
+ options.url
+ );
+ },
+
+ /************************************************************************
+ * OVERRIDED METHODS *
+ *************************************************************************/
+
+ /* Overrides base method to add a 'deletion column cell' to header row.
+ *************************************************************************/
+ _addColumnsToHeaderRow: function ($tr) {
+ base._addColumnsToHeaderRow.apply(this, arguments);
+ if (this.options.actions.deleteAction != undefined) {
+ $tr.append(this._createEmptyCommandHeader());
+ }
+ },
+
+ /* Overrides base method to add a 'delete command cell' to a row.
+ *************************************************************************/
+ _addCellsToRowUsingRecord: function ($row) {
+ base._addCellsToRowUsingRecord.apply(this, arguments);
+
+ var self = this;
+ if (self.options.actions.deleteAction != undefined) {
+ var $span = $('<span></span>').html(self.options.messages.deleteText);
+ var $button = $('<button title="' + self.options.messages.deleteText + '"></button>')
+ .addClass('jtable-command-button jtable-delete-command-button')
+ .append($span)
+ .click(function (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ self._deleteButtonClickedForRow($row);
+ });
+ $('<td></td>')
+ .addClass('jtable-command-column')
+ .append($button)
+ .appendTo($row);
+ }
+ },
+
+ /************************************************************************
+ * PRIVATE METHODS *
+ *************************************************************************/
+
+ /* This method is called when user clicks delete button on a row.
+ *************************************************************************/
+ _deleteButtonClickedForRow: function ($row) {
+ var self = this;
+
+ var deleteConfirm;
+ var deleteConfirmMessage = self.options.messages.deleteConfirmation;
+
+ //If options.deleteConfirmation is function then call it
+ if ($.isFunction(self.options.deleteConfirmation)) {
+ var data = { row: $row, record: $row.data('record'), deleteConfirm: true, deleteConfirmMessage: deleteConfirmMessage, cancel: false, cancelMessage: null };
+ self.options.deleteConfirmation(data);
+
+ //If delete progress is cancelled
+ if (data.cancel) {
+
+ //If a canlellation reason is specified
+ if (data.cancelMessage) {
+ self._showError(data.cancelMessage); //TODO: show warning/stop message instead of error (also show warning/error ui icon)!
+ }
+
+ return;
+ }
+
+ deleteConfirmMessage = data.deleteConfirmMessage;
+ deleteConfirm = data.deleteConfirm;
+ } else {
+ deleteConfirm = self.options.deleteConfirmation;
+ }
+
+ if (deleteConfirm != false) {
+ //Confirmation
+ self._$deleteRecordDiv.find('.jtable-delete-confirm-message').html(deleteConfirmMessage);
+ self._showDeleteDialog($row);
+ } else {
+ //No confirmation
+ self._deleteRecordFromServer(
+ $row,
+ function () { //success
+ self._removeRowsFromTableWithAnimation($row);
+ },
+ function (message) { //error
+ self._showError(message);
+ }
+ );
+ }
+ },
+
+ /* Shows delete comfirmation dialog.
+ *************************************************************************/
+ _showDeleteDialog: function ($row) {
+ this._$deletingRow = $row;
+ this._$deleteRecordDiv.dialog('open');
+ },
+
+ /* Performs an ajax call to server to delete record
+ * and removesd row of record from table if ajax call success.
+ *************************************************************************/
+ _deleteRecordFromServer: function ($row, success, error, url) {
+ var self = this;
+
+ //Check if it is already being deleted right now
+ if ($row.data('deleting') == true) {
+ return;
+ }
+
+ $row.data('deleting', true);
+
+ var postData = {};
+ postData[self._keyField] = self._getKeyValueOfRecord($row.data('record'));
+
+ this._ajax({
+ url: (url || self.options.actions.deleteAction),
+ data: postData,
+ success: function (data) {
+
+ if (data.Result != 'OK') {
+ $row.data('deleting', false);
+ if (error) {
+ error(data.Message);
+ }
+
+ return;
+ }
+
+ self._trigger("recordDeleted", null, { record: $row.data('record'), row: $row, serverResponse: data });
+
+ if (success) {
+ success(data);
+ }
+ },
+ error: function () {
+ $row.data('deleting', false);
+ if (error) {
+ error(self.options.messages.serverCommunicationError);
+ }
+ }
+ });
+ },
+
+ /* Removes a row from table after a 'deleting' animation.
+ *************************************************************************/
+ _removeRowsFromTableWithAnimation: function ($rows, animationsEnabled) {
+ var self = this;
+
+ if (animationsEnabled == undefined) {
+ animationsEnabled = self.options.animationsEnabled;
+ }
+
+ if (animationsEnabled) {
+ //Stop current animation (if does exists) and begin 'deleting' animation.
+ $rows.stop(true, true).addClass('jtable-row-deleting', 'slow', '').promise().done(function () {
+ self._removeRowsFromTable($rows, 'deleted');
+ });
+ } else {
+ self._removeRowsFromTable($rows, 'deleted');
+ }
+ }
+
+ });
+
+})(jQuery);
diff --git a/dev/jquery.jtable.dynamiccolumns.js b/dev/jquery.jtable.dynamiccolumns.js
new file mode 100644
index 0000000..be552c7
--- /dev/null
+++ b/dev/jquery.jtable.dynamiccolumns.js
@@ -0,0 +1,529 @@
+/************************************************************************
+* DYNAMIC COLUMNS extension for jTable *
+* (Show/hide/resize columns) *
+*************************************************************************/
+(function ($) {
+
+ //Reference to base object members
+ var base = {
+ _create: $.hik.jtable.prototype._create,
+ _normalizeFieldOptions: $.hik.jtable.prototype._normalizeFieldOptions,
+ _createHeaderCellForField: $.hik.jtable.prototype._createHeaderCellForField,
+ _createCellForRecordField: $.hik.jtable.prototype._createCellForRecordField
+ };
+
+ //extension members
+ $.extend(true, $.hik.jtable.prototype, {
+
+ /************************************************************************
+ * DEFAULT OPTIONS / EVENTS *
+ *************************************************************************/
+
+ options: {
+ tableId: undefined,
+ columnResizable: true,
+ columnSelectable: true,
+ saveUserPreferences: true
+ },
+
+ /************************************************************************
+ * PRIVATE FIELDS *
+ *************************************************************************/
+
+ _$columnSelectionDiv: null,
+ _$columnResizeBar: null,
+ _cookieKeyPrefix: null,
+ _currentResizeArgs: null,
+
+ /************************************************************************
+ * OVERRIDED METHODS *
+ *************************************************************************/
+
+ /* Overrides _addRowToTableHead method.
+ *************************************************************************/
+
+ _create: function () {
+ base._create.apply(this, arguments);
+
+ this._createColumnResizeBar();
+ this._createColumnSelection();
+
+ this._cookieKeyPrefix = this._generateCookieKeyPrefix();
+ if (this.options.saveUserPreferences) {
+ this._loadColumnSettings();
+ }
+
+ this._normalizeColumnWidths();
+ },
+
+ /* Normalizes some options for a field (sets default values).
+ *************************************************************************/
+ _normalizeFieldOptions: function (fieldName, props) {
+ base._normalizeFieldOptions.apply(this, arguments);
+
+ //columnResizable
+ if (this.options.columnResizable) {
+ props.columnResizable = (props.columnResizable != false);
+ }
+
+ //visibility
+ if (!props.visibility) {
+ props.visibility = 'visible';
+ }
+ },
+
+ /* Overrides _createHeaderCellForField to make columns dynamic.
+ *************************************************************************/
+ _createHeaderCellForField: function (fieldName, field) {
+ var $headerCell = base._createHeaderCellForField.apply(this, arguments);
+
+ //Make data columns resizable except the last one
+ if (this.options.columnResizable && field.columnResizable && (fieldName != this._columnList[this._columnList.length - 1])) {
+ this._makeColumnResizable($headerCell);
+ }
+
+ //Hide column if needed
+ if (field.visibility == 'hidden') {
+ $headerCell.hide();
+ }
+
+ return $headerCell;
+ },
+
+ /* Overrides _createHeaderCellForField to decide show or hide a column.
+ *************************************************************************/
+ _createCellForRecordField: function (record, fieldName) {
+ var $column = base._createCellForRecordField.apply(this, arguments);
+
+ var field = this.options.fields[fieldName];
+ if (field.visibility == 'hidden') {
+ $column.hide();
+ }
+
+ return $column;
+ },
+
+ /************************************************************************
+ * PUBLIC METHODS *
+ *************************************************************************/
+
+ /* Changes visibility of a column.
+ *************************************************************************/
+ changeColumnVisibility: function (columnName, visibility) {
+ this._changeColumnVisibilityInternal(columnName, visibility);
+ this._normalizeColumnWidths();
+ if (this.options.saveUserPreferences) {
+ this._saveColumnSettings();
+ }
+ },
+
+ /************************************************************************
+ * PRIVATE METHODS *
+ *************************************************************************/
+
+ /* Changes visibility of a column.
+ *************************************************************************/
+ _changeColumnVisibilityInternal: function (columnName, visibility) {
+ //Check if there is a column with given name
+ var columnIndex = this._columnList.indexOf(columnName);
+ if (columnIndex < 0) {
+ this._logWarn('Column "' + columnName + '" does not exist in fields!');
+ return;
+ }
+
+ //Check if visibility value is valid
+ if (['visible', 'hidden', 'fixed'].indexOf(visibility) < 0) {
+ this._logWarn('Visibility value is not valid: "' + visibility + '"! Options are: visible, hidden, fixed.');
+ return;
+ }
+
+ //Get the field
+ var field = this.options.fields[columnName];
+ if (field.visibility == visibility) {
+ return; //No action if new value is same as old one.
+ }
+
+ //Hide or show the column if needed
+ var columnIndexInTable = this._firstDataColumnOffset + columnIndex + 1;
+ if (field.visibility != 'hidden' && visibility == 'hidden') {
+ this._$table
+ .find('>thead >th:nth-child(' + columnIndexInTable + '),>tbody >td:nth-child(' + columnIndexInTable + ')')
+ .hide();
+ } else if (field.visibility == 'hidden' && visibility != 'hidden') {
+ this._$table
+ .find('>thead >th:nth-child(' + columnIndexInTable + '),>tbody >td:nth-child(' + columnIndexInTable + ')')
+ .show()
+ .css('display', 'table-cell');
+ }
+
+ field.visibility = visibility;
+ },
+
+ /* Prepares dialog to change settings.
+ *************************************************************************/
+ _createColumnSelection: function () {
+ var self = this;
+
+ //Create a div for dialog and add to container element
+ this._$columnSelectionDiv = $('<div />')
+ .addClass('jtable-column-selection-container')
+ .appendTo(self._$mainContainer);
+
+ this._$table.children('thead').bind('contextmenu', function (e) {
+ if (!self.options.columnSelectable) {
+ return;
+ }
+
+ e.preventDefault();
+
+ //Make an overlay div to disable page clicks
+ $('<div />')
+ .addClass('jtable-contextmenu-overlay')
+ .click(function () {
+ $(this).remove();
+ self._$columnSelectionDiv.hide();
+ })
+ .bind('contextmenu', function () { return false; })
+ .appendTo(document.body);
+
+ self._fillColumnSelection();
+
+ //Calculate position of column selection list and show it
+
+ var containerOffset = self._$mainContainer.offset();
+ var selectionDivTop = e.pageY - containerOffset.top;
+ var selectionDivLeft = e.pageX - containerOffset.left;
+
+ var selectionDivMinWidth = 100; //in pixels
+ var containerWidth = self._$mainContainer.width();
+
+ //If user clicks right area of header of the table, show list at a little left
+ if ((containerWidth > selectionDivMinWidth) && (selectionDivLeft > (containerWidth - selectionDivMinWidth))) {
+ selectionDivLeft = containerWidth - selectionDivMinWidth;
+ }
+
+ self._$columnSelectionDiv.css({
+ left: selectionDivLeft,
+ top: selectionDivTop,
+ 'min-width': selectionDivMinWidth + 'px'
+ }).show();
+ });
+ },
+
+ /* Prepares content of settings dialog.
+ *************************************************************************/
+ _fillColumnSelection: function () {
+ var self = this;
+
+ var $columnsUl = $('<ul></ul>')
+ .addClass('jtable-column-select-list');
+ for (var i = 0; i < this._columnList.length; i++) {
+ var columnName = this._columnList[i];
+ var field = this.options.fields[columnName];
+
+ //Crete li element
+ var $columnLi = $('<li></li>').appendTo($columnsUl);
+
+ //Create label for the checkbox
+ var $label = $('<label for="' + columnName + '"></label>')
+ .append($('<span>' + (field.title || columnName) + '</span>'))
+ .appendTo($columnLi);
+
+ //Create checkbox
+ var $checkbox = $('<input type="checkbox" name="' + columnName + '">')
+ .prependTo($label)
+ .click(function () {
+ var $clickedCheckbox = $(this);
+ var clickedColumnName = $clickedCheckbox.attr('name');
+ var clickedField = self.options.fields[clickedColumnName];
+ if (clickedField.visibility == 'fixed') {
+ return;
+ }
+
+ self.changeColumnVisibility(clickedColumnName, $clickedCheckbox.is(':checked') ? 'visible' : 'hidden');
+ });
+
+ //Check, if column if shown
+ if (field.visibility != 'hidden') {
+ $checkbox.attr('checked', 'checked');
+ }
+
+ //Disable, if column is fixed
+ if (field.visibility == 'fixed') {
+ $checkbox.attr('disabled', 'disabled');
+ }
+ }
+
+ this._$columnSelectionDiv.html($columnsUl);
+ },
+
+ /* creates a vertical bar that is shown while resizing columns.
+ *************************************************************************/
+ _createColumnResizeBar: function () {
+ this._$columnResizeBar = $('<div />')
+ .addClass('jtable-column-resize-bar')
+ .appendTo(this._$mainContainer)
+ .hide();
+ },
+
+ /* Makes a column sortable.
+ *************************************************************************/
+ _makeColumnResizable: function ($columnHeader) {
+ var self = this;
+
+ //Create a handler to handle mouse click event
+ $('<div />')
+ .addClass('jtable-column-resize-handler')
+ .appendTo($columnHeader.find('.jtable-column-header-container')) //Append the handler to the column
+ .mousedown(function (downevent) { //handle mousedown event for the handler
+ downevent.preventDefault();
+ downevent.stopPropagation();
+
+ var mainContainerOffset = self._$mainContainer.offset();
+
+ //Get a reference to the next column
+ var $nextColumnHeader = $columnHeader.nextAll('th.jtable-column-header:visible:first');
+ if (!$nextColumnHeader.length) {
+ return;
+ }
+
+ //Store some information to be used on resizing
+ var minimumColumnWidth = 10; //A column's width can not be smaller than 10 pixel.
+ self._currentResizeArgs = {
+ currentColumnStartWidth: $columnHeader.outerWidth(),
+ minWidth: minimumColumnWidth,
+ maxWidth: $columnHeader.outerWidth() + $nextColumnHeader.outerWidth() - minimumColumnWidth,
+ mouseStartX: downevent.pageX,
+ minResizeX: function () { return this.mouseStartX - (this.currentColumnStartWidth - this.minWidth); },
+ maxResizeX: function () { return this.mouseStartX + (this.maxWidth - this.currentColumnStartWidth); }
+ };
+
+ //Handle mouse move event to move resizing bar
+ var resizeonmousemove = function (moveevent) {
+ if (!self._currentResizeArgs) {
+ return;
+ }
+
+ var resizeBarX = self._normalizeNumber(moveevent.pageX, self._currentResizeArgs.minResizeX(), self._currentResizeArgs.maxResizeX());
+ self._$columnResizeBar.css('left', (resizeBarX - mainContainerOffset.left) + 'px');
+ };
+
+ //Handle mouse up event to finish resizing of the column
+ var resizeonmouseup = function (upevent) {
+ if (!self._currentResizeArgs) {
+ return;
+ }
+
+ $(document).unbind('mousemove', resizeonmousemove);
+ $(document).unbind('mouseup', resizeonmouseup);
+
+ self._$columnResizeBar.hide();
+
+ //Calculate new widths in pixels
+ var mouseChangeX = upevent.pageX - self._currentResizeArgs.mouseStartX;
+ var currentColumnFinalWidth = self._normalizeNumber(self._currentResizeArgs.currentColumnStartWidth + mouseChangeX, self._currentResizeArgs.minWidth, self._currentResizeArgs.maxWidth);
+ var nextColumnFinalWidth = $nextColumnHeader.outerWidth() + (self._currentResizeArgs.currentColumnStartWidth - currentColumnFinalWidth);
+
+ //Calculate widths as percent
+ var pixelToPercentRatio = $columnHeader.data('width-in-percent') / self._currentResizeArgs.currentColumnStartWidth;
+ $columnHeader.data('width-in-percent', currentColumnFinalWidth * pixelToPercentRatio);
+ $nextColumnHeader.data('width-in-percent', nextColumnFinalWidth * pixelToPercentRatio);
+
+ //Set new widths to columns (resize!)
+ $columnHeader.css('width', $columnHeader.data('width-in-percent') + '%');
+ $nextColumnHeader.css('width', $nextColumnHeader.data('width-in-percent') + '%');
+
+ //Normalize all column widths
+ self._normalizeColumnWidths();
+
+ //Finish resizing
+ self._currentResizeArgs = null;
+
+ //Save current preferences
+ if (self.options.saveUserPreferences) {
+ self._saveColumnSettings();
+ }
+ };
+
+ //Show vertical resize bar
+ self._$columnResizeBar
+ .show()
+ .css({
+ top: ($columnHeader.offset().top - mainContainerOffset.top) + 'px',
+ left: (downevent.pageX - mainContainerOffset.left) + 'px',
+ height: (self._$table.outerHeight()) + 'px'
+ });
+
+ //Bind events
+ $(document).bind('mousemove', resizeonmousemove);
+ $(document).bind('mouseup', resizeonmouseup);
+ });
+ },
+
+ /* Normalizes column widths as percent for current view.
+ *************************************************************************/
+ _normalizeColumnWidths: function () {
+
+ //Set command column width
+ var commandColumnHeaders = this._$table
+ .find('>thead th.jtable-command-column-header')
+ .data('width-in-percent', 1)
+ .css('width', '1%');
+
+ //Find data columns
+ var headerCells = this._$table.find('>thead th.jtable-column-header');
+
+ //Calculate total width of data columns
+ var totalWidthInPixel = 0;
+ headerCells.each(function () {
+ var $cell = $(this);
+ if ($cell.is(':visible')) {
+ totalWidthInPixel += $cell.outerWidth();
+ }
+ });
+
+ //Calculate width of each column
+ var columnWidhts = {};
+ var availableWidthInPercent = 100.0 - commandColumnHeaders.length;
+ headerCells.each(function () {
+ var $cell = $(this);
+ if ($cell.is(':visible')) {
+ var fieldName = $cell.data('fieldName');
+ var widthInPercent = $cell.outerWidth() * availableWidthInPercent / totalWidthInPixel;
+ columnWidhts[fieldName] = widthInPercent;
+ }
+ });
+
+ //Set width of each column
+ headerCells.each(function () {
+ var $cell = $(this);
+ if ($cell.is(':visible')) {
+ var fieldName = $cell.data('fieldName');
+ $cell.data('width-in-percent', columnWidhts[fieldName]).css('width', columnWidhts[fieldName] + '%');
+ }
+ });
+ },
+
+ /* Saves field setting to cookie.
+ * Saved setting will be a string like that:
+ * fieldName1=visible;23|fieldName2=hidden;17|...
+ *************************************************************************/
+ _saveColumnSettings: function () {
+ var self = this;
+ var fieldSettings = '';
+ this._$table.find('>thead >th.jtable-column-header').each(function () {
+ var $cell = $(this);
+ var fieldName = $cell.data('fieldName');
+ var columnWidth = $cell.data('width-in-percent');
+ var fieldVisibility = self.options.fields[fieldName].visibility;
+ var fieldSetting = fieldName + "=" + fieldVisibility + ';' + columnWidth;
+ fieldSettings = fieldSettings + fieldSetting + '|';
+ });
+
+ this._setCookie('column-settings', fieldSettings.substr(0, fieldSettings.length - 1));
+ },
+
+ /* Loads field settings from cookie that is saved by _saveFieldSettings method.
+ *************************************************************************/
+ _loadColumnSettings: function () {
+ var self = this;
+ var columnSettingsCookie = this._getCookie('column-settings');
+ if (!columnSettingsCookie) {
+ return;
+ }
+
+ var columnSettings = {};
+ $.each(columnSettingsCookie.split('|'), function (inx, fieldSetting) {
+ var splitted = fieldSetting.split('=');
+ var fieldName = splitted[0];
+ var settings = splitted[1].split(';');
+ columnSettings[fieldName] = {
+ columnVisibility: settings[0],
+ columnWidth: settings[1]
+ }; ;
+ });
+
+ var headerCells = this._$table.find('>thead >th.jtable-column-header');
+ headerCells.each(function () {
+ var $cell = $(this);
+ var fieldName = $cell.data('fieldName');
+ var field = self.options.fields[fieldName];
+ if (columnSettings[fieldName]) {
+ if (field.visibility != 'fixed') {
+ self._changeColumnVisibilityInternal(fieldName, columnSettings[fieldName].columnVisibility);
+ }
+
+ $cell.data('width-in-percent', columnSettings[fieldName].columnWidth).css('width', columnSettings[fieldName].columnWidth + '%');
+ }
+ });
+ },
+
+ /************************************************************************
+ * COOKIE *
+ *************************************************************************/
+
+ /* Sets a cookie with given key.
+ *************************************************************************/
+ _setCookie: function (key, value) {
+ key = this._cookieKeyPrefix + key;
+
+ var expireDate = new Date();
+ expireDate.setDate(expireDate.getDate() + 30);
+ document.cookie = encodeURIComponent(key) + '=' + encodeURIComponent(value) + "; expires=" + expireDate.toUTCString();
+ },
+
+ /* Gets a cookie with given key.
+ *************************************************************************/
+ _getCookie: function (key) {
+ key = this._cookieKeyPrefix + key;
+
+ var equalities = document.cookie.split('; ');
+ for (var i = 0; i < equalities.length; i++) {
+ if (!equalities[i]) {
+ continue;
+ }
+
+ var splitted = equalities[i].split('=');
+ if (splitted.length != 2) {
+ continue;
+ }
+
+ if (decodeURIComponent(splitted[0]) === key) {
+ return decodeURIComponent(splitted[1] || '');
+ }
+ }
+
+ return null;
+ },
+
+ /* Generates a hash key to be prefix for all cookies for this jtable instance.
+ *************************************************************************/
+ _generateCookieKeyPrefix: function () {
+
+ var simpleHash = function (value) {
+ var hash = 0;
+ if (value.length == 0) {
+ return hash;
+ }
+
+ for (var i = 0; i < value.length; i++) {
+ var ch = value.charCodeAt(i);
+ hash = ((hash << 5) - hash) + ch;
+ hash = hash & hash;
+ }
+
+ return hash;
+ };
+
+ var strToHash = '';
+ if (this.options.tableId) {
+ strToHash = strToHash + this.options.tableId + '#';
+ }
+
+ strToHash = strToHash + this._columnList.join('$') + '#c' + this._$table.find('thead th').length;
+ return 'jtable#' + simpleHash(strToHash);
+ }
+
+ });
+
+})(jQuery);
diff --git a/dev/jquery.jtable.editing.js b/dev/jquery.jtable.editing.js
new file mode 100644
index 0000000..8b90ac0
--- /dev/null
+++ b/dev/jquery.jtable.editing.js
@@ -0,0 +1,394 @@
+/************************************************************************
+* EDIT RECORD extension for jTable *
+*************************************************************************/
+(function ($) {
+
+ //Reference to base object members
+ var base = {
+ _create: $.hik.jtable.prototype._create,
+ _addColumnsToHeaderRow: $.hik.jtable.prototype._addColumnsToHeaderRow,
+ _addCellsToRowUsingRecord: $.hik.jtable.prototype._addCellsToRowUsingRecord
+ };
+
+ //extension members
+ $.extend(true, $.hik.jtable.prototype, {
+
+ /************************************************************************
+ * DEFAULT OPTIONS / EVENTS *
+ *************************************************************************/
+ options: {
+
+ //Events
+ recordUpdated: function (event, data) { },
+ rowUpdated: function (event, data) { },
+
+ //Localization
+ messages: {
+ editRecord: 'Edit Record'
+ }
+ },
+
+ /************************************************************************
+ * PRIVATE FIELDS *
+ *************************************************************************/
+
+ _$editDiv: null, //Reference to the editing dialog div (jQuery object)
+ _$editingRow: null, //Reference to currently editing row (jQuery object)
+
+ /************************************************************************
+ * CONSTRUCTOR AND INITIALIZATION METHODS *
+ *************************************************************************/
+
+ /* Overrides base method to do editing-specific constructions.
+ *************************************************************************/
+ _create: function () {
+ base._create.apply(this, arguments);
+ this._createEditDialogDiv();
+ },
+
+ /* Creates and prepares edit dialog div
+ *************************************************************************/
+ _createEditDialogDiv: function () {
+ var self = this;
+
+ //Create a div for dialog and add to container element
+ self._$editDiv = $('<div></div>')
+ .appendTo(self._$mainContainer);
+
+ //Prepare dialog
+ self._$editDiv.dialog({
+ autoOpen: false,
+ show: self.options.dialogShowEffect,
+ hide: self.options.dialogHideEffect,
+ width: 'auto',
+ minWidth: '300',
+ modal: true,
+ title: self.options.messages.editRecord,
+ buttons:
+ [{ //cancel button
+ text: self.options.messages.cancel,
+ click: function () {
+ self._$editDiv.dialog('close');
+ }
+ }, { //save button
+ id: 'EditDialogSaveButton',
+ text: self.options.messages.save,
+ click: function () {
+ var $saveButton = self._$editDiv.find('#EditDialogSaveButton');
+ var $editForm = self._$editDiv.find('form');
+ if (self._trigger("formSubmitting", null, { form: $editForm, formType: 'edit' }) != false) {
+ self._setEnabledOfDialogButton($saveButton, false, self.options.messages.saving);
+ self._saveEditForm($editForm, $saveButton);
+ }
+ }
+ }],
+ close: function () {
+ var $editForm = self._$editDiv.find('form:first');
+ var $saveButton = $('#EditDialogSaveButton');
+ self._trigger("formClosed", null, { form: $editForm, formType: 'edit' });
+ self._setEnabledOfDialogButton($saveButton, true, self.options.messages.save);
+ $editForm.remove();
+ }
+ });
+ },
+
+ /************************************************************************
+ * PUNLIC METHODS *
+ *************************************************************************/
+
+ /* Updates a record on the table (optionally on the server also)
+ *************************************************************************/
+ updateRecord: function (options) {
+ var self = this;
+ options = $.extend({
+ clientOnly: false,
+ animationsEnabled: self.options.animationsEnabled,
+ url: self.options.actions.updateAction,
+ success: function () { },
+ error: function () { }
+ }, options);
+
+ if (!options.record) {
+ self._logWarn('options parameter in updateRecord method must contain a record property.');
+ return;
+ }
+
+ var key = self._getKeyValueOfRecord(options.record);
+ if (key == undefined || key == null) {
+ self._logWarn('options parameter in updateRecord method must contain a record that contains the key field property.');
+ return;
+ }
+
+ var $updatingRow = self.getRowByKey(key);
+ if ($updatingRow == null) {
+ self._logWarn('Can not found any row by key: ' + key);
+ return;
+ }
+
+ if (options.clientOnly) {
+ $.extend($updatingRow.data('record'), options.record);
+ self._updateRowTexts($updatingRow);
+ self._onRecordUpdated($updatingRow, null);
+ if (options.animationsEnabled) {
+ self._showUpdateAnimationForRow($updatingRow);
+ }
+
+ options.success();
+ return;
+ }
+
+ self._submitFormUsingAjax(
+ options.url,
+ $.param(options.record),
+ function (data) {
+ if (data.Result != 'OK') {
+ self._showError(data.Message);
+ options.error(data);
+ return;
+ }
+
+ $.extend($updatingRow.data('record'), options.record);
+ self._updateRowTexts($updatingRow);
+ self._onRecordUpdated($updatingRow, data);
+ if (options.animationsEnabled) {
+ self._showUpdateAnimationForRow($updatingRow);
+ }
+
+ options.success(data);
+ },
+ function () {
+ self._showError(self.options.messages.serverCommunicationError);
+ options.error();
+ });
+ },
+
+ /************************************************************************
+ * OVERRIDED METHODS *
+ *************************************************************************/
+
+ /* Overrides base method to add a 'editing column cell' to header row.
+ *************************************************************************/
+ _addColumnsToHeaderRow: function ($tr) {
+ base._addColumnsToHeaderRow.apply(this, arguments);
+ if (this.options.actions.updateAction != undefined) {
+ $tr.append(this._createEmptyCommandHeader());
+ }
+ },
+
+ /* Overrides base method to add a 'edit command cell' to a row.
+ *************************************************************************/
+ _addCellsToRowUsingRecord: function ($row) {
+ var self = this;
+ base._addCellsToRowUsingRecord.apply(this, arguments);
+
+ if (self.options.actions.updateAction != undefined) {
+ var $span = $('<span></span>').html(self.options.messages.editRecord);
+ var $button = $('<button title="' + self.options.messages.editRecord + '"></button>')
+ .addClass('jtable-command-button jtable-edit-command-button')
+ .append($span)
+ .click(function (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ self._showEditForm($row);
+ });
+ $('<td></td>')
+ .addClass('jtable-command-column')
+ .append($button)
+ .appendTo($row);
+ }
+ },
+
+ /************************************************************************
+ * PRIVATE METHODS *
+ *************************************************************************/
+
+ /* Shows edit form for a row.
+ *************************************************************************/
+ _showEditForm: function ($tableRow) {
+ var self = this;
+ var record = $tableRow.data('record');
+
+ //Create edit form
+ var $editForm = $('<form id="jtable-edit-form" class="jtable-dialog-form jtable-edit-form" action="' + self.options.actions.updateAction + '" method="POST"></form>');
+
+ //Create input fields
+ for (var i = 0; i < self._fieldList.length; i++) {
+
+ var fieldName = self._fieldList[i];
+ var field = self.options.fields[fieldName];
+ var fieldValue = record[fieldName];
+
+ if (field.key == true) {
+ if (field.edit != true) {
+ //Create hidden field for key
+ $editForm.append(self._createInputForHidden(fieldName, fieldValue));
+ continue;
+ } else {
+ //Create a special hidden field for key (since key is be editable)
+ $editForm.append(self._createInputForHidden('jtRecordKey', fieldValue));
+ }
+ }
+
+ //Do not create element for non-editable fields
+ if (field.edit == false) {
+ continue;
+ }
+
+ //Hidden field
+ if (field.type == 'hidden') {
+ $editForm.append(self._createInputForHidden(fieldName, fieldValue));
+ continue;
+ }
+
+ //Create a container div for this input field and add to form
+ var $fieldContainer = $('<div class="jtable-input-field-container"></div>').appendTo($editForm);
+
+ //Create a label for input
+ $fieldContainer.append(self._createInputLabelForRecordField(fieldName));
+
+ //Create input element with it's current value
+ var currentValue = self._getValueForRecordField(record, fieldName);
+ $fieldContainer.append(self._createInputForRecordField(fieldName, currentValue, record));
+ }
+
+ //Open dialog
+ self._$editingRow = $tableRow;
+ self._$editDiv.append($editForm).dialog('open');
+ self._trigger("formCreated", null, { form: $editForm, formType: 'edit', record: record });
+ },
+
+ /* Saves editing form to the server and updates the record on the table.
+ *************************************************************************/
+ _saveEditForm: function ($editForm, $saveButton) {
+ var self = this;
+ self._submitFormUsingAjax(
+ $editForm.attr('action'),
+ $editForm.serialize(),
+ function (data) {
+ //Check for errors
+ if (data.Result != 'OK') {
+ self._showError(data.Message);
+ self._setEnabledOfDialogButton($saveButton, true, self.options.messages.save);
+ return;
+ }
+
+ var record = self._$editingRow.data('record');
+
+ self._updateRecordValuesFromEditForm(record, $editForm);
+ self._updateRecordValuesFromServerResponse(record, data);
+ self._updateRowTexts(self._$editingRow);
+
+ self._$editingRow.attr('data-record-key', self._getKeyValueOfRecord(record));
+
+ self._onRecordUpdated(self._$editingRow, data);
+
+ if (self.options.animationsEnabled) {
+ self._showUpdateAnimationForRow(self._$editingRow);
+ }
+
+ self._$editDiv.dialog("close");
+ },
+ function () {
+ self._showError(self.options.messages.serverCommunicationError);
+ self._setEnabledOfDialogButton($saveButton, true, self.options.messages.save);
+ });
+ },
+
+ /* Updates values of a record from given edit form
+ *************************************************************************/
+ _updateRecordValuesFromEditForm: function (record, $form) {
+ for (var i = 0; i < this._fieldList.length; i++) {
+ var fieldName = this._fieldList[i];
+ var field = this.options.fields[fieldName];
+
+ //Do not update non-editable fields
+ if (field.edit == false) {
+ continue;
+ }
+
+ //Get field name and the input element of this field in the form
+ var $inputElement = $form.find('[name="' + fieldName + '"]');
+
+ //Update field in record according to it's type
+ if (field.type == 'date') {
+ var displayFormat = field.displayFormat || this.options.defaultDateFormat;
+ try {
+ var date = $.datepicker.parseDate(displayFormat, $inputElement.val());
+ record[fieldName] = '/Date(' + date.getTime() + ')/';
+ } catch (e) {
+ //TODO: Handle incorrect/different date formats
+ record[fieldName] = '/Date(' + (new Date()).getTime() + ')/';
+ }
+ } else if (field.options && field.type == 'radiobutton') {
+ var $checkedElement = $inputElement.filter('[checked="true"]');
+ if ($checkedElement.length) {
+ record[fieldName] = $checkedElement.val();
+ } else {
+ record[fieldName] = undefined;
+ }
+ } else {
+ record[fieldName] = $inputElement.val();
+ }
+ }
+ },
+
+
+ /* This method ensures updating of current record with server response,
+ * if server sends a Record object as response to updateAction.
+ *************************************************************************/
+ _updateRecordValuesFromServerResponse: function (record, serverResponse) {
+ if (!serverResponse || !serverResponse.Record) {
+ return;
+ }
+
+ $.extend(true, record, serverResponse.Record);
+ },
+
+ /* Gets text for a field of a record according to it's type.
+ *************************************************************************/
+ _getValueForRecordField: function (record, fieldName) {
+ var field = this.options.fields[fieldName];
+ var fieldValue = record[fieldName];
+ if (field.type == 'date') {
+ return this._getDisplayTextForDateRecordField(field, fieldValue);
+ } else {
+ return fieldValue;
+ }
+ },
+
+ /* Updates cells of a table row's text values from row's record values.
+ *************************************************************************/
+ _updateRowTexts: function ($tableRow) {
+ var record = $tableRow.data('record');
+ var $columns = $tableRow.find('td');
+ for (var i = 0; i < this._columnList.length; i++) {
+ var displayItem = this._getDisplayTextForRecordField(record, this._columnList[i]);
+ $columns.eq(this._firstDataColumnOffset + i).html(displayItem || '');
+ }
+
+ this._onRowUpdated($tableRow);
+ },
+
+ /* Shows 'updated' animation for a table row.
+ *************************************************************************/
+ _showUpdateAnimationForRow: function ($tableRow) {
+ $tableRow.stop(true, true).addClass('jtable-row-updated', 'slow', '', function () {
+ $tableRow.removeClass('jtable-row-updated', 5000);
+ });
+ },
+
+ /************************************************************************
+ * EVENT RAISING METHODS *
+ *************************************************************************/
+
+ _onRowUpdated: function ($row) {
+ this._trigger("rowUpdated", null, { row: $row, record: $row.data('record') });
+ },
+
+ _onRecordUpdated: function ($row, data) {
+ this._trigger("recordUpdated", null, { record: $row.data('record'), row: $row, serverResponse: data });
+ }
+
+ });
+
+})(jQuery);
diff --git a/dev/jquery.jtable.forms.js b/dev/jquery.jtable.forms.js
new file mode 100644
index 0000000..7e3b1f1
--- /dev/null
+++ b/dev/jquery.jtable.forms.js
@@ -0,0 +1,376 @@
+/************************************************************************
+* FORMS extension for jTable (base for edit/create forms) *
+*************************************************************************/
+(function ($) {
+
+ $.extend(true, $.hik.jtable.prototype, {
+
+ /************************************************************************
+ * PRIVATE METHODS *
+ *************************************************************************/
+
+ /* Submits a form asynchronously using AJAX.
+ * This method is needed, since form submitting logic can be overrided
+ * by extensions.
+ *************************************************************************/
+ _submitFormUsingAjax: function (url, formData, success, error) {
+ this._ajax({
+ url: url,
+ data: formData,
+ success: success,
+ error: error
+ });
+ },
+
+ /* Creates label for an input element.
+ *************************************************************************/
+ _createInputLabelForRecordField: function (fieldName) {
+ //TODO: May create label tag instead of a div.
+ return $('<div />')
+ .addClass('jtable-input-label')
+ .html(this.options.fields[fieldName].title);
+ },
+
+ /* Creates an input element according to field type.
+ *************************************************************************/
+ _createInputForRecordField: function (fieldName, value, record) {
+
+ //Get the field
+ var field = this.options.fields[fieldName];
+
+ //If value if not supplied, use defaultValue of the field
+ if (value == undefined || value == null) {
+ value = field.defaultValue;
+ }
+
+ //Use custom function if supplied
+ if (field.input) {
+ var $input = $(field.input({ value: value, record: record }));
+
+ //Add id attribute if does not exists
+ //TODO: Check if id is needed?
+ if (!$input.attr('id')) {
+ $input.attr('id', 'Edit-' + fieldName);
+ }
+
+ return $input;
+ }
+
+ //Create input according to field type
+ if (field.type == 'date') {
+ return this._createDateInputForField(field, fieldName, value);
+ } else if (field.type == 'textarea') {
+ return this._createTextAreaForField(field, fieldName, value);
+ } else if (field.type == 'password') {
+ return this._createPasswordInputForField(field, fieldName, value);
+ } else if (field.type == 'checkbox') {
+ return this._createCheckboxForField(field, fieldName, value);
+ } else if (field.options) {
+ if (field.type == 'radiobutton') {
+ return this._createRadioButtonListForField(field, fieldName, value);
+ } else {
+ return this._createDropDownListForField(field, fieldName, value);
+ }
+ } else {
+ return this._createTextInputForField(field, fieldName, value);
+ }
+ },
+
+ //Creates a hidden input element with given name and value.
+ _createInputForHidden: function (fieldName, value) {
+ if (value == undefined || value == null) {
+ value = "";
+ }
+
+ return $('<input type="hidden" value="' + value + '" name="' + fieldName + '" id="Edit-' + fieldName + '"></input>');
+ },
+
+ /* Creates a date input for a field.
+ *************************************************************************/
+ _createDateInputForField: function (field, fieldName, value) {
+ var $input = $('<input class="' + field.inputClass + '" id="Edit-' + fieldName + '" type="text"' + (value != undefined ? 'value="' + value + '"' : '') + ' name="' + fieldName + '"></input>');
+ var displayFormat = field.displayFormat || this.options.defaultDateFormat;
+ $input.datepicker({ dateFormat: displayFormat });
+ return $('<div />')
+ .addClass('jtable-input jtable-date-input')
+ .append($input);
+ },
+
+ /* Creates a textarea element for a field.
+ *************************************************************************/
+ _createTextAreaForField: function (field, fieldName, value) {
+ var $textArea = $('<textarea class="' + field.inputClass + '" id="Edit-' + fieldName + '" name="' + fieldName + '">' + (value || '') + '</textarea>');
+ return $('<div />')
+ .addClass('jtable-input jtable-textarea-input')
+ .append($textArea);
+ },
+
+ /* Creates a standart textbox for a field.
+ *************************************************************************/
+ _createTextInputForField: function (field, fieldName, value) {
+ var $input = $('<input class="' + field.inputClass + '" id="Edit-' + fieldName + '" type="text"' + (value != undefined ? 'value="' + value + '"' : '') + ' name="' + fieldName + '"></input>');
+ return $('<div />')
+ .addClass('jtable-input jtable-text-input')
+ .append($input);
+ },
+
+ /* Creates a password input for a field.
+ *************************************************************************/
+ _createPasswordInputForField: function (field, fieldName, value) {
+ var $input = $('<input class="' + field.inputClass + '" id="Edit-' + fieldName + '" type="password"' + (value != undefined ? 'value="' + value + '"' : '') + ' name="' + fieldName + '"></input>');
+ return $('<div />')
+ .addClass('jtable-input jtable-password-input')
+ .append($input);
+ },
+
+ /* Creates a checkboxfor a field.
+ *************************************************************************/
+ _createCheckboxForField: function (field, fieldName, value) {
+ var self = this;
+
+ //If value is undefined, get unchecked state's value
+ if (value == undefined) {
+ value = self._getCheckBoxPropertiesForFieldByState(fieldName, false).Value;
+ }
+
+ //Create a container div
+ var $containerDiv = $('<div />')
+ .addClass('jtable-input jtable-checkbox-input');
+
+ //Create checkbox and check if needed
+ var $checkBox = $('<input class="' + field.inputClass + '" id="Edit-' + fieldName + '" type="checkbox" name="' + fieldName + '" value="' + value + '" />')
+ .appendTo($containerDiv);
+
+ //Create display text of checkbox for current state
+ var $textSpan = $('<span>' + (field.formText || self._getCheckBoxTextForFieldByValue(fieldName, value)) + '</span>')
+ .appendTo($containerDiv);
+
+ //Check the checkbox if it's value is checked-value
+ if (self._getIsCheckBoxSelectedForFieldByValue(fieldName, value)) {
+ $checkBox.attr('checked', 'checked');
+ }
+
+ //This method sets checkbox's value and text according to state of the checkbox
+ var refreshCheckBoxValueAndText = function () {
+ var checkboxProps = self._getCheckBoxPropertiesForFieldByState(fieldName, $checkBox.is(':checked'));
+ $checkBox.attr('value', checkboxProps.Value);
+ $textSpan.html(field.formText || checkboxProps.DisplayText);
+ };
+
+ //Register to click event to change display text when state of checkbox is changed.
+ $checkBox.click(function () {
+ refreshCheckBoxValueAndText();
+ });
+
+ //Change checkbox state when clicked to text
+ if (field.setOnTextClick != false) {
+ $textSpan
+ .addClass('jtable-option-text-clickable')
+ .click(function() {
+ if ($checkBox.is(':checked')) {
+ $checkBox.attr('checked', false);
+ } else {
+ $checkBox.attr('checked', true);
+ }
+
+ refreshCheckBoxValueAndText();
+ });
+ }
+
+ return $containerDiv;
+ },
+
+ /* Creates a drop down list (combobox) input element for a field.
+ *************************************************************************/
+ _createDropDownListForField: function (field, fieldName, value) {
+ //Create a container div
+ var $containerDiv = $('<div />')
+ .addClass('jtable-input jtable-dropdown-input');
+
+ //Create select element
+ var $select = $('<select class="' + field.inputClass + '" id="Edit-' + fieldName + '" name="' + fieldName + '"></select>')
+ .appendTo($containerDiv);
+
+ //add options
+ var options = this._getOptionsWithCaching(fieldName);
+ $.each(options, function (propName, propValue) {
+ $select.append('<option value="' + propName + '"' + (propName == value ? ' selected="selected"' : '') + '>' + propValue + '</option>');
+ });
+
+ return $containerDiv;
+ },
+ /* Creates a radio button list for a field.
+ *************************************************************************/
+ _createRadioButtonListForField: function (field, fieldName, value) {
+ //Create a container div
+ var $containerDiv = $('<div />')
+ .addClass('jtable-input jtable-radiobuttonlist-input');
+
+ //create radio buttons
+ var options = this._getOptionsWithCaching(fieldName);
+ var radioButtonIndex = 0;
+ $.each(options, function (propName, propValue) {
+
+ var $radioButtonDiv = $('<div class=""></div>')
+ .addClass('jtable-radio-input')
+ .appendTo($containerDiv);
+
+ var $radioButton = $('<input type="radio" id="Edit-' + fieldName + (radioButtonIndex++) + '" class="' + field.inputClass + '" name="' + fieldName + '" value="' + propName + '"' + ((propName == (value + '')) ? ' checked="true"' : '') + ' />')
+ .appendTo($radioButtonDiv);
+
+ var $textSpan = $('<span></span>')
+ .html(propValue)
+ .appendTo($radioButtonDiv);
+
+ if (field.setOnTextClick != false) {
+ $textSpan
+ .addClass('jtable-option-text-clickable')
+ .click(function() {
+ if (!$radioButton.is(':checked')) {
+ $radioButton.attr('checked', true);
+ }
+ });
+ }
+ });
+
+ return $containerDiv;
+ },
+
+ /* Gets display text for a checkbox field.
+ *************************************************************************/
+ _getCheckBoxTextForFieldByValue: function (fieldName, value) {
+ return this.options.fields[fieldName].values[value];
+ },
+
+ /* Returns true if given field's value must be checked state.
+ *************************************************************************/
+ _getIsCheckBoxSelectedForFieldByValue: function (fieldName, value) {
+ return (this._createCheckBoxStateArrayForFieldWithCaching(fieldName)[1].Value.toString() == value.toString());
+ },
+
+ /* Gets an object for a checkbox field that has Value and DisplayText
+ * properties.
+ *************************************************************************/
+ _getCheckBoxPropertiesForFieldByState: function (fieldName, checked) {
+ return this._createCheckBoxStateArrayForFieldWithCaching(fieldName)[(checked ? 1 : 0)];
+ },
+
+ /* Calls _createCheckBoxStateArrayForField with caching.
+ *************************************************************************/
+ _createCheckBoxStateArrayForFieldWithCaching: function (fieldName) {
+ var cacheKey = 'checkbox_' + fieldName;
+ if (!this._cache[cacheKey]) {
+
+ this._cache[cacheKey] = this._createCheckBoxStateArrayForField(fieldName);
+ }
+
+ return this._cache[cacheKey];
+ },
+
+ /* Creates a two element array of objects for states of a checkbox field.
+ * First element for unchecked state, second for checked state.
+ * Each object has two properties: Value and DisplayText
+ *************************************************************************/
+ _createCheckBoxStateArrayForField: function (fieldName) {
+ var stateArray = [];
+ var currentIndex = 0;
+ $.each(this.options.fields[fieldName].values, function (propName, propValue) {
+ if (currentIndex++ < 2) {
+ stateArray.push({ 'Value': propName, 'DisplayText': propValue });
+ }
+ });
+
+ return stateArray;
+ },
+
+ /* Gets options from cache if exists, else downloads and caches.
+ * TODO: Allow options to be a function and send record to the function.
+ *************************************************************************/
+ _getOptionsWithCaching: function (fieldName) {
+ var cacheKey = 'options_' + fieldName;
+ if (!this._cache[cacheKey]) {
+ var optionsSource = this.options.fields[fieldName].options;
+ //Build options according to it's source type
+ if (typeof optionsSource == 'string') {
+ //It is an Url to rownload options
+ this._cache[cacheKey] = this._downloadOptions(fieldName, optionsSource);
+ } else if (jQuery.isArray(optionsSource)) {
+ //It is an array of options
+ this._cache[cacheKey] = this._buildOptionsFromArray(optionsSource);
+ } else {
+ //It is an object that it's properties are options, so use directly this object
+ this._cache[cacheKey] = optionsSource;
+ }
+ }
+
+ return this._cache[cacheKey];
+ },
+
+ /* Download options for a field from server.
+ *************************************************************************/
+ _downloadOptions: function (fieldName, url) {
+ var self = this;
+ var options = {};
+
+ self._ajax({
+ url: url,
+ async: false,
+ success: function (data) {
+ if (data.Result != 'OK') {
+ self._showError(data.Message);
+ return;
+ }
+
+ //Get options from incoming data
+ for (var i = 0; i < data.Options.length; i++) {
+ options[data.Options[i].Value] = data.Options[i].DisplayText;
+ }
+ },
+ error: function () {
+ var errMessage = self._formatString(self.options.messages.cannotLoadOptionsFor, fieldName);
+ self._showError(errMessage);
+ }
+ });
+
+ return options;
+ },
+
+ /* Creates an options object (that it's property is value, value is displaytext)
+ * from a simple array.
+ *************************************************************************/
+ _buildOptionsFromArray: function (optionsArray) {
+ var options = {};
+ for (var i = 0; i < optionsArray.length; i++) {
+ options[optionsArray[i]] = optionsArray[i];
+ }
+
+ return options;
+ },
+
+ /* Sets enabled/disabled state of a dialog button.
+ *************************************************************************/
+ _setEnabledOfDialogButton: function ($button, enabled, buttonText) {
+ if (!$button) {
+ return;
+ }
+
+ if (enabled != false) {
+ $button
+ .removeAttr('disabled')
+ .removeClass('ui-state-disabled');
+ } else {
+ $button
+ .attr('disabled', 'disabled')
+ .addClass('ui-state-disabled');
+ }
+
+ if (buttonText) {
+ $button
+ .find('span')
+ .text(buttonText);
+ }
+ }
+
+ });
+
+})(jQuery);
diff --git a/dev/jquery.jtable.header.txt b/dev/jquery.jtable.header.txt
new file mode 100644
index 0000000..2b02121
--- /dev/null
+++ b/dev/jquery.jtable.header.txt
@@ -0,0 +1,28 @@
+/*
+
+jTable 2.0.0
+http://www.jtable.org
+
+---------------------------------------------------------------------------
+
+Copyright (C) 2011-2013 by Halil İbrahim Kalkan (http://www.halilibrahimkalkan.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/ \ No newline at end of file
diff --git a/dev/jquery.jtable.masterchild.js b/dev/jquery.jtable.masterchild.js
new file mode 100644
index 0000000..9b769a0
--- /dev/null
+++ b/dev/jquery.jtable.masterchild.js
@@ -0,0 +1,166 @@
+/************************************************************************
+* MASTER/CHILD tables extension for jTable *
+*************************************************************************/
+(function ($) {
+
+ //Reference to base object members
+ var base = {
+ _removeRowsFromTable: $.hik.jtable.prototype._removeRowsFromTable
+ };
+
+ //extension members
+ $.extend(true, $.hik.jtable.prototype, {
+
+ /************************************************************************
+ * DEFAULT OPTIONS / EVENTS *
+ *************************************************************************/
+ options: {
+ openChildAsAccordion: false
+ },
+
+ /************************************************************************
+ * PUBLIC METHODS *
+ *************************************************************************/
+
+ /* Creates and opens a new child table for given row.
+ *************************************************************************/
+ openChildTable: function ($row, tableOptions, opened) {
+ var self = this;
+
+ //Show close button as default
+ tableOptions.showCloseButton = (tableOptions.showCloseButton != false);
+
+ //Close child table when close button is clicked (default behavior)
+ if (tableOptions.showCloseButton && !tableOptions.closeRequested) {
+ tableOptions.closeRequested = function () {
+ self.closeChildTable($row);
+ };
+ }
+
+ //If accordion style, close open child table (if it does exists)
+ if (self.options.openChildAsAccordion) {
+ $row.siblings('.jtable-data-row').each(function () {
+ self.closeChildTable($(this));
+ });
+ }
+
+ //Close child table for this row and open new one for child table
+ self.closeChildTable($row, function () {
+ var $childRowColumn = self.getChildRow($row).children('td').empty();
+ var $childTableContainer = $('<div />')
+ .addClass('jtable-child-table-container')
+ .appendTo($childRowColumn);
+ $childRowColumn.data('childTable', $childTableContainer);
+ $childTableContainer.jtable(tableOptions);
+ self.openChildRow($row);
+ $childTableContainer.hide().slideDown('fast', function () {
+ if (opened) {
+ opened({
+ childTable: $childTableContainer
+ });
+ }
+ });
+ });
+ },
+
+ /* Closes child table for given row.
+ *************************************************************************/
+ closeChildTable: function ($row, closed) {
+ var self = this;
+
+ var $childRowColumn = this.getChildRow($row).children('td');
+ var $childTable = $childRowColumn.data('childTable');
+ if (!$childTable) {
+ if (closed) {
+ closed();
+ }
+
+ return;
+ }
+
+ $childRowColumn.data('childTable', null);
+ $childTable.slideUp('fast', function () {
+ $childTable.jtable('destroy');
+ $childTable.remove();
+ self.closeChildRow($row);
+ if (closed) {
+ closed();
+ }
+ });
+ },
+
+ /* Returns a boolean value indicates that if a child row is open for given row.
+ *************************************************************************/
+ isChildRowOpen: function ($row) {
+ return (this.getChildRow($row).is(':visible'));
+ },
+
+ /* Gets child row for given row, opens it if it's closed (Creates if needed).
+ *************************************************************************/
+ getChildRow: function ($row) {
+ return $row.data('childRow') || this._createChildRow($row);
+ },
+
+ /* Creates and opens child row for given row.
+ *************************************************************************/
+ openChildRow: function ($row) {
+ var $childRow = this.getChildRow($row);
+ if (!$childRow.is(':visible')) {
+ $childRow.show();
+ }
+
+ return $childRow;
+ },
+
+ /* Closes child row if it's open.
+ *************************************************************************/
+ closeChildRow: function ($row) {
+ var $childRow = this.getChildRow($row);
+ if ($childRow.is(':visible')) {
+ $childRow.hide();
+ }
+ },
+
+ /************************************************************************
+ * OVERRIDED METHODS *
+ *************************************************************************/
+
+ /* Overrides _removeRowsFromTable method to remove child rows of deleted rows.
+ *************************************************************************/
+ _removeRowsFromTable: function ($rows, reason) {
+ var self = this;
+
+ if (reason == 'deleted') {
+ $rows.each(function () {
+ var $row = $(this);
+ var $childRow = $row.data('childRow');
+ if ($childRow) {
+ self.closeChildTable($row);
+ $childRow.remove();
+ }
+ });
+ }
+
+ base._removeRowsFromTable.apply(this, arguments);
+ },
+
+ /************************************************************************
+ * PRIVATE METHODS *
+ *************************************************************************/
+
+ /* Creates a child row for a row, hides and returns it.
+ *************************************************************************/
+ _createChildRow: function ($row) {
+ var totalColumnCount = this._$table.find('thead th').length;
+ var $childRow = $('<tr></tr>')
+ .addClass('jtable-child-row')
+ .append('<td colspan="' + totalColumnCount + '"></td>');
+ $row.after($childRow);
+ $row.data('childRow', $childRow);
+ $childRow.hide();
+ return $childRow;
+ }
+
+ });
+
+})(jQuery); \ No newline at end of file
diff --git a/dev/jquery.jtable.paging.js b/dev/jquery.jtable.paging.js
new file mode 100644
index 0000000..a272c90
--- /dev/null
+++ b/dev/jquery.jtable.paging.js
@@ -0,0 +1,292 @@
+/************************************************************************
+* PAGING extension for jTable *
+*************************************************************************/
+(function($) {
+
+ //Reference to base object members
+ var base = {
+ load: $.hik.jtable.prototype.load,
+ _create: $.hik.jtable.prototype._create,
+ _createRecordLoadUrl: $.hik.jtable.prototype._createRecordLoadUrl,
+ _addRowToTable: $.hik.jtable.prototype._addRowToTable,
+ _addRow: $.hik.jtable.prototype._addRow,
+ _removeRowsFromTable: $.hik.jtable.prototype._removeRowsFromTable,
+ _onRecordsLoaded: $.hik.jtable.prototype._onRecordsLoaded
+ };
+
+ //extension members
+ $.extend(true, $.hik.jtable.prototype, {
+
+ /************************************************************************
+ * DEFAULT OPTIONS / EVENTS *
+ *************************************************************************/
+ options: {
+ paging: false,
+ pageSize: 10,
+
+ messages: {
+ pagingInfo: 'Showing {0} to {1} of {2} records'
+ }
+ },
+
+ /************************************************************************
+ * PRIVATE FIELDS *
+ *************************************************************************/
+
+ _$pagingListArea: null, //Reference to the page list area in to bottom panel
+ _totalRecordCount: 0, //Total count of records on all pages
+ _currentPageNo: 1, //Current page number
+
+ /************************************************************************
+ * CONSTRUCTOR AND INITIALIZING METHODS *
+ *************************************************************************/
+
+ /* Overrides base method to do paging-specific constructions.
+ *************************************************************************/
+ _create: function() {
+ base._create.apply(this, arguments);
+ this._createPageListArea();
+ },
+
+ /* Creates page list area if paging enabled.
+ *************************************************************************/
+ _createPageListArea: function() {
+ if (!this.options.paging) {
+ return;
+ }
+
+ this._$pagingListArea = $('<span></span>')
+ .addClass('jtable-page-list')
+ .prependTo(this._$bottomPanel.find('.jtable-left-area'));
+ },
+
+ /************************************************************************
+ * OVERRIDED METHODS *
+ *************************************************************************/
+
+ /* Overrides load method to set current page to 1.
+ *************************************************************************/
+ load: function() {
+ this._currentPageNo = 1;
+ base.load.apply(this, arguments);
+ },
+
+ /* Overrides _createRecordLoadUrl method to add paging info to URL.
+ *************************************************************************/
+ _createRecordLoadUrl: function() {
+ var loadUrl = base._createRecordLoadUrl.apply(this, arguments);
+ loadUrl = this._addPagingInfoToUrl(loadUrl, this._currentPageNo);
+ return loadUrl;
+ },
+
+ /* Overrides _addRowToTable method to re-load table when a new row is created.
+ * NOTE: THIS METHOD IS DEPRECATED AND WILL BE REMOVED FROM FEATURE RELEASES.
+ * USE _addRow METHOD.
+ *************************************************************************/
+ _addRowToTable: function($tableRow, index, isNewRow) {
+ if (isNewRow && this.options.paging) {
+ this._reloadTable();
+ return;
+ }
+
+ base._addRowToTable.apply(this, arguments);
+ },
+
+ /* Overrides _addRow method to re-load table when a new row is created.
+ *************************************************************************/
+ _addRow: function ($row, options) {
+ if (options && options.isNewRow && this.options.paging) {
+ this._reloadTable();
+ return;
+ }
+
+ base._addRow.apply(this, arguments);
+ },
+
+ /* Overrides _removeRowsFromTable method to re-load table when a row is removed from table.
+ *************************************************************************/
+ _removeRowsFromTable: function($rows, reason) {
+ base._removeRowsFromTable.apply(this, arguments);
+
+ if (this.options.paging) {
+ if (this._$tableRows.length <= 0 && this._currentPageNo > 1) {
+ --this._currentPageNo;
+ }
+
+ this._reloadTable();
+ }
+ },
+
+ /* Overrides _onRecordsLoaded method to to do paging specific tasks.
+ *************************************************************************/
+ _onRecordsLoaded: function(data) {
+ this._totalRecordCount = data.TotalRecordCount;
+ this._createPagingList();
+ base._onRecordsLoaded.apply(this, arguments);
+ },
+
+ /************************************************************************
+ * PRIVATE METHODS *
+ *************************************************************************/
+
+ /* Adds jtStartIndex and jtPageSize parameters to a URL as query string.
+ *************************************************************************/
+ _addPagingInfoToUrl: function(url, pageNumber) {
+ if (!this.options.paging) {
+ return url;
+ }
+
+ var jtStartIndex = (pageNumber - 1) * this.options.pageSize;
+ var jtPageSize = this.options.pageSize;
+
+ return (url + (url.indexOf('?') < 0 ? '?' : '&') + 'jtStartIndex=' + jtStartIndex + '&jtPageSize=' + jtPageSize);
+ },
+
+ /* Creates and shows the page list.
+ *************************************************************************/
+ _createPagingList: function() {
+ if (!this.options.paging || this.options.pageSize <= 0) {
+ return;
+ }
+
+ this._$pagingListArea.empty();
+
+ var pageCount = this._calculatePageCount();
+
+ this._createFirstAndPreviousPageButtons();
+ this._createPageNumberButtons(this._calculatePageNumbers(pageCount));
+ this._createLastAndNextPageButtons(pageCount);
+ this._createPagingInfo();
+ this._bindClickEventsToPageNumberButtons();
+ },
+
+ /* Creates and shows previous and first page links.
+ *************************************************************************/
+ _createFirstAndPreviousPageButtons: function() {
+ if (this._currentPageNo > 1) {
+ $('<span></span>')
+ .addClass('jtable-page-number-first')
+ .html('|&lt')
+ .data('pageNumber', 1)
+ .appendTo(this._$pagingListArea);
+ $('<span></span>')
+ .addClass('jtable-page-number-previous')
+ .html('&lt')
+ .data('pageNumber', this._currentPageNo - 1)
+ .appendTo(this._$pagingListArea);
+ }
+ },
+
+ /* Creates and shows next and last page links.
+ *************************************************************************/
+ _createLastAndNextPageButtons: function(pageCount) {
+ if (this._currentPageNo < pageCount) {
+ $('<span></span>')
+ .addClass('jtable-page-number-next')
+ .html('&gt')
+ .data('pageNumber', this._currentPageNo + 1)
+ .appendTo(this._$pagingListArea);
+ $('<span></span>')
+ .addClass('jtable-page-number-last')
+ .html('&gt|')
+ .data('pageNumber', pageCount)
+ .appendTo(this._$pagingListArea);
+ }
+ },
+
+ /* Creates and shows page number links for given number array.
+ *************************************************************************/
+ _createPageNumberButtons: function(pageNumbers) {
+ var previousNumber = 0;
+ for (var i = 0; i < pageNumbers.length; i++) {
+ //Create "..." between page numbers if needed
+ if ((pageNumbers[i] - previousNumber) > 1) {
+ $('<span></span>')
+ .addClass('jtable-page-number-space')
+ .html('...')
+ .appendTo(this._$pagingListArea);
+ }
+
+ this._createPageNumberButton(pageNumbers[i]);
+ previousNumber = pageNumbers[i];
+ }
+ },
+
+ /* Creates a page number link and adds to paging area.
+ *************************************************************************/
+ _createPageNumberButton: function(pageNumber) {
+ $('<span class="' + (this._currentPageNo == pageNumber ? 'jtable-page-number-active' : 'jtable-page-number') + '">' + pageNumber + '</span>')
+ .data('pageNumber', pageNumber)
+ .appendTo(this._$pagingListArea);
+ },
+
+ /* Calculates total page count according to page size and total record count.
+ *************************************************************************/
+ _calculatePageCount: function() {
+ var pageCount = Math.floor(this._totalRecordCount / this.options.pageSize);
+ if (this._totalRecordCount % this.options.pageSize != 0) {
+ ++pageCount;
+ }
+
+ return pageCount;
+ },
+
+ /* Calculates page numbers and returns an array of these numbers.
+ *************************************************************************/
+ _calculatePageNumbers: function(pageCount) {
+ if (pageCount <= 6) {
+ //Show all pages
+ var pageNumbers = [];
+ for (var i = 1; i <= pageCount; ++i) {
+ pageNumbers.push(i);
+ }
+
+ return pageNumbers;
+ } else {
+ //show first three, last three, current, previous and next page numbers
+ var shownPageNumbers = [1, 2, 3, pageCount - 2, pageCount - 1, pageCount];
+ var previousPageNo = this._normalizeNumber(this._currentPageNo - 1, 1, pageCount, 1);
+ var nextPageNo = this._normalizeNumber(this._currentPageNo + 1, 1, pageCount, 1);
+
+ this._insertToArrayIfDoesNotExists(shownPageNumbers, previousPageNo);
+ this._insertToArrayIfDoesNotExists(shownPageNumbers, this._currentPageNo);
+ this._insertToArrayIfDoesNotExists(shownPageNumbers, nextPageNo);
+
+ shownPageNumbers.sort(function(a, b) { return a - b; });
+ return shownPageNumbers;
+ }
+ },
+
+ /* Creates and shows paging informations.
+ *************************************************************************/
+ _createPagingInfo: function() {
+ var startNo = (this._currentPageNo - 1) * this.options.pageSize + 1;
+ var endNo = this._currentPageNo * this.options.pageSize;
+ endNo = this._normalizeNumber(endNo, startNo, this._totalRecordCount, 0);
+
+ if (endNo >= startNo) {
+ var pagingInfoMessage = this._formatString(this.options.messages.pagingInfo, startNo, endNo, this._totalRecordCount);
+ $('<span></span>')
+ .addClass('jtable-page-info')
+ .html(pagingInfoMessage)
+ .appendTo(this._$pagingListArea);
+ }
+ },
+
+ /* Binds click events of all page links to change the page.
+ *************************************************************************/
+ _bindClickEventsToPageNumberButtons: function() {
+ var self = this;
+ self._$pagingListArea
+ .find('.jtable-page-number,.jtable-page-number-previous,.jtable-page-number-next,.jtable-page-number-first,.jtable-page-number-last')
+ .click(function(e) {
+ e.preventDefault();
+ var $this = $(this);
+ self._currentPageNo = $this.data('pageNumber');
+ self._reloadTable();
+ });
+ }
+
+ });
+
+})(jQuery);
diff --git a/dev/jquery.jtable.selecting.js b/dev/jquery.jtable.selecting.js
new file mode 100644
index 0000000..63c4457
--- /dev/null
+++ b/dev/jquery.jtable.selecting.js
@@ -0,0 +1,377 @@
+/************************************************************************
+* SELECTING extension for jTable *
+*************************************************************************/
+(function ($) {
+
+ //Reference to base object members
+ var base = {
+ _create: $.hik.jtable.prototype._create,
+ _addColumnsToHeaderRow: $.hik.jtable.prototype._addColumnsToHeaderRow,
+ _addCellsToRowUsingRecord: $.hik.jtable.prototype._addCellsToRowUsingRecord,
+ _onLoadingRecords: $.hik.jtable.prototype._onLoadingRecords,
+ _onRecordsLoaded: $.hik.jtable.prototype._onRecordsLoaded,
+ _onRowsRemoved: $.hik.jtable.prototype._onRowsRemoved
+ };
+
+ //extension members
+ $.extend(true, $.hik.jtable.prototype, {
+
+ /************************************************************************
+ * DEFAULT OPTIONS / EVENTS *
+ *************************************************************************/
+ options: {
+
+ //Options
+ selecting: false,
+ multiselect: false,
+ selectingCheckboxes: false,
+ selectOnRowClick: true,
+
+ //Events
+ selectionChanged: function (event, data) { }
+ },
+
+ /************************************************************************
+ * PRIVATE FIELDS *
+ *************************************************************************/
+
+ _selectedRecordIdsBeforeLoad: null, //This array is used to store selected row Id's to restore them after a page refresh (string array).
+ _$selectAllCheckbox: null, //Reference to the 'select/deselect all' checkbox (jQuery object)
+ _shiftKeyDown: false, //True, if shift key is currently down.
+
+ /************************************************************************
+ * CONSTRUCTOR *
+ *************************************************************************/
+
+ /* Overrides base method to do selecting-specific constructions.
+ *************************************************************************/
+ _create: function () {
+ if (this.options.selecting && this.options.selectingCheckboxes) {
+ ++this._firstDataColumnOffset;
+ }
+
+ this._bindKeyboardEvents();
+
+ //Call base method
+ base._create.apply(this, arguments);
+ },
+
+ /* Registers to keyboard events those are needed for selection
+ *************************************************************************/
+ _bindKeyboardEvents: function () {
+ var self = this;
+ //Register to events to set _shiftKeyDown value
+ $(document)
+ .keydown(function(event) {
+ switch (event.which) {
+ case 16:
+ self._shiftKeyDown = true;
+ break;
+ }
+ })
+ .keyup(function(event) {
+ switch (event.which) {
+ case 16:
+ self._shiftKeyDown = false;
+ break;
+ }
+ });
+ },
+
+ /************************************************************************
+ * PUBLIC METHODS *
+ *************************************************************************/
+
+ /* Gets jQuery selection for currently selected rows.
+ *************************************************************************/
+ selectedRows: function () {
+ return this._getSelectedRows();
+ },
+
+ /* Makes row/rows 'selected'.
+ *************************************************************************/
+ selectRows: function ($rows) {
+ this._selectRows($rows);
+ this._onSelectionChanged(); //TODO: trigger only if selected rows changes?
+ },
+
+ /************************************************************************
+ * OVERRIDED METHODS *
+ *************************************************************************/
+
+ /* Overrides base method to add a 'select column' to header row.
+ *************************************************************************/
+ _addColumnsToHeaderRow: function ($tr) {
+ if (this.options.selecting && this.options.selectingCheckboxes) {
+ if (this.options.multiselect) {
+ $tr.append(this._createSelectAllHeader());
+ } else {
+ $tr.append(this._createEmptyCommandHeader());
+ }
+ }
+
+ base._addColumnsToHeaderRow.apply(this, arguments);
+ },
+
+ /* Overrides base method to add a 'delete command cell' to a row.
+ *************************************************************************/
+ _addCellsToRowUsingRecord: function ($row) {
+ if (this.options.selecting) {
+ this._makeRowSelectable($row);
+ }
+
+ base._addCellsToRowUsingRecord.apply(this, arguments);
+ },
+
+ /* Overrides base event to store selection list
+ *************************************************************************/
+ _onLoadingRecords: function () {
+ this._storeSelectionList();
+ base._onLoadingRecords.apply(this, arguments);
+ },
+
+ /* Overrides base event to restore selection list
+ *************************************************************************/
+ _onRecordsLoaded: function () {
+ this._restoreSelectionList();
+ base._onRecordsLoaded.apply(this, arguments);
+ },
+
+ /* Overrides base event to check is any selected row is being removed.
+ *************************************************************************/
+ _onRowsRemoved: function ($rows, reason) {
+ if ((reason != 'reloading') && this.options.selecting && ($rows.filter('.jtable-row-selected').length > 0)) {
+ this._onSelectionChanged();
+ }
+
+ base._onRowsRemoved.apply(this, arguments);
+ },
+
+ /************************************************************************
+ * PRIVATE METHODS *
+ *************************************************************************/
+
+ /* Creates a header column to select/deselect all rows.
+ *************************************************************************/
+ _createSelectAllHeader: function () {
+ var self = this;
+
+ var $columnHeader = $('<th class=""></th>')
+ .addClass('jtable-command-column-header jtable-column-header-selecting');
+
+ var $headerContainer = $('<div />')
+ .addClass('jtable-column-header-container')
+ .appendTo($columnHeader);
+
+ self._$selectAllCheckbox = $('<input type="checkbox" />')
+ .appendTo($headerContainer)
+ .click(function() {
+ if (self._$tableRows.length <= 0) {
+ self._$selectAllCheckbox.attr('checked', false);
+ return;
+ }
+
+ var allRows = self._$tableBody.find('tr.jtable-data-row');
+ if (self._$selectAllCheckbox.is(':checked')) {
+ self._selectRows(allRows);
+ } else {
+ self._deselectRows(allRows);
+ }
+
+ self._onSelectionChanged();
+ });
+
+ return $columnHeader;
+ },
+
+ /* Stores Id's of currently selected records to _selectedRecordIdsBeforeLoad.
+ *************************************************************************/
+ _storeSelectionList: function () {
+ var self = this;
+
+ if (!self.options.selecting) {
+ return;
+ }
+
+ self._selectedRecordIdsBeforeLoad = [];
+ self._getSelectedRows().each(function () {
+ self._selectedRecordIdsBeforeLoad.push(self._getKeyValueOfRecord($(this).data('record')));
+ });
+ },
+
+ /* Selects rows whose Id is in _selectedRecordIdsBeforeLoad;
+ *************************************************************************/
+ _restoreSelectionList: function () {
+ var self = this;
+
+ if (!self.options.selecting) {
+ return;
+ }
+
+ var selectedRowCount = 0;
+ for (var i = 0; i < self._$tableRows.length; ++i) {
+ var recordId = self._getKeyValueOfRecord(self._$tableRows[i].data('record'));
+ if ($.inArray(recordId, self._selectedRecordIdsBeforeLoad) > -1) {
+ self._selectRows(self._$tableRows[i]);
+ ++selectedRowCount;
+ }
+ }
+
+ if (self._selectedRecordIdsBeforeLoad.length > 0 && self._selectedRecordIdsBeforeLoad.length != selectedRowCount) {
+ self._onSelectionChanged();
+ }
+
+ self._selectedRecordIdsBeforeLoad = [];
+ self._refreshSelectAllCheckboxState();
+ },
+
+ /* Gets all selected rows.
+ *************************************************************************/
+ _getSelectedRows: function () {
+ return this._$tableBody
+ .find('.jtable-row-selected');
+ },
+
+ /* Adds selectable feature to a row.
+ *************************************************************************/
+ _makeRowSelectable: function ($row) {
+ var self = this;
+
+ //Select/deselect on row click
+ if (self.options.selectOnRowClick) {
+ $row.click(function () {
+ self._invertRowSelection($row);
+ });
+ }
+
+ //'select/deselect' checkbox column
+ if (self.options.selectingCheckboxes) {
+ var $cell = $('<td></td>')
+ .addClass('jtable-selecting-column');
+ var $selectCheckbox = $('<input type="checkbox" />')
+ .appendTo($cell);
+ if (!self.options.selectOnRowClick) {
+ $selectCheckbox.click(function () {
+ self._invertRowSelection($row);
+ });
+ }
+
+ $row.append($cell);
+ }
+ },
+
+ /* Inverts selection state of a single row.
+ *************************************************************************/
+ _invertRowSelection: function ($row) {
+ if ($row.hasClass('jtable-row-selected')) {
+ this._deselectRows($row);
+ } else {
+ //Shift key?
+ if (this._shiftKeyDown) {
+ var rowIndex = this._findRowIndex($row);
+ //try to select row and above rows until first selected row
+ var beforeIndex = this._findFirstSelectedRowIndexBeforeIndex(rowIndex) + 1;
+ if (beforeIndex > 0 && beforeIndex < rowIndex) {
+ this._selectRows(this._$tableBody.find('tr').slice(beforeIndex, rowIndex + 1));
+ } else {
+ //try to select row and below rows until first selected row
+ var afterIndex = this._findFirstSelectedRowIndexAfterIndex(rowIndex) - 1;
+ if (afterIndex > rowIndex) {
+ this._selectRows(this._$tableBody.find('tr').slice(rowIndex, afterIndex + 1));
+ } else {
+ //just select this row
+ this._selectRows($row);
+ }
+ }
+ } else {
+ this._selectRows($row);
+ }
+ }
+
+ this._onSelectionChanged();
+ },
+
+ /* Search for a selected row (that is before given row index) to up and returns it's index
+ *************************************************************************/
+ _findFirstSelectedRowIndexBeforeIndex: function (rowIndex) {
+ for (var i = rowIndex - 1; i >= 0; --i) {
+ if (this._$tableRows[i].hasClass('jtable-row-selected')) {
+ return i;
+ }
+ }
+
+ return -1;
+ },
+
+ /* Search for a selected row (that is after given row index) to down and returns it's index
+ *************************************************************************/
+ _findFirstSelectedRowIndexAfterIndex: function (rowIndex) {
+ for (var i = rowIndex + 1; i < this._$tableRows.length; ++i) {
+ if (this._$tableRows[i].hasClass('jtable-row-selected')) {
+ return i;
+ }
+ }
+
+ return -1;
+ },
+
+ /* Makes row/rows 'selected'.
+ *************************************************************************/
+ _selectRows: function ($rows) {
+
+ if (!this.options.multiselect) {
+ this._deselectRows(this._getSelectedRows());
+ }
+
+ $rows.addClass('jtable-row-selected');
+ if (this.options.selectingCheckboxes) {
+ $rows.find('td.jtable-selecting-column input').attr('checked', true);
+ }
+
+ this._refreshSelectAllCheckboxState();
+ },
+
+ /* Makes row/rows 'non selected'.
+ *************************************************************************/
+ _deselectRows: function ($rows) {
+ $rows.removeClass('jtable-row-selected');
+ if (this.options.selectingCheckboxes) {
+ $rows.find('td.jtable-selecting-column input').removeAttr('checked');
+ }
+
+ this._refreshSelectAllCheckboxState();
+ },
+
+ /* Updates state of the 'select/deselect' all checkbox according to count of selected rows.
+ *************************************************************************/
+ _refreshSelectAllCheckboxState: function () {
+ if (!this.options.selectingCheckboxes || !this.options.multiselect) {
+ return;
+ }
+
+ var totalRowCount = this._$tableRows.length;
+ var selectedRowCount = this._getSelectedRows().length;
+
+ if (selectedRowCount == 0) {
+ this._$selectAllCheckbox.prop('indeterminate', false);
+ this._$selectAllCheckbox.attr('checked', false);
+ } else if (selectedRowCount == totalRowCount) {
+ this._$selectAllCheckbox.prop('indeterminate', false);
+ this._$selectAllCheckbox.attr('checked', true);
+ } else {
+ this._$selectAllCheckbox.attr('checked', false);
+ this._$selectAllCheckbox.prop('indeterminate', true);
+ }
+ },
+
+ /************************************************************************
+ * EVENT RAISING METHODS *
+ *************************************************************************/
+
+ _onSelectionChanged: function () {
+ this._trigger("selectionChanged", null, {});
+ }
+
+ });
+
+})(jQuery);
diff --git a/dev/jquery.jtable.sorting.js b/dev/jquery.jtable.sorting.js
new file mode 100644
index 0000000..d5b9300
--- /dev/null
+++ b/dev/jquery.jtable.sorting.js
@@ -0,0 +1,123 @@
+/************************************************************************
+* SORTING extension for jTable *
+*************************************************************************/
+(function ($) {
+
+ //Reference to base object members
+ var base = {
+ _create: $.hik.jtable.prototype._create,
+ _normalizeFieldOptions: $.hik.jtable.prototype._normalizeFieldOptions,
+ _createHeaderCellForField: $.hik.jtable.prototype._createHeaderCellForField,
+ _createRecordLoadUrl: $.hik.jtable.prototype._createRecordLoadUrl
+ };
+
+ //extension members
+ $.extend(true, $.hik.jtable.prototype, {
+
+ /************************************************************************
+ * DEFAULT OPTIONS / EVENTS *
+ *************************************************************************/
+ options: {
+ sorting: false,
+ defaultSorting: ''
+ },
+
+ /************************************************************************
+ * PRIVATE FIELDS *
+ *************************************************************************/
+
+ _lastSorting: '', //Last sorting of the table
+
+ /************************************************************************
+ * OVERRIDED METHODS *
+ *************************************************************************/
+
+ /* Overrides _normalizeFieldOptions method to normalize sorting option for fields.
+ *************************************************************************/
+ _normalizeFieldOptions: function (fieldName, props) {
+ base._normalizeFieldOptions.apply(this, arguments);
+ props.sorting = (props.sorting != false);
+ },
+
+ /* Overrides _createHeaderCellForField to make columns sortable.
+ *************************************************************************/
+ _createHeaderCellForField: function (fieldName, field) {
+ var $headerCell = base._createHeaderCellForField.apply(this, arguments);
+ if (this.options.sorting && field.sorting) {
+ this._makeColumnSortable($headerCell, fieldName);
+ }
+
+ return $headerCell;
+ },
+
+ /* Overrides _createRecordLoadUrl to add sorting specific info to URL.
+ *************************************************************************/
+ _createRecordLoadUrl: function () {
+ var loadUrl = base._createRecordLoadUrl.apply(this, arguments);
+ loadUrl = this._addSortingInfoToUrl(loadUrl);
+ return loadUrl;
+ },
+
+ /************************************************************************
+ * PRIVATE METHODS *
+ *************************************************************************/
+
+ /* Makes a column sortable.
+ *************************************************************************/
+ _makeColumnSortable: function ($columnHeader, fieldName) {
+ var self = this;
+ $columnHeader
+ .addClass('jtable-column-header-sortable')
+ .click(function(e) {
+ e.preventDefault();
+ self._sortTableByColumn($columnHeader);
+ });
+
+ //Default sorting?
+ if (self.options.defaultSorting.indexOf(fieldName) > -1) {
+ if (self.options.defaultSorting.indexOf(' DESC') > -1) {
+ $columnHeader.addClass('jtable-column-header-sorted-desc');
+ self._lastSorting = fieldName + " DESC";
+ } else {
+ $columnHeader.addClass('jtable-column-header-sorted-asc');
+ self._lastSorting = fieldName + " ASC";
+ }
+ }
+ },
+
+ /* Sorts table according to a column header.
+ *************************************************************************/
+ _sortTableByColumn: function ($columnHeader) {
+ //Remove sorting styles from all columns except this one
+ $columnHeader.siblings().removeClass('jtable-column-header-sorted-asc jtable-column-header-sorted-desc');
+
+ //Sort ASC or DESC according to current sorting state
+ if ($columnHeader.hasClass('jtable-column-header-sorted-asc')) {
+ $columnHeader
+ .removeClass('jtable-column-header-sorted-asc')
+ .addClass('jtable-column-header-sorted-desc');
+ this._lastSorting = $columnHeader.data('fieldName') + " DESC";
+ } else {
+ $columnHeader
+ .removeClass('jtable-column-header-sorted-desc')
+ .addClass('jtable-column-header-sorted-asc');
+ this._lastSorting = $columnHeader.data('fieldName') + " ASC";
+ }
+
+ //Load current page again
+ this._reloadTable();
+ },
+
+ /* Adds jtSorting parameter to a URL as query string.
+ *************************************************************************/
+ _addSortingInfoToUrl: function (url) {
+ if (!this.options.sorting || this._lastSorting == '') {
+ return url;
+ }
+
+ return (url + (url.indexOf('?') < 0 ? '?' : '&') + 'jtSorting=' + this._lastSorting);
+ }
+
+ });
+
+})(jQuery);
diff --git a/dev/jquery.jtable.utils.js b/dev/jquery.jtable.utils.js
new file mode 100644
index 0000000..fe4ba4f
--- /dev/null
+++ b/dev/jquery.jtable.utils.js
@@ -0,0 +1,133 @@
+/************************************************************************
+* Some UTULITY methods used by jTable *
+*************************************************************************/
+(function ($) {
+
+ $.extend(true, $.hik.jtable.prototype, {
+
+ /* Gets property value of an object recursively.
+ *************************************************************************/
+ _getPropertyOfObject: function (obj, propName) {
+ if (propName.indexOf('.') < 0) {
+ return obj[propName];
+ } else {
+ var preDot = propName.substring(0, propName.indexOf('.'));
+ var postDot = propName.substring(propName.indexOf('.') + 1);
+ return this._getPropertyOfObject(obj[preDot], postDot);
+ }
+ },
+
+ /* Sets property value of an object recursively.
+ *************************************************************************/
+ _setPropertyOfObject: function (obj, propName, value) {
+ if (propName.indexOf('.') < 0) {
+ obj[propName] = value;
+ } else {
+ var preDot = propName.substring(0, propName.indexOf('.'));
+ var postDot = propName.substring(propName.indexOf('.') + 1);
+ this._setPropertyOfObject(obj[preDot], postDot, value);
+ }
+ },
+
+ /* Inserts a value to an array if it does not exists in the array.
+ *************************************************************************/
+ _insertToArrayIfDoesNotExists: function (array, value) {
+ if ($.inArray(value, array) < 0) {
+ array.push(value);
+ }
+ },
+
+ /* Finds index of an element in an array according to given comparision function
+ *************************************************************************/
+ _findIndexInArray: function (value, array, compareFunc) {
+
+ //If not defined, use default comparision
+ if (!compareFunc) {
+ compareFunc = function (a, b) {
+ return a == b;
+ };
+ }
+
+ for (var i = 0; i < array.length; i++) {
+ if (compareFunc(value, array[i])) {
+ return i;
+ }
+ }
+
+ return -1;
+ },
+
+ /* Normalizes a number between given bounds or sets to a defaultValue
+ * if it is undefined
+ *************************************************************************/
+ _normalizeNumber: function (number, min, max, defaultValue) {
+ if (number == undefined || number == null) {
+ return defaultValue;
+ }
+
+ if (number < min) {
+ return min;
+ }
+
+ if (number > max) {
+ return max;
+ }
+
+ return number;
+ },
+
+ /* Formats a string just like string.format in c#.
+ * Example:
+ * _formatString('Hello {0}','Halil') = 'Hello Halil'
+ *************************************************************************/
+ _formatString: function () {
+ if (arguments.length == 0) {
+ return null;
+ }
+
+ var str = arguments[0];
+ for (var i = 1; i < arguments.length; i++) {
+ var placeHolder = '{' + (i - 1) + '}';
+ str = str.replace(placeHolder, arguments[i]);
+ }
+
+ return str;
+ },
+
+ //Logging methods ////////////////////////////////////////////////////////
+
+ _logDebug: function (text) {
+ if (!window.console) {
+ return;
+ }
+
+ console.log('jTable DEBUG: ' + text);
+ },
+
+ _logInfo: function (text) {
+ if (!window.console) {
+ return;
+ }
+
+ console.log('jTable INFO: ' + text);
+ },
+
+ _logWarn: function (text) {
+ if (!window.console) {
+ return;
+ }
+
+ console.log('jTable WARNING: ' + text);
+ },
+
+ _logError: function (text) {
+ if (!window.console) {
+ return;
+ }
+
+ console.log('jTable ERROR: ' + text);
+ }
+
+ });
+
+})(jQuery);