/src/w2grid.js
https://github.com/delebash/w2ui · JavaScript · 5177 lines · 4531 code · 201 blank · 445 comment · 1920 complexity · 4ac01842935df7bb7d4875cf0b3ada86 MD5 · raw file
Large files are truncated click here to view the full file
- /************************************************************************
- * Library: Web 2.0 UI for jQuery (using prototypical inheritance)
- * - Following objects defined
- * - w2grid - grid widget
- * - $().w2grid - jQuery wrapper
- * - Dependencies: jQuery, w2utils, w2toolbar, w2fields, w2alert, w2confirm
- *
- * == NICE TO HAVE ==
- * - frozen columns
- * - add colspans
- * - allow this.total to be unknown (-1)
- * - column autosize based on largest content
- * - easy bubbles in the grid
- * - More than 2 layers of header groups
- * - reorder columns/records
- * - hidden searches could not be clearned by the user
- * - problem with .set() and arrays, array get extended too, but should be replaced
- * - move events into prototype
- * - add grid.focus()
- * - add showExtra, KickIn Infinite scroll when so many records
- * - after edit stay on the same record option
- * - allow render: function to be filters
- * - if supplied array of ids, get should return array of records
- * - row drag and drop has bugs
- *
- * == 1.5 changes
- * - $('#grid').w2grid() - if called w/o argument then it returns grid object
- * - added statusRange : true,
- * statusBuffered : false,
- * statusRecordID : true,
- * statusSelection : true,
- * statusResponse : true,
- * statusSort : true,
- * statusSearch : true,
- * - change selectAll() and selectNone() - return time it took
- *
- ************************************************************************/
- (function () {
- var w2grid = function(options) {
- // public properties
- this.name = null;
- this.box = null; // HTML element that hold this element
- this.header = '';
- this.url = '';
- this.routeData = {}; // data for dynamic routes
- this.columns = []; // { field, caption, size, attr, render, hidden, gridMinWidth, editable }
- this.columnGroups = []; // { span: int, caption: 'string', master: true/false }
- this.records = []; // { recid: int(requied), field1: 'value1', ... fieldN: 'valueN', style: 'string', editable: true/false, summary: true/false, changes: object }
- this.summary = []; // arry of summary records, same structure as records array
- this.searches = []; // { type, caption, field, inTag, outTag, hidden }
- this.searchData = [];
- this.sortData = [];
- this.postData = {};
- this.toolbar = {}; // if not empty object; then it is toolbar object
- this.show = {
- header : false,
- toolbar : false,
- footer : false,
- columnHeaders : true,
- lineNumbers : false,
- expandColumn : false,
- selectColumn : false,
- emptyRecords : true,
- toolbarReload : true,
- toolbarColumns : true,
- toolbarSearch : true,
- toolbarAdd : false,
- toolbarEdit : false,
- toolbarDelete : false,
- toolbarSave : false,
- statusRange : true,
- statusBuffered : false,
- statusRecordID : true,
- statusSelection : true,
- statusResponse : true,
- statusSort : true,
- statusSearch : true,
- recordTitles : true,
- selectionBorder : true,
- skipRecords : true
- };
- this.autoLoad = true; // for infinite scroll
- this.fixedBody = true; // if false; then grid grows with data
- this.recordHeight = 24;
- this.keyboard = true;
- this.selectType = 'row'; // can be row|cell
- this.multiSearch = true;
- this.multiSelect = true;
- this.multiSort = true;
- this.reorderColumns = false;
- this.reorderRows = false;
- this.markSearch = true;
- this.total = 0; // server total
- this.limit = 100;
- this.offset = 0; // how many records to skip (for infinite scroll) when pulling from server
- this.style = '';
- this.ranges = [];
- this.menu = [];
- this.method = null; // if defined, then overwrited ajax method
- this.recid = null;
- this.parser = null;
- // events
- this.onAdd = null;
- this.onEdit = null;
- this.onRequest = null; // called on any server event
- this.onLoad = null;
- this.onDelete = null;
- this.onDeleted = null;
- this.onSubmit = null;
- this.onSave = null;
- this.onSelect = null;
- this.onUnselect = null;
- this.onClick = null;
- this.onDblClick = null;
- this.onContextMenu = null;
- this.onMenuClick = null; // when context menu item selected
- this.onColumnClick = null;
- this.onColumnResize = null;
- this.onSort = null;
- this.onSearch = null;
- this.onChange = null; // called when editable record is changed
- this.onRestore = null; // called when editable record is restored
- this.onExpand = null;
- this.onCollapse = null;
- this.onError = null;
- this.onKeydown = null;
- this.onToolbar = null; // all events from toolbar
- this.onColumnOnOff = null;
- this.onCopy = null;
- this.onPaste = null;
- this.onSelectionExtend = null;
- this.onEditField = null;
- this.onRender = null;
- this.onRefresh = null;
- this.onReload = null;
- this.onResize = null;
- this.onDestroy = null;
- this.onStateSave = null;
- this.onStateRestore = null;
- // internal
- this.last = {
- field : 'all',
- caption : w2utils.lang('All Fields'),
- logic : 'OR',
- search : '',
- searchIds : [],
- selection : {
- indexes : [],
- columns : {}
- },
- multi : false,
- scrollTop : 0,
- scrollLeft : 0,
- sortData : null,
- sortCount : 0,
- xhr : null,
- range_start : null,
- range_end : null,
- sel_ind : null,
- sel_col : null,
- sel_type : null,
- edit_col : null
- };
- $.extend(true, this, w2obj.grid, options);
- };
- // ====================================================
- // -- Registers as a jQuery plugin
- $.fn.w2grid = function(method) {
- if ($.isPlainObject(method)) {
- // check name parameter
- if (!w2utils.checkName(method, 'w2grid')) return;
- // remember items
- var columns = method.columns;
- var columnGroups = method.columnGroups;
- var records = method.records;
- var searches = method.searches;
- var searchData = method.searchData;
- var sortData = method.sortData;
- var postData = method.postData;
- var toolbar = method.toolbar;
- // extend items
- var object = new w2grid(method);
- $.extend(object, { postData: {}, records: [], columns: [], searches: [], toolbar: {}, sortData: [], searchData: [], handlers: [] });
- if (object.onExpand != null) object.show.expandColumn = true;
- $.extend(true, object.toolbar, toolbar);
- // reassign variables
- for (var p in columns) object.columns[p] = $.extend(true, {}, columns[p]);
- for (var p in columnGroups) object.columnGroups[p] = $.extend(true, {}, columnGroups[p]);
- for (var p in searches) object.searches[p] = $.extend(true, {}, searches[p]);
- for (var p in searchData) object.searchData[p] = $.extend(true, {}, searchData[p]);
- for (var p in sortData) object.sortData[p] = $.extend(true, {}, sortData[p]);
- object.postData = $.extend(true, {}, postData);
- // check if there are records without recid
- for (var r in records) {
- if (records[r].recid == null || typeof records[r].recid == 'undefined') {
- console.log('ERROR: Cannot add records without recid. (obj: '+ object.name +')');
- return;
- }
- object.records[r] = $.extend(true, {}, records[r]);
- }
- // add searches
- for (var c in object.columns) {
- var col = object.columns[c];
- if (typeof col.searchable == 'undefined' || object.getSearch(col.field) != null) continue;
- var stype = col.searchable;
- var attr = '';
- if (col.searchable === true) { stype = 'text'; attr = 'size="20"'; }
- object.addSearch({ field: col.field, caption: col.caption, type: stype, attr: attr });
- }
- // init toolbar
- object.initToolbar();
- // render if necessary
- if ($(this).length !== 0) {
- object.render($(this)[0]);
- }
- // register new object
- w2ui[object.name] = object;
- return object;
- } else {
- var obj = w2ui[$(this).attr('name')];
- if (!obj) return null;
- if (arguments.length > 0) {
- if (obj[method]) obj[method].apply(obj, Array.prototype.slice.call(arguments, 1));
- return this;
- } else {
- return obj;
- }
- }
- };
- // ====================================================
- // -- Implementation of core functionality
- w2grid.prototype = {
- // ----
- // properties that need to be in prototype
- msgDelete : 'Are you sure you want to delete selected records?',
- msgNotJSON : 'Returned data is not in valid JSON format.',
- msgAJAXerror : 'AJAX error. See console for more details.',
- msgRefresh : 'Refreshing...',
- // for easy button overwrite
- buttons: {
- 'reload' : { type: 'button', id: 'w2ui-reload', icon: 'w2ui-icon-reload', hint: 'Reload data in the list' },
- 'columns' : { type: 'drop', id: 'w2ui-column-on-off', icon: 'w2ui-icon-columns', hint: 'Show/hide columns', arrow: false, html: '' },
- 'search' : { type: 'html', id: 'w2ui-search',
- html: '<div class="w2ui-icon icon-search-down w2ui-search-down" title="'+ 'Select Search Field' +'" '+
- 'onclick="var obj = w2ui[$(this).parents(\'div.w2ui-grid\').attr(\'name\')]; obj.searchShowFields();"></div>'
- },
- 'search-go': { type: 'check', id: 'w2ui-search-advanced', caption: 'Search...', hint: 'Open Search Fields' },
- 'add' : { type: 'button', id: 'w2ui-add', caption: 'Add New', hint: 'Add new record', icon: 'w2ui-icon-plus' },
- 'edit' : { type: 'button', id: 'w2ui-edit', caption: 'Edit', hint: 'Edit selected record', icon: 'w2ui-icon-pencil', disabled: true },
- 'delete' : { type: 'button', id: 'w2ui-delete', caption: 'Delete', hint: 'Delete selected records', icon: 'w2ui-icon-cross', disabled: true },
- 'save' : { type: 'button', id: 'w2ui-save', caption: 'Save', hint: 'Save changed records', icon: 'w2ui-icon-check' }
- },
- add: function (record) {
- if (!$.isArray(record)) record = [record];
- var added = 0;
- for (var o in record) {
- if (!this.recid && typeof record[o].recid == 'undefined') record[o].recid = record[o][this.recid];
- if (record[o].recid == null || typeof record[o].recid == 'undefined') {
- console.log('ERROR: Cannot add record without recid. (obj: '+ this.name +')');
- continue;
- }
- this.records.push(record[o]);
- added++;
- }
- var url = (typeof this.url != 'object' ? this.url : this.url.get);
- if (!url) {
- this.total = this.records.length;
- this.localSort();
- this.localSearch();
- }
- this.refresh(); // ?? should it be reload?
- return added;
- },
- find: function (obj, returnIndex) {
- if (typeof obj == 'undefined' || obj == null) obj = {};
- var recs = [];
- var hasDots = false;
- // check if property is nested - needed for speed
- for (var o in obj) if (String(o).indexOf('.') != -1) hasDots = true;
- // look for an item
- for (var i = 0; i < this.records.length; i++) {
- var match = true;
- for (var o in obj) {
- var val = this.records[i][o];
- if (hasDots && String(o).indexOf('.') != -1) val = this.parseField(this.records[i], o);
- if (obj[o] != val) match = false;
- }
- if (match && returnIndex !== true) recs.push(this.records[i].recid);
- if (match && returnIndex === true) recs.push(i);
- }
- return recs;
- },
- set: function (recid, record, noRefresh) { // does not delete existing, but overrides on top of it
- if (typeof recid == 'object') {
- noRefresh = record;
- record = recid;
- recid = null;
- }
- // update all records
- if (recid == null) {
- for (var r in this.records) {
- $.extend(true, this.records[r], record); // recid is the whole record
- }
- if (noRefresh !== true) this.refresh();
- } else { // find record to update
- var ind = this.get(recid, true);
- if (ind == null) return false;
- var isSummary = (this.records[ind] && this.records[ind].recid == recid ? false : true);
- if (isSummary) {
- $.extend(true, this.summary[ind], record);
- } else {
- $.extend(true, this.records[ind], record);
- }
- if (noRefresh !== true) this.refreshRow(recid); // refresh only that record
- }
- return true;
- },
- get: function (recid, returnIndex) {
- // search records
- for (var i = 0; i < this.records.length; i++) {
- if (this.records[i].recid == recid) {
- if (returnIndex === true) return i; else return this.records[i];
- }
- }
- // search summary
- for (var i = 0; i < this.summary.length; i++) {
- if (this.summary[i].recid == recid) {
- if (returnIndex === true) return i; else return this.summary[i];
- }
- }
- return null;
- },
- remove: function () {
- var removed = 0;
- for (var a = 0; a < arguments.length; a++) {
- for (var r = this.records.length-1; r >= 0; r--) {
- if (this.records[r].recid == arguments[a]) { this.records.splice(r, 1); removed++; }
- }
- }
- var url = (typeof this.url != 'object' ? this.url : this.url.get);
- if (!url) {
- this.localSort();
- this.localSearch();
- }
- this.refresh();
- return removed;
- },
- addColumn: function (before, columns) {
- var added = 0;
- if (arguments.length == 1) {
- columns = before;
- before = this.columns.length;
- } else {
- if (typeof before == 'string') before = this.getColumn(before, true);
- if (before === null) before = this.columns.length;
- }
- if (!$.isArray(columns)) columns = [columns];
- for (var o in columns) {
- this.columns.splice(before, 0, columns[o]);
- before++;
- added++;
- }
- this.refresh();
- return added;
- },
- removeColumn: function () {
- var removed = 0;
- for (var a = 0; a < arguments.length; a++) {
- for (var r = this.columns.length-1; r >= 0; r--) {
- if (this.columns[r].field == arguments[a]) { this.columns.splice(r, 1); removed++; }
- }
- }
- this.refresh();
- return removed;
- },
- getColumn: function (field, returnIndex) {
- for (var i = 0; i < this.columns.length; i++) {
- if (this.columns[i].field == field) {
- if (returnIndex === true) return i; else return this.columns[i];
- }
- }
- return null;
- },
- toggleColumn: function () {
- var effected = 0;
- for (var a = 0; a < arguments.length; a++) {
- for (var r = this.columns.length-1; r >= 0; r--) {
- var col = this.columns[r];
- if (col.field == arguments[a]) {
- col.hidden = !col.hidden;
- effected++;
- }
- }
- }
- this.refresh();
- return effected;
- },
- showColumn: function () {
- var shown = 0;
- for (var a = 0; a < arguments.length; a++) {
- for (var r = this.columns.length-1; r >= 0; r--) {
- var col = this.columns[r];
- if (col.gridMinWidth) delete col.gridMinWidth;
- if (col.field == arguments[a] && col.hidden !== false) {
- col.hidden = false;
- shown++;
- }
- }
- }
- this.refresh();
- return shown;
- },
- hideColumn: function () {
- var hidden = 0;
- for (var a = 0; a < arguments.length; a++) {
- for (var r = this.columns.length-1; r >= 0; r--) {
- var col = this.columns[r];
- if (col.field == arguments[a] && col.hidden !== true) {
- col.hidden = true;
- hidden++;
- }
- }
- }
- this.refresh();
- return hidden;
- },
- addSearch: function (before, search) {
- var added = 0;
- if (arguments.length == 1) {
- search = before;
- before = this.searches.length;
- } else {
- if (typeof before == 'string') before = this.getSearch(before, true);
- if (before === null) before = this.searches.length;
- }
- if (!$.isArray(search)) search = [search];
- for (var o in search) {
- this.searches.splice(before, 0, search[o]);
- before++;
- added++;
- }
- this.searchClose();
- return added;
- },
- removeSearch: function () {
- var removed = 0;
- for (var a = 0; a < arguments.length; a++) {
- for (var r = this.searches.length-1; r >= 0; r--) {
- if (this.searches[r].field == arguments[a]) { this.searches.splice(r, 1); removed++; }
- }
- }
- this.searchClose();
- return removed;
- },
- getSearch: function (field, returnIndex) {
- for (var i = 0; i < this.searches.length; i++) {
- if (this.searches[i].field == field) {
- if (returnIndex === true) return i; else return this.searches[i];
- }
- }
- return null;
- },
- toggleSearch: function () {
- var effected = 0;
- for (var a = 0; a < arguments.length; a++) {
- for (var r = this.searches.length-1; r >= 0; r--) {
- if (this.searches[r].field == arguments[a]) {
- this.searches[r].hidden = !this.searches[r].hidden;
- effected++;
- }
- }
- }
- this.searchClose();
- return effected;
- },
- showSearch: function () {
- var shown = 0;
- for (var a = 0; a < arguments.length; a++) {
- for (var r = this.searches.length-1; r >= 0; r--) {
- if (this.searches[r].field == arguments[a] && this.searches[r].hidden !== false) {
- this.searches[r].hidden = false;
- shown++;
- }
- }
- }
- this.searchClose();
- return shown;
- },
- hideSearch: function () {
- var hidden = 0;
- for (var a = 0; a < arguments.length; a++) {
- for (var r = this.searches.length-1; r >= 0; r--) {
- if (this.searches[r].field == arguments[a] && this.searches[r].hidden !== true) {
- this.searches[r].hidden = true;
- hidden++;
- }
- }
- }
- this.searchClose();
- return hidden;
- },
- getSearchData: function (field) {
- for (var s in this.searchData) {
- if (this.searchData[s].field == field) return this.searchData[s];
- }
- return null;
- },
- localSort: function (silent) {
- var url = (typeof this.url != 'object' ? this.url : this.url.get);
- if (url) {
- console.log('ERROR: grid.localSort can only be used on local data source, grid.url should be empty.');
- return;
- }
- if ($.isEmptyObject(this.sortData)) return;
- var time = (new Date()).getTime();
- var obj = this;
- // process date fields
- obj.prepareData();
- // process sortData
- for (var s in this.sortData) {
- var column = this.getColumn(this.sortData[s].field);
- if (!column) return;
- if (typeof column.render == 'string') {
- if (['date', 'age'].indexOf(column.render.split(':')[0]) != -1) {
- this.sortData[s]['field_'] = column.field + '_';
- }
- if (['time'].indexOf(column.render.split(':')[0]) != -1) {
- this.sortData[s]['field_'] = column.field + '_';
- }
- }
- }
- // process sort
- this.records.sort(function (a, b) {
- var ret = 0;
- for (var s in obj.sortData) {
- var fld = obj.sortData[s].field;
- if (obj.sortData[s].field_) fld = obj.sortData[s].field_;
- var aa = a[fld];
- var bb = b[fld];
- if (String(fld).indexOf('.') != -1) {
- aa = obj.parseField(a, fld);
- bb = obj.parseField(b, fld);
- }
- if (typeof aa == 'string') aa = $.trim(aa.toLowerCase());
- if (typeof bb == 'string') bb = $.trim(bb.toLowerCase());
- if (aa > bb) ret = (obj.sortData[s].direction == 'asc' ? 1 : -1);
- if (aa < bb) ret = (obj.sortData[s].direction == 'asc' ? -1 : 1);
- if (typeof aa != 'object' && typeof bb == 'object') ret = -1;
- if (typeof bb != 'object' && typeof aa == 'object') ret = 1;
- if (aa == null && bb != null) ret = 1; // all nuls and undefined on bottom
- if (aa != null && bb == null) ret = -1;
- if (ret != 0) break;
- }
- return ret;
- });
- time = (new Date()).getTime() - time;
- if (silent !== true && obj.show.statusSort) {
- setTimeout(function () {
- obj.status(w2utils.lang('Sorting took') + ' ' + time/1000 + ' ' + w2utils.lang('sec'));
- }, 10);
- }
- return time;
- },
- localSearch: function (silent) {
- var url = (typeof this.url != 'object' ? this.url : this.url.get);
- if (url) {
- console.log('ERROR: grid.localSearch can only be used on local data source, grid.url should be empty.');
- return;
- }
- var time = (new Date()).getTime();
- var obj = this;
- this.total = this.records.length;
- // mark all records as shown
- this.last.searchIds = [];
- // prepare date/time fields
- this.prepareData();
- // hide records that did not match
- if (this.searchData.length > 0 && !url) {
- this.total = 0;
- for (var r in this.records) {
- var rec = this.records[r];
- var fl = 0;
- for (var s in this.searchData) {
- var sdata = this.searchData[s];
- var search = this.getSearch(sdata.field);
- if (sdata == null) continue;
- if (search == null) search = { field: sdata.field, type: sdata.type };
- var val1 = String(obj.parseField(rec, search.field)).toLowerCase();
- if (typeof sdata.value != 'undefined') {
- if (!$.isArray(sdata.value)) {
- var val2 = String(sdata.value).toLowerCase();
- } else {
- var val2 = sdata.value[0];
- var val3 = sdata.value[1];
- }
- }
- switch (sdata.operator) {
- case 'is':
- if (rec[search.field] == sdata.value) fl++; // do not hide record
- if (search.type == 'date') {
- var val1 = w2utils.formatDate(rec[search.field + '_'], 'yyyy-mm-dd');
- var val2 = w2utils.formatDate(val2, 'yyyy-mm-dd');
- if (val1 == val2) fl++;
- }
- if (search.type == 'time') {
- var val1 = w2utils.formatTime(rec[search.field + '_'], 'h24:mi');
- var val2 = w2utils.formatTime(val2, 'h24:mi');
- if (val1 == val2) fl++;
- }
- break;
- case 'between':
- if (['int', 'float', 'money', 'currency', 'percent'].indexOf(search.type) != -1) {
- if (parseFloat(rec[search.field]) >= parseFloat(val2) && parseFloat(rec[search.field]) <= parseFloat(val3)) fl++;
- }
- if (search.type == 'date') {
- var val1 = rec[search.field + '_'];
- var val2 = w2utils.isDate(val2, w2utils.settings.date_format, true);
- var val3 = w2utils.isDate(val3, w2utils.settings.date_format, true);
- if (val3 != null) val3 = new Date(val3.getTime() + 86400000); // 1 day
- if (val1 >= val2 && val1 < val3) fl++;
- }
- if (search.type == 'time') {
- var val1 = rec[search.field + '_'];
- var val2 = w2utils.isTime(val2, true);
- var val3 = w2utils.isTime(val3, true);
- val2 = (new Date()).setHours(val2.hours, val2.minutes, val2.seconds ? val2.seconds : 0, 0);
- val3 = (new Date()).setHours(val3.hours, val3.minutes, val3.seconds ? val3.seconds : 0, 0);
- if (val1 >= val2 && val1 < val3) fl++;
- }
- break;
- case 'in':
- var tmp = sdata.value;
- if (sdata.svalue) tmp = sdata.svalue;
- if (tmp.indexOf(val1) !== -1) fl++;
- break;
- case 'not in':
- var tmp = sdata.value;
- if (sdata.svalue) tmp = sdata.svalue;
- if (tmp.indexOf(val1) == -1) fl++;
- break;
- case 'begins':
- case 'begins with': // need for back compatib.
- if (val1.indexOf(val2) == 0) fl++; // do not hide record
- break;
- case 'contains':
- if (val1.indexOf(val2) >= 0) fl++; // do not hide record
- break;
- case 'ends':
- case 'ends with': // need for back compatib.
- if (val1.indexOf(val2) == val1.length - val2.length) fl++; // do not hide record
- break;
- }
- }
- if ((this.last.logic == 'OR' && fl != 0) || (this.last.logic == 'AND' && fl == this.searchData.length)) this.last.searchIds.push(parseInt(r));
- }
- this.total = this.last.searchIds.length;
- }
- time = (new Date()).getTime() - time;
- if (silent !== true && obj.show.statusSearch) {
- setTimeout(function () {
- obj.status(w2utils.lang('Search took') + ' ' + time/1000 + ' ' + w2utils.lang('sec'));
- }, 10);
- }
- return time;
- },
- getRangeData: function (range, extra) {
- var rec1 = this.get(range[0].recid, true);
- var rec2 = this.get(range[1].recid, true);
- var col1 = range[0].column;
- var col2 = range[1].column;
- var res = [];
- if (col1 == col2) { // one row
- for (var r = rec1; r <= rec2; r++) {
- var record = this.records[r];
- var dt = record[this.columns[col1].field] || null;
- if (extra !== true) {
- res.push(dt);
- } else {
- res.push({ data: dt, column: col1, index: r, record: record });
- }
- }
- } else if (rec1 == rec2) { // one line
- var record = this.records[rec1];
- for (var i = col1; i <= col2; i++) {
- var dt = record[this.columns[i].field] || null;
- if (extra !== true) {
- res.push(dt);
- } else {
- res.push({ data: dt, column: i, index: rec1, record: record });
- }
- }
- } else {
- for (var r = rec1; r <= rec2; r++) {
- var record = this.records[r];
- res.push([]);
- for (var i = col1; i <= col2; i++) {
- var dt = record[this.columns[i].field];
- if (extra !== true) {
- res[res.length-1].push(dt);
- } else {
- res[res.length-1].push({ data: dt, column: i, index: r, record: record });
- }
- }
- }
- }
- return res;
- },
- addRange: function (ranges) {
- var added = 0;
- if (this.selectType == 'row') return added;
- if (!$.isArray(ranges)) ranges = [ranges];
- // if it is selection
- for (var r in ranges) {
- if (typeof ranges[r] != 'object') ranges[r] = { name: 'selection' };
- if (ranges[r].name == 'selection') {
- if (this.show.selectionBorder === false) continue;
- var sel = this.getSelection();
- if (sel.length == 0) {
- this.removeRange(ranges[r].name);
- continue;
- } else {
- var first = sel[0];
- var last = sel[sel.length-1];
- var td1 = $('#grid_'+ this.name +'_rec_'+ first.recid + ' td[col='+ first.column +']');
- var td2 = $('#grid_'+ this.name +'_rec_'+ last.recid + ' td[col='+ last.column +']');
- }
- } else { // other range
- var first = ranges[r].range[0];
- var last = ranges[r].range[1];
- var td1 = $('#grid_'+ this.name +'_rec_'+ first.recid + ' td[col='+ first.column +']');
- var td2 = $('#grid_'+ this.name +'_rec_'+ last.recid + ' td[col='+ last.column +']');
- }
- if (first) {
- var rg = {
- name: ranges[r].name,
- range: [{ recid: first.recid, column: first.column }, { recid: last.recid, column: last.column }],
- style: ranges[r].style || ''
- };
- // add range
- var ind = false;
- for (var t in this.ranges) if (this.ranges[t].name == ranges[r].name) { ind = r; break; }
- if (ind !== false) {
- this.ranges[ind] = rg;
- } else {
- this.ranges.push(rg);
- }
- added++
- }
- }
- this.refreshRanges();
- return added;
- },
- removeRange: function () {
- var removed = 0;
- for (var a = 0; a < arguments.length; a++) {
- var name = arguments[a];
- $('#grid_'+ this.name +'_'+ name).remove();
- for (var r = this.ranges.length-1; r >= 0; r--) {
- if (this.ranges[r].name == name) {
- this.ranges.splice(r, 1);
- removed++;
- }
- }
- }
- return removed;
- },
- refreshRanges: function () {
- var obj = this;
- var time = (new Date()).getTime();
- var rec = $('#grid_'+ this.name +'_records');
- for (var r in this.ranges) {
- var rg = this.ranges[r];
- var first = rg.range[0];
- var last = rg.range[1];
- var td1 = $('#grid_'+ this.name +'_rec_'+ first.recid + ' td[col='+ first.column +']');
- var td2 = $('#grid_'+ this.name +'_rec_'+ last.recid + ' td[col='+ last.column +']');
- var sel1 = $('#grid_'+ this.name +'_rec_top').next().find('td.w2ui-selected');
- var sel2 = $('#grid_'+ this.name +'_rec_bottom').prev().find('td.w2ui-selected');
- if ($('#grid_'+ this.name +'_'+ rg.name).length == 0) {
- rec.append('<div id="grid_'+ this.name +'_' + rg.name +'" class="w2ui-selection" style="'+ rg.style +'">'+
- (rg.name == 'selection' ? '<div id="grid_'+ this.name +'_resizer" class="w2ui-selection-resizer"></div>' : '')+
- '</div>');
- } else {
- $('#grid_'+ this.name +'_'+ rg.name).attr('style', rg.style);
- }
- // if virtual scrolling kicked in
- if ((td1.length == 0 && td2.length != 0) || sel1.length > 0) {
- td1 = $('#grid_'+ this.name +'_rec_top').next().find('td[col='+ first.column +']');
- }
- if ((td2.length == 0 && td1.length != 0) || sel2.length > 0) {
- td2 = $('#grid_'+ this.name +'_rec_bottom').prev().find('td[col='+ last.column +']');
- }
- // display range
- if (td1.length > 0 && td2.length > 0) {
- $('#grid_'+ this.name +'_'+ rg.name).css({
- left : (td1.position().left - 1 + rec.scrollLeft()) + 'px',
- top : (td1.position().top - 1 + rec.scrollTop()) + 'px',
- width : (td2.position().left - td1.position().left + td2.width() + 3) + 'px',
- height : (td2.position().top - td1.position().top + td2.height() + 3) + 'px'
- });
- }
- }
- // add resizer events
- $(this.box).find('#grid_'+ this.name +'_resizer').off('mousedown').on('mousedown', mouseStart);
- //$(this.box).find('#grid_'+ this.name +'_resizer').off('selectstart').on('selectstart', function () { return false; }); // fixes chrome cursror bug
- var eventData = { phase: 'before', type: 'selectionExtend', target: obj.name, originalRange: null, newRange: null };
- function mouseStart (event) {
- var sel = obj.getSelection();
- obj.last.move = {
- type : 'expand',
- x : event.screenX,
- y : event.screenY,
- divX : 0,
- divY : 0,
- recid : sel[0].recid,
- column : sel[0].column,
- originalRange : [{ recid: sel[0].recid, column: sel[0].column }, { recid: sel[sel.length-1].recid, column: sel[sel.length-1].column }],
- newRange : [{ recid: sel[0].recid, column: sel[0].column }, { recid: sel[sel.length-1].recid, column: sel[sel.length-1].column }]
- };
- $(document).off('mousemove', mouseMove).on('mousemove', mouseMove);
- $(document).off('mouseup', mouseStop).on('mouseup', mouseStop);
- }
- function mouseMove (event) {
- var mv = obj.last.move;
- if (!mv || mv.type != 'expand') return;
- mv.divX = (event.screenX - mv.x);
- mv.divY = (event.screenY - mv.y);
- // find new cell
- var recid, column;
- var tmp = event.originalEvent.target;
- if (tmp.tagName != 'TD') tmp = $(tmp).parents('td')[0];
- if (typeof $(tmp).attr('col') != 'undefined') column = parseInt($(tmp).attr('col'));
- tmp = $(tmp).parents('tr')[0];
- recid = $(tmp).attr('recid');
- // new range
- if (mv.newRange[1].recid == recid && mv.newRange[1].column == column) return;
- var prevNewRange = $.extend({}, mv.newRange);
- mv.newRange = [{ recid: mv.recid, column: mv.column }, { recid: recid, column: column }];
- // event before
- eventData = obj.trigger($.extend(eventData, { originalRange: mv.originalRange, newRange : mv.newRange }));
- if (eventData.isCancelled === true) {
- mv.newRange = prevNewRange;
- eventData.newRange = prevNewRange;
- return;
- } else {
- // default behavior
- obj.removeRange('grid-selection-expand');
- obj.addRange({
- name : 'grid-selection-expand',
- range : eventData.newRange,
- style : 'background-color: rgba(100,100,100,0.1); border: 2px dotted rgba(100,100,100,0.5);'
- });
- }
- }
- function mouseStop (event) {
- // default behavior
- obj.removeRange('grid-selection-expand');
- delete obj.last.move;
- $(document).off('mousemove', mouseMove);
- $(document).off('mouseup', mouseStop);
- // event after
- obj.trigger($.extend(eventData, { phase: 'after' }));
- }
- return (new Date()).getTime() - time;
- },
- select: function () {
- var selected = 0;
- var sel = this.last.selection;
- if (!this.multiSelect) this.selectNone();
- for (var a = 0; a < arguments.length; a++) {
- var recid = typeof arguments[a] == 'object' ? arguments[a].recid : arguments[a];
- var record = this.get(recid);
- if (record == null) continue;
- var index = this.get(recid, true);
- var recEl = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid));
- if (this.selectType == 'row') {
- if (sel.indexes.indexOf(index) >= 0) continue;
- // event before
- var eventData = this.trigger({ phase: 'before', type: 'select', target: this.name, recid: recid, index: index });
- if (eventData.isCancelled === true) continue;
- // default action
- sel.indexes.push(index);
- sel.indexes.sort(function(a, b) { return a-b });
- recEl.addClass('w2ui-selected').data('selected', 'yes').find('.w2ui-col-number').addClass('w2ui-row-selected');
- recEl.find('.w2ui-grid-select-check').prop("checked", true);
- selected++;
- } else {
- var col = arguments[a].column;
- if (!w2utils.isInt(col)) { // select all columns
- var cols = [];
- for (var c in this.columns) { if (this.columns[c].hidden) continue; cols.push({ recid: recid, column: parseInt(c) }); }
- if (!this.multiSelect) cols = cols.splice(0, 1);
- return this.select.apply(this, cols);
- }
- var s = sel.columns[index] || [];
- if ($.isArray(s) && s.indexOf(col) != -1) continue;
- // event before
- var eventData = this.trigger({ phase: 'before', type: 'select', target: this.name, recid: recid, index: index, column: col });
- if (eventData.isCancelled === true) continue;
- // default action
- if (sel.indexes.indexOf(index) == -1) {
- sel.indexes.push(index);
- sel.indexes.sort(function(a, b) { return a-b });
- }
- s.push(col);
- s.sort(function(a, b) { return a-b }); // sort function must be for numerical sort
- recEl.find(' > td[col='+ col +']').addClass('w2ui-selected');
- recEl.find('.w2ui-col-number').addClass('w2ui-row-selected');
- $(this.box).find('.w2ui-grid-columns td[col='+ col +'] .w2ui-col-header').addClass('w2ui-col-selected');
- selected++;
- recEl.data('selected', 'yes');
- recEl.find('.w2ui-grid-select-check').prop("checked", true);
- // save back to selection object
- sel.columns[index] = s;
- }
- // event after
- this.trigger($.extend(eventData, { phase: 'after' }));
- }
- // all selected?
- if (sel.indexes.length == this.records.length || (this.searchData.length !== 0 && sel.indexes.length == this.last.searchIds.length)) {
- $('#grid_'+ this.name +'_check_all').prop('checked', true);
- } else {
- $('#grid_'+ this.name +'_check_all').prop('checked', false);
- }
- this.status();
- this.addRange('selection');
- return selected;
- },
- unselect: function () {
- var unselected = 0;
- var sel = this.last.selection;
- for (var a = 0; a < arguments.length; a++) {
- var recid = typeof arguments[a] == 'object' ? arguments[a].recid : arguments[a];
- var record = this.get(recid);
- if (record == null) continue;
- var index = this.get(record.recid, true);
- var recEl = $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid));
- if (this.selectType == 'row') {
- if (sel.indexes.indexOf(index) == -1) continue;
- // event before
- var eventData = this.trigger({ phase: 'before', type: 'unselect', target: this.name, recid: recid, index: index });
- if (eventData.isCancelled === true) continue;
- // default action
- sel.indexes.splice(sel.indexes.indexOf(index), 1);
- recEl.removeClass('w2ui-selected').removeData('selected').find('.w2ui-col-number').removeClass('w2ui-row-selected');
- if (recEl.length != 0) recEl[0].style.cssText = 'height: '+ this.recordHeight +'px; ' + recEl.attr('custom_style');
- recEl.find('.w2ui-grid-select-check').prop("checked", false);
- unselected++;
- } else {
- var col = arguments[a].column;
- if (!w2utils.isInt(col)) { // unselect all columns
- var cols = [];
- for (var c in this.columns) { if (this.columns[c].hidden) continue; cols.push({ recid: recid, column: parseInt(c) }); }
- return this.unselect.apply(this, cols);
- }
- var s = sel.columns[index];
- if (!$.isArray(s) || s.indexOf(col) == -1) continue;
- // event before
- var eventData = this.trigger({ phase: 'before', type: 'unselect', target: this.name, recid: recid, column: col });
- if (eventData.isCancelled === true) continue;
- // default action
- s.splice(s.indexOf(col), 1);
- $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)).find(' > td[col='+ col +']').removeClass('w2ui-selected');
- // check if any row/column still selected
- var isColSelected = false;
- var isRowSelected = false;
- var tmp = this.getSelection();
- for (var t in tmp) {
- if (tmp[t].column == col) isColSelected = true;
- if (tmp[t].recid == recid) isRowSelected = true;
- }
- if (!isColSelected) {
- $(this.box).find('.w2ui-grid-columns td[col='+ col +'] .w2ui-col-header').removeClass('w2ui-col-selected');
- }
- if (!isRowSelected) {
- $('#grid_'+ this.name +'_rec_'+ w2utils.escapeId(recid)).find('.w2ui-col-number').removeClass('w2ui-row-selected');
- }
- unselected++;
- if (s.length == 0) {
- delete sel.columns[index];
- sel.indexes.splice(sel.indexes.indexOf(index), 1);
- recEl.removeData('selected');
- recEl.find('.w2ui-grid-select-check').prop("checked", false);
- }
- }
- // event after
- this.trigger($.extend(eventData, { phase: 'after' }));
- }
- // all selected?
- if (sel.indexes.length == this.records.length || (this.searchData.length !== 0 && sel.indexes.length == this.last.searchIds.length)) {
- $('#grid_'+ this.name +'_check_all').prop('checked', true);
- } else {
- $('#grid_'+ this.name +'_check_all').prop('checked', false);
- }
- // show number of selected
- this.status();
- this.addRange('selection');
- return unselected;
- },
- selectAll: function () {
- var time = (new Date()).getTime();
- if (this.multiSelect === false) return;
- // event before
- var eventData = this.trigger({ phase: 'before', type: 'select', target: this.name, all: true });
- if (eventData.isCancelled === true) return;
- // default action
- var url = (typeof this.url != 'object' ? this.url : this.url.get);
- var sel = this.last.selection;
- var cols = [];
- for (var c in this.columns) cols.push(parseInt(c));
- // if local data source and searched
- sel.indexes = [];
- if (!url && this.searchData.length !== 0) {
- // local search applied
- for (var i = 0; i < this.last.searchIds.length; i++) {…