nsedit/jtable/dev/jquery.jtable.core.js

1290 lines
49 KiB
JavaScript

/************************************************************************
* CORE jTable module *
*************************************************************************/
(function ($) {
var unloadingPage;
$(window).on('beforeunload', function () {
unloadingPage = true;
});
$(window).on('unload', function () {
unloadingPage = false;
});
$.widget("hik.jtable", {
/************************************************************************
* DEFAULT OPTIONS / EVENTS *
*************************************************************************/
options: {
//Options
actions: {},
fields: {},
animationsEnabled: true,
defaultDateFormat: 'yy-mm-dd',
dialogShowEffect: 'fade',
dialogHideEffect: 'fade',
showCloseButton: false,
loadingAnimationDelay: 500,
saveUserPreferences: true,
jqueryuiTheme: false,
unAuthorizedRequestRedirectUrl: null,
ajaxSettings: {
type: 'POST',
dataType: 'json'
},
toolbar: {
hoverAnimation: true,
hoverAnimationDuration: 60,
hoverAnimationEasing: undefined,
items: []
},
//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)
_$titleDiv: null, //Reference to the title div (jQuery object)
_$toolbarDiv: null, //Reference to the toolbar div (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)
_$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._createToolBar();
this._createTable();
this._createBusyPanel();
this._createErrorDialogDiv();
this._addNoDataRow();
this._cookieKeyPrefix = this._generateCookieKeyPrefix();
},
/* 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) {
if (props.listClass == undefined) {
props.listClass = '';
}
if (props.inputClass == undefined) {
props.inputClass = '';
}
if (props.placeholder == undefined) {
props.placeholder = '';
}
//Convert dependsOn to array if it's a comma seperated lists
if (props.dependsOn && $.type(props.dependsOn) === 'string') {
var dependsOnArray = props.dependsOn.split(',');
props.dependsOn = [];
for (var i = 0; i < dependsOnArray.length; i++) {
props.dependsOn.push($.trim(dependsOnArray[i]));
}
}
},
/* 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);
this._jqueryuiThemeAddClass(this._$mainContainer, 'ui-widget');
},
/* 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);
self._jqueryuiThemeAddClass($titleDiv, 'ui-widget-header');
$('<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();
});
}
self._$titleDiv = $titleDiv;
},
/* Creates the table.
*************************************************************************/
_createTable: function () {
this._$table = $('<table></table>')
.addClass('jtable')
.appendTo(this._$mainContainer);
if (this.options.tableId) {
this._$table.attr('id', this.options.tableId);
}
this._jqueryuiThemeAddClass(this._$table, 'ui-widget-content');
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')
.addClass(field.listClass)
.css('width', field.width)
.data('fieldName', fieldName)
.append($headerContainerDiv);
this._jqueryuiThemeAddClass($th, 'ui-state-default');
return $th;
},
/* Creates an empty header cell that can be used as command column headers.
*************************************************************************/
_createEmptyCommandHeader: function () {
var $th = $('<th></th>')
.addClass('jtable-command-column-header')
.css('width', '1%');
this._jqueryuiThemeAddClass($th, 'ui-state-default');
return $th;
},
/* Creates tbody tag and adds to the table.
*************************************************************************/
_createTableBody: function () {
this._$tableBody = $('<tbody></tbody>').appendTo(this._$table);
},
/* 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._jqueryuiThemeAddClass(this._$busyMessageDiv, 'ui-widget-header');
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 *
*************************************************************************/
/* Used to change options dynamically after initialization.
*************************************************************************/
_setOption: function (key, value) {
},
/* LOADING RECORDS *****************************************************/
/* Performs an AJAX call to reload data of the table.
*************************************************************************/
_reloadTable: function (completeCallback) {
var self = this;
var completeReload = 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();
}
};
self._showBusy(self.options.messages.loadingMessage, self.options.loadingAnimationDelay); //Disable table since it's busy
self._onLoadingRecords();
//listAction may be a function, check if it is
if ($.isFunction(self.options.actions.listAction)) {
//Execute the function
var funcResult = self.options.actions.listAction(self._lastPostData, self._createJtParamsForLoading());
//Check if result is a jQuery Deferred object
if (self._isDeferredObject(funcResult)) {
funcResult.done(function(data) {
completeReload(data);
}).fail(function() {
self._showError(self.options.messages.serverCommunicationError);
}).always(function() {
self._hideBusy();
});
} else { //assume it's the data we're loading
completeReload(funcResult);
}
} else { //assume listAction as URL string.
//Generate URL (with query string parameters) to load records
var loadUrl = self._createRecordLoadUrl();
//Load data from server using AJAX
self._ajax({
url: loadUrl,
data: self._lastPostData,
success: function (data) {
completeReload(data);
},
error: function () {
self._hideBusy();
self._showError(self.options.messages.serverCommunicationError);
}
});
}
},
/* Creates URL to load records.
*************************************************************************/
_createRecordLoadUrl: function () {
return this.options.actions.listAction;
},
_createJtParamsForLoading: function() {
return {
//Empty as default, paging, sorting or other extensions can override this method to add additional params to load request
};
},
/* 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) {
var className = 'jtable-row-created';
if (this.options.jqueryuiTheme) {
className = className + ' ui-state-highlight';
}
$tableRow.addClass(className, 'slow', '', function () {
$tableRow.removeClass(className, 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.addClass('jtable-row-removed').remove();
//remove from _$tableRows array
$rows.each(function () {
var index = self._findRowIndex($(this));
if (index >= 0) {
self._$tableRows.splice(index, 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 () {
if (this._$tableBody.find('>tr.jtable-no-data-row').length > 0) {
return;
}
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) { //combobox or radio button list since there are options.
var options = this._getOptionsForField(fieldName, {
record: record,
value: fieldValue,
source: 'list',
dependedValues: this._createDependedValuesUsingRecord(record, field.dependsOn)
});
return this._findOptionByValue(options, fieldValue).DisplayText;
} else { //other types
return fieldValue;
}
},
/* Creates and returns an object that's properties are depended values of a record.
*************************************************************************/
_createDependedValuesUsingRecord: function (record, dependsOn) {
if (!dependsOn) {
return {};
}
var dependedValues = {};
for (var i = 0; i < dependsOn.length; i++) {
dependedValues[dependsOn[i]] = record[dependsOn[i]];
}
return dependedValues;
},
/* Finds an option object by given value.
*************************************************************************/
_findOptionByValue: function (options, value) {
for (var i = 0; i < options.length; i++) {
if (options[i].Value == value) {
return options[i];
}
}
return {}; //no option found
},
/* 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);
},
/* Gets options for a field according to user preferences.
*************************************************************************/
_getOptionsForField: function (fieldName, funcParams) {
var field = this.options.fields[fieldName];
var optionsSource = field.options;
if ($.isFunction(optionsSource)) {
//prepare parameter to the function
funcParams = $.extend(true, {
_cacheCleared: false,
dependedValues: {},
clearCache: function () {
this._cacheCleared = true;
}
}, funcParams);
//call function and get actual options source
optionsSource = optionsSource(funcParams);
}
var options;
//Build options according to it's source type
if (typeof optionsSource == 'string') { //It is an Url to download options
var cacheKey = 'options_' + fieldName + '_' + optionsSource; //create a unique cache key
if (funcParams._cacheCleared || (!this._cache[cacheKey])) {
//if user calls clearCache() or options are not found in the cache, download options
this._cache[cacheKey] = this._buildOptionsFromArray(this._downloadOptions(fieldName, optionsSource));
this._sortFieldOptions(this._cache[cacheKey], field.optionsSorting);
} else {
//found on cache..
//if this method (_getOptionsForField) is called to get option for a specific value (on funcParams.source == 'list')
//and this value is not in cached options, we need to re-download options to get the unfound (probably new) option.
if (funcParams.value != undefined) {
var optionForValue = this._findOptionByValue(this._cache[cacheKey], funcParams.value);
if (optionForValue.DisplayText == undefined) { //this value is not in cached options...
this._cache[cacheKey] = this._buildOptionsFromArray(this._downloadOptions(fieldName, optionsSource));
this._sortFieldOptions(this._cache[cacheKey], field.optionsSorting);
}
}
}
options = this._cache[cacheKey];
} else if (jQuery.isArray(optionsSource)) { //It is an array of options
options = this._buildOptionsFromArray(optionsSource);
this._sortFieldOptions(options, field.optionsSorting);
} else { //It is an object that it's properties are options
options = this._buildOptionsArrayFromObject(optionsSource);
this._sortFieldOptions(options, field.optionsSorting);
}
return options;
},
/* 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;
}
options = data.Options;
},
error: function () {
var errMessage = self._formatString(self.options.messages.cannotLoadOptionsFor, fieldName);
self._showError(errMessage);
}
});
return options;
},
/* Sorts given options according to sorting parameter.
* sorting can be: 'value', 'value-desc', 'text' or 'text-desc'.
*************************************************************************/
_sortFieldOptions: function (options, sorting) {
if ((!options) || (!options.length) || (!sorting)) {
return;
}
//Determine using value of text
var dataSelector;
if (sorting.indexOf('value') == 0) {
dataSelector = function (option) {
return option.Value;
};
} else { //assume as text
dataSelector = function (option) {
return option.DisplayText;
};
}
var compareFunc;
if ($.type(dataSelector(options[0])) == 'string') {
compareFunc = function (option1, option2) {
return dataSelector(option1).localeCompare(dataSelector(option2));
};
} else { //asuume as numeric
compareFunc = function (option1, option2) {
return dataSelector(option1) - dataSelector(option2);
};
}
if (sorting.indexOf('desc') > 0) {
options.sort(function (a, b) {
return compareFunc(b, a);
});
} else { //assume as asc
options.sort(function (a, b) {
return compareFunc(a, b);
});
}
},
/* Creates an array of options from given object.
*************************************************************************/
_buildOptionsArrayFromObject: function (options) {
var list = [];
$.each(options, function (propName, propValue) {
list.push({
Value: propName,
DisplayText: propValue
});
});
return list;
},
/* Creates array of options from giving options array.
*************************************************************************/
_buildOptionsFromArray: function (optionsArray) {
var list = [];
for (var i = 0; i < optionsArray.length; i++) {
if ($.isPlainObject(optionsArray[i])) {
list.push(optionsArray[i]);
} else { //assumed as primitive type (int, string...)
list.push({
Value: optionsArray[i],
DisplayText: optionsArray[i]
});
}
}
return list;
},
/* 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), 10)
);
} else if (dateString.length == 10) { //Format: 2011-01-01
return new Date(
parseInt(dateString.substr(0, 4), 10),
parseInt(dateString.substr(5, 2), 10) - 1,
parseInt(dateString.substr(8, 2), 10)
);
} else if (dateString.length == 19) { //Format: 2011-01-01 20:32:42
return new Date(
parseInt(dateString.substr(0, 4), 10),
parseInt(dateString.substr(5, 2), 10) - 1,
parseInt(dateString.substr(8, 2), 10),
parseInt(dateString.substr(11, 2), 10),
parseInt(dateString.substr(14, 2), 10),
parseInt(dateString.substr(17, 2), 10)
);
} else {
this._logWarn('Given date is not properly formatted: ' + dateString);
return 'format error!';
}
},
/* TOOL BAR *************************************************************/
/* Creates the toolbar.
*************************************************************************/
_createToolBar: function () {
this._$toolbarDiv = $('<div />')
.addClass('jtable-toolbar')
.appendTo(this._$titleDiv);
for (var i = 0; i < this.options.toolbar.items.length; i++) {
this._addToolBarItem(this.options.toolbar.items[i]);
}
},
/* Adds a new item to the toolbar.
*************************************************************************/
_addToolBarItem: function (item) {
//Check if item is valid
if ((item == undefined) || (item.text == undefined && item.icon == undefined)) {
this._logWarn('Can not add tool bar item since it is not valid!');
this._logWarn(item);
return null;
}
var $toolBarItem = $('<span></span>')
.addClass('jtable-toolbar-item')
.appendTo(this._$toolbarDiv);
this._jqueryuiThemeAddClass($toolBarItem, 'ui-widget ui-state-default ui-corner-all', 'ui-state-hover');
//cssClass property
if (item.cssClass) {
$toolBarItem
.addClass(item.cssClass);
}
//tooltip property
if (item.tooltip) {
$toolBarItem
.attr('title', item.tooltip);
}
//icon property
if (item.icon) {
var $icon = $('<span class="jtable-toolbar-item-icon"></span>').appendTo($toolBarItem);
if (item.icon === true) {
//do nothing
} else if ($.type(item.icon === 'string')) {
$icon.css('background', 'url("' + item.icon + '")');
}
}
//text property
if (item.text) {
$('<span class=""></span>')
.html(item.text)
.addClass('jtable-toolbar-item-text').appendTo($toolBarItem);
}
//click event
if (item.click) {
$toolBarItem.click(function () {
item.click();
});
}
//set hover animation parameters
var hoverAnimationDuration = undefined;
var hoverAnimationEasing = undefined;
if (this.options.toolbar.hoverAnimation) {
hoverAnimationDuration = this.options.toolbar.hoverAnimationDuration;
hoverAnimationEasing = this.options.toolbar.hoverAnimationEasing;
}
//change class on hover
$toolBarItem.hover(function () {
$toolBarItem.addClass('jtable-toolbar-item-hover', hoverAnimationDuration, hoverAnimationEasing);
}, function () {
$toolBarItem.removeClass('jtable-toolbar-item-hover', hoverAnimationDuration, hoverAnimationEasing);
});
return $toolBarItem;
},
/* 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,
_showBusy: function (message, delay) {
var self = this; //
//Show a transparent overlay to prevent clicking to the table
self._$busyDiv
.width(self._$mainContainer.width())
.height(self._$mainContainer.height())
.addClass('jtable-busy-panel-background-invisible')
.show();
var makeVisible = function () {
self._$busyDiv.removeClass('jtable-busy-panel-background-invisible');
self._$busyMessageDiv.html(message).show();
};
if (delay) {
if (self._setBusyTimer) {
return;
}
self._setBusyTimer = setTimeout(makeVisible, delay);
} else {
makeVisible();
}
},
/* Hides busy indicator and unblocks table UI.
*************************************************************************/
_hideBusy: function () {
clearTimeout(this._setBusyTimer);
this._setBusyTimer = null;
this._$busyDiv.hide();
this._$busyMessageDiv.html('').hide();
},
/* Returns true if jTable is busy.
*************************************************************************/
_isBusy: function () {
return this._$busyMessageDiv.is(':visible');
},
/* Adds jQueryUI class to an item.
*************************************************************************/
_jqueryuiThemeAddClass: function ($elm, className, hoverClassName) {
if (!this.options.jqueryuiTheme) {
return;
}
$elm.addClass(className);
if (hoverClassName) {
$elm.hover(function () {
$elm.addClass(hoverClassName);
}, function () {
$elm.removeClass(hoverClassName);
});
}
},
/* 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
});
},
_unAuthorizedRequestHandler: function() {
if (this.options.unAuthorizedRequestRedirectUrl) {
location.href = this.options.unAuthorizedRequestRedirectUrl;
} else {
location.reload(true);
}
},
/* This method is used to perform AJAX calls in jTable instead of direct
* usage of jQuery.ajax method.
*************************************************************************/
_ajax: function (options) {
var self = this;
//Handlers for HTTP status codes
var opts = {
statusCode: {
401: function () { //Unauthorized
self._unAuthorizedRequestHandler();
}
}
};
opts = $.extend(opts, this.options.ajaxSettings, options);
//Override success
opts.success = function (data) {
//Checking for Authorization error
if (data && data.UnAuthorizedRequest == true) {
self._unAuthorizedRequestHandler();
}
if (options.success) {
options.success(data);
}
};
//Override error
opts.error = function (jqXHR, textStatus, errorThrown) {
if (unloadingPage) {
jqXHR.abort();
return;
}
if (options.error) {
options.error(arguments);
}
};
//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];
},
/************************************************************************
* 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);
},
/************************************************************************
* 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));