/* * jQuery Dynatable plugin 0.3.1 * * Copyright (c) 2014 Steve Schwartz (JangoSteve) * * Dual licensed under the AGPL and Proprietary licenses: * http://www.dynatable.com/license/ * * Date: Tue Jan 02 2014 */ // (function($) { var defaults, mergeSettings, dt, Model, modelPrototypes = { dom: Dom, domColumns: DomColumns, records: Records, recordsCount: RecordsCount, processingIndicator: ProcessingIndicator, state: State, sorts: Sorts, sortsHeaders: SortsHeaders, queries: Queries, inputsSearch: InputsSearch, paginationPage: PaginationPage, paginationPerPage: PaginationPerPage, paginationLinks: PaginationLinks }, utility, build, processAll, initModel, defaultRowWriter, defaultCellWriter, defaultAttributeWriter, defaultAttributeReader; //----------------------------------------------------------------- // Cached plugin global defaults //----------------------------------------------------------------- defaults = { features: { paginate: true, sort: true, pushState: true, search: true, recordCount: true, perPageSelect: true }, table: { defaultColumnIdStyle: 'camelCase', columns: null, headRowSelector: 'thead tr', // or e.g. tr:first-child bodyRowSelector: 'tbody tr', headRowClass: null }, inputs: { queries: null, sorts: null, multisort: ['ctrlKey', 'shiftKey', 'metaKey'], page: null, queryEvent: 'blur change', recordCountTarget: null, recordCountPlacement: 'after', paginationLinkTarget: null, paginationLinkPlacement: 'after', paginationClass: 'dynatable-pagination-links', paginationLinkClass: 'dynatable-page-link', paginationPrevClass: 'dynatable-page-prev', paginationNextClass: 'dynatable-page-next', paginationActiveClass: 'dynatable-active-page', paginationDisabledClass: 'dynatable-disabled-page', paginationPrev: 'Previous', paginationNext: 'Next', paginationGap: [1,2,2,1], searchTarget: null, searchPlacement: 'before', perPageTarget: null, perPagePlacement: 'before', perPageText: 'Show: ', recordCountText: 'Showing ', processingText: 'Processing...' }, dataset: { ajax: false, ajaxUrl: null, ajaxCache: null, ajaxOnLoad: false, ajaxMethod: 'GET', ajaxDataType: 'json', totalRecordCount: null, queries: {}, queryRecordCount: null, page: null, perPageDefault: 10, perPageOptions: [10,20,50,100], sorts: {}, sortsKeys: null, sortTypes: {}, records: null }, writers: { _rowWriter: defaultRowWriter, _cellWriter: defaultCellWriter, _attributeWriter: defaultAttributeWriter }, readers: { _rowReader: null, _attributeReader: defaultAttributeReader }, params: { dynatable: 'dynatable', queries: 'queries', sorts: 'sorts', page: 'page', perPage: 'perPage', offset: 'offset', records: 'records', record: null, queryRecordCount: 'queryRecordCount', totalRecordCount: 'totalRecordCount' } }; //----------------------------------------------------------------- // Each dynatable instance inherits from this, // set properties specific to instance //----------------------------------------------------------------- dt = { init: function(element, options) { this.settings = mergeSettings(options); this.element = element; this.$element = $(element); // All the setup that doesn't require element or options build.call(this); return this; }, process: function(skipPushState) { processAll.call(this, skipPushState); } }; //----------------------------------------------------------------- // Cached plugin global functions //----------------------------------------------------------------- mergeSettings = function(options) { var newOptions = $.extend(true, {}, defaults, options); // TODO: figure out a better way to do this. // Doing `extend(true)` causes any elements that are arrays // to merge the default and options arrays instead of overriding the defaults. if (options) { if (options.inputs) { if (options.inputs.multisort) { newOptions.inputs.multisort = options.inputs.multisort; } if (options.inputs.paginationGap) { newOptions.inputs.paginationGap = options.inputs.paginationGap; } } if (options.dataset && options.dataset.perPageOptions) { newOptions.dataset.perPageOptions = options.dataset.perPageOptions; } } return newOptions; }; build = function() { this.$element.trigger('dynatable:preinit', this); for (model in modelPrototypes) { if (modelPrototypes.hasOwnProperty(model)) { var modelInstance = this[model] = new modelPrototypes[model](this, this.settings); if (modelInstance.initOnLoad()) { modelInstance.init(); } } } this.$element.trigger('dynatable:init', this); if (!this.settings.dataset.ajax || (this.settings.dataset.ajax && this.settings.dataset.ajaxOnLoad) || this.settings.features.paginate) { this.process(); } }; processAll = function(skipPushState) { var data = {}; this.$element.trigger('dynatable:beforeProcess', data); if (!$.isEmptyObject(this.settings.dataset.queries)) { data[this.settings.params.queries] = this.settings.dataset.queries; } // TODO: Wrap this in a try/rescue block to hide the processing indicator and indicate something went wrong if error this.processingIndicator.show(); if (this.settings.features.sort && !$.isEmptyObject(this.settings.dataset.sorts)) { data[this.settings.params.sorts] = this.settings.dataset.sorts; } if (this.settings.features.paginate && this.settings.dataset.page) { var page = this.settings.dataset.page, perPage = this.settings.dataset.perPage; data[this.settings.params.page] = page; data[this.settings.params.perPage] = perPage; data[this.settings.params.offset] = (page - 1) * perPage; } if (this.settings.dataset.ajaxData) { $.extend(data, this.settings.dataset.ajaxData); } // If ajax, sends query to ajaxUrl with queries and sorts serialized and appended in ajax data // otherwise, executes queries and sorts on in-page data if (this.settings.dataset.ajax) { var _this = this; var options = { type: _this.settings.dataset.ajaxMethod, dataType: _this.settings.dataset.ajaxDataType, data: data, error: function(xhr, error) { }, success: function(response) { _this.$element.trigger('dynatable:ajax:success', response); // Merge ajax results and meta-data into dynatables cached data _this.records.updateFromJson(response); // update table with new records _this.dom.update(); if (!skipPushState && _this.state.initOnLoad()) { _this.state.push(data); } }, complete: function() { _this.processingIndicator.hide(); } }; // Do not pass url to `ajax` options if blank if (this.settings.dataset.ajaxUrl) { options.url = this.settings.dataset.ajaxUrl; // If ajaxUrl is blank, then we're using the current page URL, // we need to strip out any query, sort, or page data controlled by dynatable // that may have been in URL when page loaded, so that it doesn't conflict with // what's passed in with the data ajax parameter } else { options.url = utility.refreshQueryString(window.location.href, {}, this.settings); } if (this.settings.dataset.ajaxCache !== null) { options.cache = this.settings.dataset.ajaxCache; } $.ajax(options); } else { this.records.resetOriginal(); this.queries.run(); if (this.settings.features.sort) { this.records.sort(); } if (this.settings.features.paginate) { this.records.paginate(); } this.dom.update(); this.processingIndicator.hide(); if (!skipPushState && this.state.initOnLoad()) { this.state.push(data); } } this.$element.trigger('dynatable:afterProcess', data); }; function defaultRowWriter(rowIndex, record, columns, cellWriter) { var tr = ''; // grab the record's attribute for each column for (var i = 0, len = columns.length; i < len; i++) { tr += cellWriter(columns[i], record); } return '' + tr + ''; }; function defaultCellWriter(column, record) { var html = column.attributeWriter(record), td = '