696 lines
26 KiB
JavaScript
696 lines
26 KiB
JavaScript
|
/**
|
||
|
* Sort and paginate tabular data
|
||
|
* @module Ink.UI.Table_1
|
||
|
* @version 1
|
||
|
*/
|
||
|
Ink.createModule('Ink.UI.Table', '1', ['Ink.Util.Url_1','Ink.UI.Pagination_1','Ink.Net.Ajax_1','Ink.UI.Common_1','Ink.Dom.Event_1','Ink.Dom.Css_1','Ink.Dom.Element_1','Ink.Dom.Selector_1','Ink.Util.Array_1','Ink.Util.String_1', 'Ink.Util.Json_1'], function(InkUrl,Pagination, Ajax, Common, Event, Css, Element, Selector, InkArray, InkString, Json) {
|
||
|
'use strict';
|
||
|
|
||
|
var rNumber = /\d/g;
|
||
|
// Turn into a number, if we can. For sorting data which could be numeric or not.
|
||
|
function maybeTurnIntoNumber(value) {
|
||
|
if( !isNaN(value) && rNumber.test(value) ){
|
||
|
return parseInt(value, 10);
|
||
|
} else if( !isNaN(value) ){
|
||
|
return parseFloat(value);
|
||
|
}
|
||
|
return value;
|
||
|
}
|
||
|
function cmp (a, b) {
|
||
|
if( a === b ){
|
||
|
return 0;
|
||
|
}
|
||
|
return ( ( a > b ) ? 1 : -1 );
|
||
|
}
|
||
|
// cmp function for comparing data which might be a number.
|
||
|
function numberishEnabledCmp (a, b) {
|
||
|
var aValue = maybeTurnIntoNumber(Element.textContent(a));
|
||
|
var bValue = maybeTurnIntoNumber(Element.textContent(b));
|
||
|
|
||
|
return cmp(aValue, bValue);
|
||
|
}
|
||
|
// Object.keys polyfill
|
||
|
function keys(obj) {
|
||
|
if (typeof Object.keys !== 'undefined') {
|
||
|
return Object.keys(obj);
|
||
|
}
|
||
|
var ret = [];
|
||
|
for (var k in obj) if (obj.hasOwnProperty(k)) {
|
||
|
ret.push(k);
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The Table component transforms the native/DOM table element into a sortable, paginated component.
|
||
|
* You can use this component to display data from a JSON endpoint, or from table rows in the DOM. Displaying from the DOM is more practical, but sometimes you don't want to load everything at once (if you have a HUGE table). In those cases, you should configure Ink.UI.Table to get data from JSON endpoint.
|
||
|
* To enable sorting, just set the `data-sortable` attribute of your table headers (they must be in the `thead` of the table) to "true". To enable pagination, you should pass either an `Ink.UI.Pagination` instance or a selector to create the Ink.UI.Pagination element on.
|
||
|
*
|
||
|
* @class Ink.UI.Table
|
||
|
* @constructor
|
||
|
* @version 1
|
||
|
* @param {String|DOMElement} selector
|
||
|
* @param {Object} [options] Options
|
||
|
* @param {Number} [options.pageSize] Number of rows per page. Omit to avoid paginating.
|
||
|
* @param {String} [options.endpoint] Endpoint to get the records via AJAX. Omit if you don't want to do AJAX
|
||
|
* @param {Function} [options.createEndpointUrl] Callback to customise what URL the AJAX endpoint is at. Receives three arguments: base (the "endpoint" option), sort (`{ order: 'asc' or 'desc', field: fieldname }`) and page ({ page: page number, size: items per page })
|
||
|
* @param {Function} [options.getDataFromEndPoint] Callback to allow the user to retrieve the data himself given an URL. Must accept two arguments: `url` and `callback`. This `callback` will take as a single argument a JavaScript object.
|
||
|
* @param {Function} [options.processJSONRows] Retrieve an array of rows from the data which came from AJAX.
|
||
|
* @param {Function} [options.processJSONHeaders] Get an object with all the headers' names as keys, and a { label, sortable } object as value. Example: `{col1: {label: "Column 1"}, col2: {label: "Column 2", sortable: true}`. Takes a single argument, the JSON response.
|
||
|
* @param {Function} [options.processJSONRow] Process a row object before it gets on the table.
|
||
|
* @param {Function} [options.processJSONField] Process the field data before putting it on the table. You can return HTML, a DOM element, or a string here. Arguments you receive: `(column, fieldData, rowIndex)`.
|
||
|
* @param {Function} [options.processJSONField.FIELD_NAME] The same as processJSONField, but for a particular field.
|
||
|
* @param {Function} [options.processJSONTotalRows] A callback where you have a chance to say how many rows are in the dataset (not only on this page) you have on the collection. You get as an argument the JSON response.
|
||
|
* @param {Function} [options.getSortKey] A function taking a `{ columnIndex, columnName, data, element }` object and returning a value which serves as a sort key for the sorting operation. For example, if you want to sort by a `data-sort-key` atribute, set `getSortKey` to: function (cell) { return cell.element.getAttribute('data-sort-key'); }
|
||
|
* @param {Function} [options.getSortKey.FIELD_NAME] Same as `options.getSortKey`, but for a particular field.
|
||
|
* @param {Object} [options.tdClassNames] An object mapping each field to what classes it gets. Example: `{ name: "large-10", isBoss: "hide-small" }`
|
||
|
* @param {Mixed} [options.pagination] Pagination instance, element or selector.
|
||
|
* @param {Object} [options.paginationOptions] Override the options with which we instantiate the Ink.UI.Pagination.
|
||
|
* @param {Boolean} [options.allowResetSorting] Allow sort order to be set to "none" in addition to "ascending" and "descending"
|
||
|
* @param {String|Array} [options.visibleFields] Set of fields which get shown on the table
|
||
|
*
|
||
|
* @sample Ink_UI_Table_1.html
|
||
|
*/
|
||
|
function Table(){
|
||
|
Common.BaseUIComponent.apply(this, arguments);
|
||
|
}
|
||
|
|
||
|
Table._name = 'Table_1';
|
||
|
|
||
|
// Most processJSON* functions can just default to this.
|
||
|
function sameSame(obj) { return obj; }
|
||
|
|
||
|
Table._optionDefinition = {
|
||
|
pageSize: ['Integer', null],
|
||
|
caretUpClass: ['String', 'fa fa-caret-up'],
|
||
|
caretDownClass: ['String', 'fa fa-caret-down'],
|
||
|
endpoint: ['String', null],
|
||
|
createEndpointUrl: ['Function', null /* default func uses above option */],
|
||
|
getDataFromEndPoint: ['Function', null /* by default use plain ajax for JSON */],
|
||
|
processJSONRows: ['Function', sameSame],
|
||
|
processJSONRow: ['Function', sameSame],
|
||
|
processJSONField: ['Function', sameSame],
|
||
|
processJSONHeaders: ['Function', function (dt) { return dt.fields; }],
|
||
|
processJSONTotalRows: ['Function', function (dt) { return dt.length || dt.totalRows; }],
|
||
|
getSortKey: ['Function', null],
|
||
|
pagination: ['Element', null],
|
||
|
allowResetSorting: ['Boolean', false],
|
||
|
visibleFields: ['String', null],
|
||
|
tdClassNames: ['Object', {}],
|
||
|
paginationOptions: ['Object', null]
|
||
|
};
|
||
|
|
||
|
Table.prototype = {
|
||
|
_validate: function () {
|
||
|
if( this._element.nodeName.toLowerCase() !== 'table' ){
|
||
|
throw new Error('[Ink.UI.Table] :: The element is not a table');
|
||
|
}
|
||
|
},
|
||
|
/**
|
||
|
* Init function called by the constructor
|
||
|
*
|
||
|
* @method _init
|
||
|
* @private
|
||
|
*/
|
||
|
_init: function(){
|
||
|
/**
|
||
|
* Checking if it's in markup mode or endpoint mode
|
||
|
*/
|
||
|
this._markupMode = !this._options.endpoint;
|
||
|
|
||
|
if( this._options.visibleFields ){
|
||
|
this._options.visibleFields = this._options.visibleFields.toString().split(/[, ]+/g);
|
||
|
}
|
||
|
|
||
|
this._thead = this._element.tHead || this._element.createTHead();
|
||
|
this._headers = Selector.select('th', this._thead);
|
||
|
|
||
|
/**
|
||
|
* Initializing variables
|
||
|
*/
|
||
|
this._handlers = {
|
||
|
thClick: null
|
||
|
};
|
||
|
this._originalFields = [
|
||
|
// field headers from the DOM
|
||
|
];
|
||
|
this._sortableFields = {
|
||
|
// Identifies which columns are sorted and how.
|
||
|
// columnIndex: 'none'|'asc'|'desc'
|
||
|
};
|
||
|
this._originalData = this._data = [];
|
||
|
this._pagination = null;
|
||
|
this._totalRows = 0;
|
||
|
|
||
|
this._handlers.thClick = Event.observeDelegated(this._element, 'click',
|
||
|
'thead th[data-sortable="true"]',
|
||
|
Ink.bindMethod(this, '_onThClick'));
|
||
|
|
||
|
/**
|
||
|
* If not is in markup mode, we have to do the initial request
|
||
|
* to get the first data and the headers
|
||
|
*/
|
||
|
if( !this._markupMode ) {
|
||
|
/* Endpoint mode */
|
||
|
this._getData( );
|
||
|
} else /* Markup mode */ {
|
||
|
this._resetSortOrder();
|
||
|
this._addHeadersClasses();
|
||
|
|
||
|
/**
|
||
|
* Getting the table's data
|
||
|
*/
|
||
|
this._data = Selector.select('tbody tr', this._element);
|
||
|
this._originalData = this._data.slice(0);
|
||
|
|
||
|
this._totalRows = this._data.length;
|
||
|
|
||
|
/**
|
||
|
* Set pagination if options tell us to
|
||
|
*/
|
||
|
this._setPagination();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Add the classes in this._options.tdClassNames to our table headers.
|
||
|
* @method _addHeadersClasses
|
||
|
* @private
|
||
|
*/
|
||
|
_addHeadersClasses: function () {
|
||
|
var headerLabel;
|
||
|
var classNames;
|
||
|
for (var i = 0, len = this._headers.length; i < len; i++) {
|
||
|
headerLabel = Element.textContent(this._headers[i]);
|
||
|
classNames = this._options.tdClassNames[headerLabel];
|
||
|
// TODO do not find header labels this way. But how?
|
||
|
if (classNames) {
|
||
|
Css.addClassName(this._headers[i], classNames);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Click handler. This will mainly handle the sorting (when you click in the headers)
|
||
|
*
|
||
|
* @method _onThClick
|
||
|
* @param {Event} event Event obj
|
||
|
* @private
|
||
|
*/
|
||
|
_onThClick: function( event ){
|
||
|
var tgtEl = Event.element(event),
|
||
|
paginated = this._options.pageSize !== undefined;
|
||
|
|
||
|
Event.stop(event);
|
||
|
|
||
|
var index = InkArray.keyValue(tgtEl, this._headers, true);
|
||
|
var sortable = index !== false && this._sortableFields[index] !== undefined;
|
||
|
|
||
|
if( !sortable ){
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if( !this._markupMode && paginated ){
|
||
|
this._invertSortOrder(index, false);
|
||
|
} else {
|
||
|
if ( (this._sortableFields[index] === 'desc') && this._options.allowResetSorting ) {
|
||
|
this._setSortOrderOfColumn(index, null);
|
||
|
this._data = this._originalData.slice(0);
|
||
|
} else {
|
||
|
this._invertSortOrder(index, true);
|
||
|
}
|
||
|
|
||
|
var tbody = Selector.select('tbody',this._element)[0];
|
||
|
Common.cleanChildren(tbody);
|
||
|
InkArray.each(this._data, Ink.bindMethod(tbody, 'appendChild'));
|
||
|
|
||
|
if (this._pagination) {
|
||
|
this._pagination.setCurrent(0);
|
||
|
this._paginate(1);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
_invertSortOrder: function (index, sortAndReverse) {
|
||
|
var isAscending = this._sortableFields[index] === 'asc';
|
||
|
|
||
|
for (var i = 0, len = this._headers.length; i < len; i++) {
|
||
|
this._setSortOrderOfColumn(i, null);
|
||
|
}
|
||
|
|
||
|
if (sortAndReverse) {
|
||
|
this._sort(index);
|
||
|
if (isAscending) {
|
||
|
this._data.reverse();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this._setSortOrderOfColumn(index, !isAscending);
|
||
|
},
|
||
|
|
||
|
_setSortOrderOfColumn: function(index, up) {
|
||
|
var header = this._headers[index];
|
||
|
var caretHtml = [''];
|
||
|
var order = 'none';
|
||
|
|
||
|
if (up === true) {
|
||
|
caretHtml = ['<i class="', this._options.caretUpClass, '"></i>'];
|
||
|
order = 'asc';
|
||
|
} else if (up === false) {
|
||
|
caretHtml = ['<i class="', this._options.caretDownClass, '"></i>'];
|
||
|
order = 'desc';
|
||
|
}
|
||
|
|
||
|
this._sortableFields[index] = order;
|
||
|
header.innerHTML = Element.textContent(header) + caretHtml.join('');
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Applies and/or changes the CSS classes in order to show the right columns
|
||
|
*
|
||
|
* @method _paginate
|
||
|
* @param {Number} page Current page
|
||
|
* @private
|
||
|
*/
|
||
|
_paginate: function( page ){
|
||
|
if (!this._pagination) { return; }
|
||
|
|
||
|
var pageSize = this._options.pageSize;
|
||
|
|
||
|
// Hide everything except the items between these indices
|
||
|
var firstIndex = (page - 1) * pageSize;
|
||
|
var lastIndex = firstIndex + pageSize;
|
||
|
|
||
|
InkArray.each(this._data, function(item, index){
|
||
|
if (index >= firstIndex && index < lastIndex) {
|
||
|
Css.removeClassName(item,'hide-all');
|
||
|
} else {
|
||
|
Css.addClassName(item,'hide-all');
|
||
|
}
|
||
|
});
|
||
|
|
||
|
},
|
||
|
|
||
|
/* register fields into this._originalFields, whether they come from JSON or a table.
|
||
|
* @method _registerFieldNames
|
||
|
* @private
|
||
|
* @param [names] The field names in an array
|
||
|
**/
|
||
|
_registerFieldNames: function (names) {
|
||
|
this._originalFields = [];
|
||
|
|
||
|
InkArray.forEach(names, Ink.bind(function (field) {
|
||
|
if( !this._fieldIsVisible(field) ){
|
||
|
return; // The user deems this not to be necessary to see.
|
||
|
}
|
||
|
this._originalFields.push(field);
|
||
|
}, this));
|
||
|
},
|
||
|
|
||
|
_fieldIsVisible: function (field) {
|
||
|
return !this._options.visibleFields ||
|
||
|
(this._options.visibleFields.indexOf(field) !== -1);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Sorts by a specific column.
|
||
|
*
|
||
|
* @method _sort
|
||
|
* @param {Number} index Column number (starting at 0)
|
||
|
* @private
|
||
|
*/
|
||
|
_sort: function( index ){
|
||
|
// TODO this is THE worst way to declare field names. Incompatible with i18n and a lot of other things.
|
||
|
var fieldName = Element.textContent(this._headers[index]);
|
||
|
var keyFunction = this._options.getSortKey;
|
||
|
|
||
|
if (keyFunction) {
|
||
|
keyFunction =
|
||
|
typeof keyFunction[fieldName] === 'function' ?
|
||
|
keyFunction[fieldName] :
|
||
|
typeof keyFunction === 'function' ?
|
||
|
keyFunction :
|
||
|
null;
|
||
|
}
|
||
|
|
||
|
var self = this;
|
||
|
|
||
|
this._data.sort(function (trA, trB) {
|
||
|
var elementA = Ink.ss('td', trA)[index];
|
||
|
var elementB = Ink.ss('td', trB)[index];
|
||
|
if (keyFunction) {
|
||
|
return cmp(userKey(elementA), userKey(elementB));
|
||
|
} else {
|
||
|
return numberishEnabledCmp(elementA, elementB, index);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
function userKey(element) {
|
||
|
return keyFunction.call(self, {
|
||
|
columnIndex: index,
|
||
|
columnName: fieldName,
|
||
|
data: Element.textContent(element),
|
||
|
element: element
|
||
|
});
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Assembles the headers markup
|
||
|
*
|
||
|
* @method _createHeadersFromJson
|
||
|
* @param {Object} headers Key-value object that contains the fields as keys, their configuration (label and sorting ability) as value
|
||
|
* @private
|
||
|
*/
|
||
|
_createHeadersFromJson: function( headers ){
|
||
|
this._registerFieldNames(keys(headers));
|
||
|
|
||
|
if (this._thead.children.length) { return; }
|
||
|
|
||
|
var tr = this._thead.insertRow(0);
|
||
|
var th;
|
||
|
|
||
|
for (var i = 0, len = headers.length; i < len; i++) {
|
||
|
if (this._fieldIsVisible(headers[i])) {
|
||
|
th = Element.create('th');
|
||
|
th = this._createSingleHeaderFromJson(headers[i], th);
|
||
|
tr.appendChild(th);
|
||
|
this._headers.push(th);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
_createSingleHeaderFromJson: function (header, th) {
|
||
|
if (header.sortable) {
|
||
|
th.setAttribute('data-sortable','true');
|
||
|
}
|
||
|
|
||
|
if (header.label){
|
||
|
Element.setTextContent(th, header.label);
|
||
|
}
|
||
|
|
||
|
return th;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Reset the sort order as marked on the table headers to "none"
|
||
|
*
|
||
|
* @method _resetSortOrder
|
||
|
* @private
|
||
|
*/
|
||
|
_resetSortOrder: function(){
|
||
|
/**
|
||
|
* Setting the sortable columns and its event listeners
|
||
|
*/
|
||
|
for (var i = 0, len = this._headers.length; i < len; i++) {
|
||
|
var dataset = Element.data( this._headers[i] );
|
||
|
if (dataset.sortable && dataset.sortable.toString() === 'true') {
|
||
|
this._sortableFields[i] = 'none';
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* This method gets the rows from AJAX and places them as <tr> and <td>
|
||
|
*
|
||
|
* @method _createRowsFromJSON
|
||
|
* @param {Object} rows Array of objects with the data to be showed
|
||
|
* @private
|
||
|
*/
|
||
|
_createRowsFromJSON: function( rows ){
|
||
|
var tbody = Selector.select('tbody',this._element)[0];
|
||
|
|
||
|
if( !tbody ){
|
||
|
tbody = document.createElement('tbody');
|
||
|
this._element.appendChild( tbody );
|
||
|
} else {
|
||
|
Element.setHTML(tbody, '');
|
||
|
}
|
||
|
|
||
|
this._data = [];
|
||
|
var row;
|
||
|
|
||
|
for (var trIndex in rows) {
|
||
|
if (rows.hasOwnProperty(trIndex)) {
|
||
|
row = this._options.processJSONRow(rows[trIndex]);
|
||
|
this._createSingleRowFromJson(tbody, row, trIndex);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this._originalData = this._data.slice(0);
|
||
|
},
|
||
|
|
||
|
_createSingleRowFromJson: function (tbody, row, rowIndex) {
|
||
|
var tr = document.createElement('tr');
|
||
|
tbody.appendChild( tr );
|
||
|
for( var field in row ){
|
||
|
if (row.hasOwnProperty(field)) {
|
||
|
this._createFieldFromJson(tr, row[field], field, rowIndex);
|
||
|
}
|
||
|
}
|
||
|
this._data.push(tr);
|
||
|
},
|
||
|
|
||
|
_createFieldFromJson: function (tr, fieldData, fieldName, rowIndex) {
|
||
|
if (!this._fieldIsVisible(fieldName)) { return; }
|
||
|
|
||
|
var processor =
|
||
|
this._options.processJSONField[fieldName] || // per-field callback
|
||
|
this._options.processJSONField; // generic callback
|
||
|
|
||
|
var result;
|
||
|
if (typeof processor === 'function') {
|
||
|
result = processor(fieldData, fieldName, rowIndex);
|
||
|
} else {
|
||
|
result = fieldData;
|
||
|
}
|
||
|
var elm = this._elOrFieldData(result);
|
||
|
|
||
|
var className = this._options.tdClassNames[fieldName];
|
||
|
if (className) {
|
||
|
Css.addClassName(elm, className);
|
||
|
}
|
||
|
|
||
|
tr.appendChild(elm);
|
||
|
},
|
||
|
|
||
|
_elOrFieldData: function (processed) {
|
||
|
if (Common.isDOMElement(processed)) {
|
||
|
return processed;
|
||
|
}
|
||
|
|
||
|
var isString = typeof processed === 'string';
|
||
|
var isNumber = typeof processed === 'number';
|
||
|
var elm = Element.create('td');
|
||
|
|
||
|
if (isString && /^\s*?</.test(processed)) {
|
||
|
Element.setHTML(elm, processed);
|
||
|
} else if (isString || isNumber) {
|
||
|
Element.setTextContent(elm, processed);
|
||
|
} else {
|
||
|
throw new Error('Ink.UI.Table Unknown result from processJSONField: ' + processed);
|
||
|
}
|
||
|
|
||
|
return elm;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Sets the AJAX endpoint.
|
||
|
* Useful to change the endpoint in runtime.
|
||
|
*
|
||
|
* @method setEndpoint
|
||
|
* @public
|
||
|
* @param {String} endpoint New endpoint
|
||
|
*/
|
||
|
setEndpoint: function( endpoint, currentPage ){
|
||
|
if( !this._markupMode ){
|
||
|
this._options.endpoint = endpoint;
|
||
|
if (this._pagination) {
|
||
|
this._pagination.setCurrent((!!currentPage) ? parseInt(currentPage,10) : 0 );
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Sets the instance's pagination, if necessary.
|
||
|
*
|
||
|
* Precondition: this._totalRows needs to be known.
|
||
|
*
|
||
|
* @method _setPagination
|
||
|
* @private
|
||
|
*/
|
||
|
_setPagination: function(){
|
||
|
/* If user doesn't say they want pagination, bail. */
|
||
|
if( this._options.pageSize == null ){ return; }
|
||
|
|
||
|
/**
|
||
|
* Fetch pagination from options. Can be a selector string, an element or a Pagination instance.
|
||
|
*/
|
||
|
var paginationEl = this._options.pagination;
|
||
|
|
||
|
if ( paginationEl instanceof Pagination ) {
|
||
|
this._pagination = paginationEl;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!paginationEl) {
|
||
|
paginationEl = Element.create('nav', {
|
||
|
className: 'ink-navigation',
|
||
|
insertAfter: this._element
|
||
|
});
|
||
|
Element.create('ul', {
|
||
|
className: 'pagination',
|
||
|
insertBottom: paginationEl
|
||
|
});
|
||
|
}
|
||
|
|
||
|
var paginationOptions = Ink.extendObj({
|
||
|
totalItemCount: this._totalRows,
|
||
|
itemsPerPage: this._options.pageSize,
|
||
|
onChange: Ink.bind(function (_, pageNo) {
|
||
|
this._paginate(pageNo + 1);
|
||
|
}, this)
|
||
|
}, this._options.paginationOptions || {});
|
||
|
|
||
|
this._pagination = new Pagination(paginationEl, paginationOptions);
|
||
|
|
||
|
this._paginate(1);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Method to choose which is the best way to get the data based on the endpoint:
|
||
|
* - AJAX
|
||
|
* - JSONP
|
||
|
*
|
||
|
* @method _getData
|
||
|
* @private
|
||
|
*/
|
||
|
_getData: function( ){
|
||
|
var sortOrder = this._getSortOrder() || null;
|
||
|
var page = null;
|
||
|
|
||
|
if (this._pagination) {
|
||
|
page = {
|
||
|
size: this._options.pageSize,
|
||
|
page: this._pagination.getCurrent() + 1
|
||
|
};
|
||
|
}
|
||
|
|
||
|
this._getDataViaAjax( this._getUrl( sortOrder, page) );
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Return an object describing sort order { field: [field name] ,
|
||
|
* order: ["asc" or "desc"] }, or null if there is no sorting
|
||
|
* going on.
|
||
|
* @method _getSortOrder
|
||
|
* @private
|
||
|
*/
|
||
|
_getSortOrder: function () {
|
||
|
var index;
|
||
|
for (index in this._sortableFields) if (this._sortableFields.hasOwnProperty(index)) {
|
||
|
if( this._sortableFields[index] !== 'none' ){
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (!index) {
|
||
|
return null; // no sorting going on
|
||
|
}
|
||
|
return {
|
||
|
field: this._originalFields[index],
|
||
|
order: this._sortableFields[index]
|
||
|
};
|
||
|
},
|
||
|
|
||
|
_getUrl: function (sort, page) {
|
||
|
var urlCreator = this._options.createEndpointUrl ||
|
||
|
function (endpoint, sort, page
|
||
|
/* TODO implement filters too */) {
|
||
|
endpoint = InkUrl.parseUrl(endpoint);
|
||
|
endpoint.query = endpoint.query || {};
|
||
|
|
||
|
if (sort) {
|
||
|
endpoint.query.sortOrder = sort.order;
|
||
|
endpoint.query.sortField = sort.field;
|
||
|
}
|
||
|
|
||
|
if (page) {
|
||
|
endpoint.query['rows_per_page'] = page.size;
|
||
|
endpoint.query['page'] = page.page;
|
||
|
}
|
||
|
|
||
|
return InkUrl.format(endpoint);
|
||
|
};
|
||
|
|
||
|
var ret = urlCreator(this._options.endpoint, sort, page);
|
||
|
|
||
|
if (typeof ret !== 'string') {
|
||
|
throw new TypeError('Ink.UI.Table_1: ' +
|
||
|
'createEndpointUrl did not return a string!');
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Gets the data via AJAX and calls this._onAjaxSuccess with the response.
|
||
|
*
|
||
|
* Will call options.getDataFromEndpoint( Uri, callback ) if available.
|
||
|
*
|
||
|
* @param endpointUri Endpoint to get data from, after processing.
|
||
|
*/
|
||
|
_getDataViaAjax: function( endpointUri ){
|
||
|
var success = Ink.bind(function( JSONData ){
|
||
|
this._onAjaxSuccess( JSONData );
|
||
|
}, this);
|
||
|
|
||
|
if (!this._options.getDataFromEndpoint) {
|
||
|
new Ajax( endpointUri, {
|
||
|
method: 'GET',
|
||
|
contentType: 'application/json',
|
||
|
sanitizeJSON: true,
|
||
|
onSuccess: Ink.bind(function( response ){
|
||
|
if( response.status === 200 ){
|
||
|
success(Json.parse(response.responseText));
|
||
|
}
|
||
|
}, this)
|
||
|
});
|
||
|
} else {
|
||
|
this._options.getDataFromEndpoint( endpointUri, success );
|
||
|
}
|
||
|
},
|
||
|
|
||
|
_onAjaxSuccess: function (jsonResponse) {
|
||
|
var paginated = this._options.pageSize != null;
|
||
|
var rows = this._options.processJSONRows(jsonResponse);
|
||
|
this._headers = Selector.select('th', this._thead);
|
||
|
|
||
|
// If headers not in DOM, get from JSON
|
||
|
if( this._headers.length === 0 ) {
|
||
|
var headers = this._options.processJSONHeaders(
|
||
|
jsonResponse);
|
||
|
if (!headers || !headers.length || !headers[0]) {
|
||
|
throw new Error('Ink.UI.Table: processJSONHeaders option must return an array of objects!');
|
||
|
}
|
||
|
this._createHeadersFromJson( headers );
|
||
|
this._resetSortOrder();
|
||
|
this._addHeadersClasses();
|
||
|
}
|
||
|
|
||
|
this._createRowsFromJSON( rows );
|
||
|
|
||
|
this._totalRows = this._rowLength = rows.length;
|
||
|
|
||
|
if( paginated ){
|
||
|
this._totalRows = this._options.processJSONTotalRows(jsonResponse);
|
||
|
this._setPagination( );
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Common.createUIComponent(Table);
|
||
|
|
||
|
return Table;
|
||
|
|
||
|
});
|