http://git-wip-us.apache.org/repos/asf/logging-log4j-audit/blob/7ccfa3aa/log4j-catalog/log4j-catalog-editor/src/main/resources/static/js/jtable.2.4.0/jquery.jtable.js ---------------------------------------------------------------------- diff --git a/log4j-catalog/log4j-catalog-editor/src/main/resources/static/js/jtable.2.4.0/jquery.jtable.js b/log4j-catalog/log4j-catalog-editor/src/main/resources/static/js/jtable.2.4.0/jquery.jtable.js new file mode 100644 index 0000000..869af24 --- /dev/null +++ b/log4j-catalog/log4j-catalog-editor/src/main/resources/static/js/jtable.2.4.0/jquery.jtable.js @@ -0,0 +1,5021 @@ +/* + +jTable 2.4.0 +http://www.jtable.org + +--------------------------------------------------------------------------- + +Copyright (C) 2011-2014 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. + +*/ + +/************************************************************************ +* 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 = ''; + } + + //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)); + + +/************************************************************************ +* 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 || isNaN(number)) { + 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; + }, + + /* Checks if given object is a jQuery Deferred object. + */ + _isDeferredObject: function (obj) { + return obj.then && obj.done && obj.fail; + }, + + //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); + } + + }); + + /* Fix for array.indexOf method in IE7. + * This code is taken from http://www.tutorialspoint.com/javascript/array_indexof.htm */ + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function (elt) { + var len = this.length; + var from = Number(arguments[1]) || 0; + from = (from < 0) + ? Math.ceil(from) + : Math.floor(from); + if (from < 0) + from += len; + for (; from < len; from++) { + if (from in this && + this[from] === elt) + return from; + } + return -1; + }; + } + +})(jQuery); + + +/************************************************************************ +* 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].inputTitle || this.options.fields[fieldName].title); + }, + + /* Creates an input element according to field type. + *************************************************************************/ + _createInputForRecordField: function (funcParams) { + var fieldName = funcParams.fieldName, + value = funcParams.value, + record = funcParams.record, + formType = funcParams.formType, + form = funcParams.form; + + //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, + formType: formType, + form: form + })); + + //Add id attribute if does not exists + if (!$input.attr('id')) { + $input.attr('id', 'Edit-' + fieldName); + } + + //Wrap input element with div + return $('<div />') + .addClass('jtable-input jtable-custom-input') + .append($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, record, formType); + } else { + return this._createDropDownListForField(field, fieldName, value, record, formType, form); + } + } 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 = ""; + } + + return $('<input type="hidden" name="' + fieldName + '" id="Edit-' + fieldName + '"></input>') + .val(value); + }, + + /* Creates a date input for a field. + *************************************************************************/ + _createDateInputForField: function (field, fieldName, value) { + var $input = $('<input class="' + field.inputClass + '" id="Edit-' + fieldName + '" type="text" name="' + fieldName + '"></input>'); + if(value != undefined) { + $input.val(value); + } + + 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 + '"></textarea>'); + if (value != undefined) { + $textArea.val(value); + } + + 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" name="' + fieldName + '"></input>'); + if (value != undefined) { + $input.val(value); + } + + 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" name="' + fieldName + '"></input>'); + if (value != undefined) { + $input.val(value); + } + + 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 + '" />') + .appendTo($containerDiv); + if (value != undefined) { + $checkBox.val(value); + } + + //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, record, source, form) { + + //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._getOptionsForField(fieldName, { + record: record, + source: source, + form: form, + dependedValues: this._createDependedValuesUsingForm(form, field.dependsOn) + }); + + this._fillDropDownListWithOptions($select, options, value); + + return $containerDiv; + }, + + /* Fills a dropdown list with given options. + *************************************************************************/ + _fillDropDownListWithOptions: function ($select, options, value) { + $select.empty(); + for (var i = 0; i < options.length; i++) { + $('<option' + (options[i].Value == value ? ' selected="selected"' : '') + '>' + options[i].DisplayText + '</option>') + .val(options[i].Value) + .appendTo($select); + } + }, + + /* Creates depended values object from given form. + *************************************************************************/ + _createDependedValuesUsingForm: function ($form, dependsOn) { + if (!dependsOn) { + return {}; + } + + var dependedValues = {}; + + for (var i = 0; i < dependsOn.length; i++) { + var dependedField = dependsOn[i]; + + var $dependsOn = $form.find('select[name=' + dependedField + ']'); + if ($dependsOn.length <= 0) { + continue; + } + + dependedValues[dependedField] = $dependsOn.val(); + } + + + return dependedValues; + }, + + /* Creates a radio button list for a field. + *************************************************************************/ + _createRadioButtonListForField: function (field, fieldName, value, record, source) { + var $containerDiv = $('<div />') + .addClass('jtable-input jtable-radiobuttonlist-input'); + + var options = this._getOptionsForField(fieldName, { + record: record, + source: source + }); + + $.each(options, function(i, option) { + var $radioButtonDiv = $('<div class=""></div>') + .addClass('jtable-radio-input') + .appendTo($containerDiv); + + var $radioButton = $('<input type="radio" id="Edit-' + fieldName + '-' + i + '" class="' + field.inputClass + '" name="' + fieldName + '"' + ((option.Value == (value + '')) ? ' checked="true"' : '') + ' />') + .val(option.Value) + .appendTo($radioButtonDiv); + + var $textSpan = $('<span></span>') + .html(option.DisplayText) + .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; + }, + + /* Searches a form for dependend dropdowns and makes them cascaded. + */ + _makeCascadeDropDowns: function ($form, record, source) { + var self = this; + + $form.find('select') //for each combobox + .each(function () { + var $thisDropdown = $(this); + + //get field name + var fieldName = $thisDropdown.attr('name'); + if (!fieldName) { + return; + } + + var field = self.options.fields[fieldName]; + + //check if this combobox depends on others + if (!field.dependsOn) { + return; + } + + //for each dependency + $.each(field.dependsOn, function (index, dependsOnField) { + //find the depended combobox + var $dependsOnDropdown = $form.find('select[name=' + dependsOnField + ']'); + //when depended combobox changes + $dependsOnDropdown.change(function () { + + //Refresh options + var funcParams = { + record: record, + source: source, + form: $form, + dependedValues: {} + }; + funcParams.dependedValues = self._createDependedValuesUsingForm($form, field.dependsOn); + var options = self._getOptionsForField(fieldName, funcParams); + + //Fill combobox with new options + self._fillDropDownListWithOptions($thisDropdown, options, undefined); + + //Thigger change event to refresh multi cascade dropdowns. + $thisDropdown.change(); + }); + }); + }); + }, + + /* Updates values of a record from given form + *************************************************************************/ + _updateRecordValuesFromForm: 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 + '"]'); + if ($inputElement.length <= 0) { + continue; + } + + //Update field in record according to it's type + if (field.type == 'date') { + var dateVal = $inputElement.val(); + if (dateVal) { + var displayFormat = field.displayFormat || this.options.defaultDateFormat; + try { + var date = $.datepicker.parseDate(displayFormat, dateVal); + record[fieldName] = '/Date(' + date.getTime() + ')/'; + } catch (e) { + //TODO: Handle incorrect/different date formats + this._logWarn('Date format is incorrect for field ' + fieldName + ': ' + dateVal); + record[fieldName] = undefined; + } + } else { + this._logDebug('Date is empty for ' + fieldName); + record[fieldName] = undefined; //TODO: undefined, null or empty string? + } + } else if (field.options && field.type == 'radiobutton') { + var $checkedElement = $inputElement.filter(':checked'); + if ($checkedElement.length) { + record[fieldName] = $checkedElement.val(); + } else { + record[fieldName] = undefined; + } + } else { + record[fieldName] = $inputElement.val(); + } + } + }, + + /* 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); + + +/************************************************************************ +* 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); + + if (!this.options.actions.createAction) { + return; + } + + this._createAddRecordDialogDiv(); + }, + + /* Creates and prepares add new record dialog div + *************************************************************************/ + _createAddRecordDialogDiv: function () { + var self = this; + + //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 () { + self._onSaveClickedOnCreateForm(); + } + }], + close: function () { + var $addRecordForm = self._$addRecordDiv.find('form').first(); + var $saveButton = self._$addRecordDiv.parent().find('#AddRecordDialogSaveButton'); + self._trigger("formClosed", null, { form: $addRecordForm, formType: 'create' }); + self._setEnabledOfDialogButton($saveButton, true, self.options.messages.save); + $addRecordForm.remove(); + } + }); + + if (self.options.addRecordButton) { + //If user supplied a button, bind the click event to show dialog form + self.options.addRecordButton.click(function (e) { + e.preventDefault(); + self._showAddRecordForm(); + }); + } else { + //If user did not supplied a button, create a 'add record button' toolbar item. + self._addToolBarItem({ + icon: true, + cssClass: 'jtable-toolbar-item-add-record', + text: self.options.messages.addNewRecord, + click: function () { + self._showAddRecordForm(); + } + }); + } + }, + + _onSaveClickedOnCreateForm: function () { + var self = this; + + var $saveButton = self._$addRecordDiv.parent().find('#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); + } + }, + + /************************************************************************ + * 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, + 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; + } + + var completeAddRecord = function (data) { + if (data.Result != 'OK') { + self._showError(data.Message); + options.error(data); + return; + } + + if (!data.Record) { + self._logError('Server must return the created Record object.'); + options.error(data); + return; + } + + self._onRecordAdded(data); + self._addRow( + self._createRowFromRecord(data.Record), { + isNewRow: true, + animationsEnabled: options.animationsEnabled + }); + + options.success(data); + }; + + //createAction may be a function, check if it is + if (!options.url && $.isFunction(self.options.actions.createAction)) { + + //Execute the function + var funcResult = self.options.actions.createAction($.param(options.record)); + + //Check if result is a jQuery Deferred object + if (self._isDeferredObject(funcResult)) { + //Wait promise + funcResult.done(function (data) { + completeAddRecord(data); + }).fail(function () { + self._showError(self.options.messages.serverCommunicationError); + options.error(); + }); + } else { //assume it returned the creation result + completeAddRecord(funcResult); + } + + } else { //Assume it's a URL string + + //Make an Ajax call to create record + self._submitFormUsingAjax( + options.url || self.options.actions.createAction, + $.param(options.record), + function (data) { + completeAddRecord(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 rec
<TRUNCATED>