jquery-dynatable-rails 0.3.1 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +19 -3
- data/jquery-dynatable-rails.gemspec +5 -3
- data/lib/jquery/dynatable/rails.rb +2 -1
- data/lib/jquery/dynatable/rails/version.rb +1 -1
- data/vendor/assets/javascripts/jquery.dynatable.js +1681 -0
- data/vendor/assets/stylesheets/jquery.dynatable.css +69 -0
- metadata +36 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b6e2fa52f5c8abbb77cada9fe2b076b82d071908
|
4
|
+
data.tar.gz: f8ef2f1d0139ade6d88e07c7f806c12d504c4b20
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e23d4686145ee95fa3ae64cbd1c41952b0554be2fca8c2e6f75febc6f873141181f1024f490f0b681fcac6974810d3110c022e1213c2c31a7ca552a9d60edaa9
|
7
|
+
data.tar.gz: 23fba218fb0113acfebc5192ef1dd0465210ba783b36e966d4541329d6ff9a9f770598d39a5c3e583e4b5ad62f6b0e6182cbedf69ac5932bc4c2eac22c4a6502
|
data/README.md
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
# Jquery::Dynatable::Rails
|
2
2
|
|
3
|
-
|
3
|
+
This gem packages the jQuery [Dynatable](https://github.com/alfajango/jquery-dynatable)
|
4
|
+
plugin for easy use with the Rails 3.1+ asset pipleine.
|
5
|
+
|
6
|
+
Dynatable is a funner, semantic, interactive table plugin using jQuery,
|
7
|
+
HTML5, and JSON. And it's not just for tables.
|
4
8
|
|
5
9
|
## Installation
|
6
10
|
|
@@ -18,11 +22,23 @@ Or install it yourself as:
|
|
18
22
|
|
19
23
|
## Usage
|
20
24
|
|
21
|
-
|
25
|
+
/= require 'jquery.dynatable'
|
26
|
+
|
27
|
+
|
28
|
+
## Thanks
|
29
|
+
Thanks the authors for making the [Dynatable](https://github.com/alfajango/jquery-dynatable) plugin for jQuery:
|
30
|
+
|
31
|
+
Steve Schwartz -
|
32
|
+
[JangoSteve on Github](https://github.com/JangoSteve),
|
33
|
+
[@jangosteve on Twitter](https://twitter.com/jangosteve)
|
34
|
+
|
35
|
+
![Alfa Jango logo](https://s3.amazonaws.com/s3.alfajango.com/github-readmes/AlfaJango_Logo_Black_noname-tiny.png)
|
36
|
+
[Alfa Jango Open Source](http://os.alfajango.com) -
|
37
|
+
[@alfajango on Twitter](https://twitter.com/alfajango)
|
22
38
|
|
23
39
|
## Contributing
|
24
40
|
|
25
|
-
1. Fork it ( http://github.com
|
41
|
+
1. Fork it ( http://github.com/ricardodovalle/jquery-dynatable-rails/fork )
|
26
42
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
43
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
44
|
4. Push to the branch (`git push origin my-new-feature`)
|
@@ -20,8 +20,10 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
21
|
spec.require_paths = ["lib"]
|
22
22
|
|
23
|
-
spec.add_development_dependency 'bundler'
|
24
|
-
spec.add_development_dependency 'rake'
|
23
|
+
spec.add_development_dependency 'bundler'
|
24
|
+
spec.add_development_dependency 'rake'
|
25
|
+
|
26
|
+
spec.add_dependency 'jquery-rails'
|
27
|
+
spec.add_dependency "railties", ">= 3.2", "< 5.0"
|
25
28
|
|
26
|
-
spec.add_dependency 'jquery-rails', '~> 0'
|
27
29
|
end
|
@@ -0,0 +1,1681 @@
|
|
1
|
+
/*
|
2
|
+
* jQuery Dynatable plugin 0.3.1
|
3
|
+
*
|
4
|
+
* Copyright (c) 2014 Steve Schwartz (JangoSteve)
|
5
|
+
*
|
6
|
+
* Dual licensed under the AGPL and Proprietary licenses:
|
7
|
+
* http://www.dynatable.com/license/
|
8
|
+
*
|
9
|
+
* Date: Tue Jan 02 2014
|
10
|
+
*/
|
11
|
+
//
|
12
|
+
|
13
|
+
(function($) {
|
14
|
+
var defaults,
|
15
|
+
mergeSettings,
|
16
|
+
dt,
|
17
|
+
Model,
|
18
|
+
modelPrototypes = {
|
19
|
+
dom: Dom,
|
20
|
+
domColumns: DomColumns,
|
21
|
+
records: Records,
|
22
|
+
recordsCount: RecordsCount,
|
23
|
+
processingIndicator: ProcessingIndicator,
|
24
|
+
state: State,
|
25
|
+
sorts: Sorts,
|
26
|
+
sortsHeaders: SortsHeaders,
|
27
|
+
queries: Queries,
|
28
|
+
inputsSearch: InputsSearch,
|
29
|
+
paginationPage: PaginationPage,
|
30
|
+
paginationPerPage: PaginationPerPage,
|
31
|
+
paginationLinks: PaginationLinks
|
32
|
+
},
|
33
|
+
utility,
|
34
|
+
build,
|
35
|
+
processAll,
|
36
|
+
initModel,
|
37
|
+
defaultRowWriter,
|
38
|
+
defaultCellWriter,
|
39
|
+
defaultAttributeWriter,
|
40
|
+
defaultAttributeReader;
|
41
|
+
|
42
|
+
//-----------------------------------------------------------------
|
43
|
+
// Cached plugin global defaults
|
44
|
+
//-----------------------------------------------------------------
|
45
|
+
|
46
|
+
defaults = {
|
47
|
+
features: {
|
48
|
+
paginate: true,
|
49
|
+
sort: true,
|
50
|
+
pushState: true,
|
51
|
+
search: true,
|
52
|
+
recordCount: true,
|
53
|
+
perPageSelect: true
|
54
|
+
},
|
55
|
+
table: {
|
56
|
+
defaultColumnIdStyle: 'camelCase',
|
57
|
+
columns: null,
|
58
|
+
headRowSelector: 'thead tr', // or e.g. tr:first-child
|
59
|
+
bodyRowSelector: 'tbody tr',
|
60
|
+
headRowClass: null
|
61
|
+
},
|
62
|
+
inputs: {
|
63
|
+
queries: null,
|
64
|
+
sorts: null,
|
65
|
+
multisort: ['ctrlKey', 'shiftKey', 'metaKey'],
|
66
|
+
page: null,
|
67
|
+
queryEvent: 'blur change',
|
68
|
+
recordCountTarget: null,
|
69
|
+
recordCountPlacement: 'after',
|
70
|
+
paginationLinkTarget: null,
|
71
|
+
paginationLinkPlacement: 'after',
|
72
|
+
paginationClass: 'dynatable-pagination-links',
|
73
|
+
paginationLinkClass: 'dynatable-page-link',
|
74
|
+
paginationPrevClass: 'dynatable-page-prev',
|
75
|
+
paginationNextClass: 'dynatable-page-next',
|
76
|
+
paginationActiveClass: 'dynatable-active-page',
|
77
|
+
paginationDisabledClass: 'dynatable-disabled-page',
|
78
|
+
paginationPrev: 'Previous',
|
79
|
+
paginationNext: 'Next',
|
80
|
+
paginationGap: [1,2,2,1],
|
81
|
+
searchTarget: null,
|
82
|
+
searchPlacement: 'before',
|
83
|
+
perPageTarget: null,
|
84
|
+
perPagePlacement: 'before',
|
85
|
+
perPageText: 'Show: ',
|
86
|
+
recordCountText: 'Showing ',
|
87
|
+
processingText: 'Processing...'
|
88
|
+
},
|
89
|
+
dataset: {
|
90
|
+
ajax: false,
|
91
|
+
ajaxUrl: null,
|
92
|
+
ajaxCache: null,
|
93
|
+
ajaxOnLoad: false,
|
94
|
+
ajaxMethod: 'GET',
|
95
|
+
ajaxDataType: 'json',
|
96
|
+
totalRecordCount: null,
|
97
|
+
queries: {},
|
98
|
+
queryRecordCount: null,
|
99
|
+
page: null,
|
100
|
+
perPageDefault: 10,
|
101
|
+
perPageOptions: [10,20,50,100],
|
102
|
+
sorts: {},
|
103
|
+
sortsKeys: null,
|
104
|
+
sortTypes: {},
|
105
|
+
records: null
|
106
|
+
},
|
107
|
+
writers: {
|
108
|
+
_rowWriter: defaultRowWriter,
|
109
|
+
_cellWriter: defaultCellWriter,
|
110
|
+
_attributeWriter: defaultAttributeWriter
|
111
|
+
},
|
112
|
+
readers: {
|
113
|
+
_rowReader: null,
|
114
|
+
_attributeReader: defaultAttributeReader
|
115
|
+
},
|
116
|
+
params: {
|
117
|
+
dynatable: 'dynatable',
|
118
|
+
queries: 'queries',
|
119
|
+
sorts: 'sorts',
|
120
|
+
page: 'page',
|
121
|
+
perPage: 'perPage',
|
122
|
+
offset: 'offset',
|
123
|
+
records: 'records',
|
124
|
+
record: null,
|
125
|
+
queryRecordCount: 'queryRecordCount',
|
126
|
+
totalRecordCount: 'totalRecordCount'
|
127
|
+
}
|
128
|
+
};
|
129
|
+
|
130
|
+
//-----------------------------------------------------------------
|
131
|
+
// Each dynatable instance inherits from this,
|
132
|
+
// set properties specific to instance
|
133
|
+
//-----------------------------------------------------------------
|
134
|
+
|
135
|
+
dt = {
|
136
|
+
init: function(element, options) {
|
137
|
+
this.settings = mergeSettings(options);
|
138
|
+
this.element = element;
|
139
|
+
this.$element = $(element);
|
140
|
+
|
141
|
+
// All the setup that doesn't require element or options
|
142
|
+
build.call(this);
|
143
|
+
|
144
|
+
return this;
|
145
|
+
},
|
146
|
+
|
147
|
+
process: function(skipPushState) {
|
148
|
+
processAll.call(this, skipPushState);
|
149
|
+
}
|
150
|
+
};
|
151
|
+
|
152
|
+
//-----------------------------------------------------------------
|
153
|
+
// Cached plugin global functions
|
154
|
+
//-----------------------------------------------------------------
|
155
|
+
|
156
|
+
mergeSettings = function(options) {
|
157
|
+
var newOptions = $.extend(true, {}, defaults, options);
|
158
|
+
|
159
|
+
// TODO: figure out a better way to do this.
|
160
|
+
// Doing `extend(true)` causes any elements that are arrays
|
161
|
+
// to merge the default and options arrays instead of overriding the defaults.
|
162
|
+
if (options) {
|
163
|
+
if (options.inputs) {
|
164
|
+
if (options.inputs.multisort) {
|
165
|
+
newOptions.inputs.multisort = options.inputs.multisort;
|
166
|
+
}
|
167
|
+
if (options.inputs.paginationGap) {
|
168
|
+
newOptions.inputs.paginationGap = options.inputs.paginationGap;
|
169
|
+
}
|
170
|
+
}
|
171
|
+
if (options.dataset && options.dataset.perPageOptions) {
|
172
|
+
newOptions.dataset.perPageOptions = options.dataset.perPageOptions;
|
173
|
+
}
|
174
|
+
}
|
175
|
+
|
176
|
+
return newOptions;
|
177
|
+
};
|
178
|
+
|
179
|
+
build = function() {
|
180
|
+
this.$element.trigger('dynatable:preinit', this);
|
181
|
+
|
182
|
+
for (model in modelPrototypes) {
|
183
|
+
if (modelPrototypes.hasOwnProperty(model)) {
|
184
|
+
var modelInstance = this[model] = new modelPrototypes[model](this, this.settings);
|
185
|
+
if (modelInstance.initOnLoad()) {
|
186
|
+
modelInstance.init();
|
187
|
+
}
|
188
|
+
}
|
189
|
+
}
|
190
|
+
|
191
|
+
this.$element.trigger('dynatable:init', this);
|
192
|
+
|
193
|
+
if (!this.settings.dataset.ajax || (this.settings.dataset.ajax && this.settings.dataset.ajaxOnLoad) || this.settings.features.paginate) {
|
194
|
+
this.process();
|
195
|
+
}
|
196
|
+
};
|
197
|
+
|
198
|
+
processAll = function(skipPushState) {
|
199
|
+
var data = {};
|
200
|
+
|
201
|
+
this.$element.trigger('dynatable:beforeProcess', data);
|
202
|
+
|
203
|
+
if (!$.isEmptyObject(this.settings.dataset.queries)) { data[this.settings.params.queries] = this.settings.dataset.queries; }
|
204
|
+
// TODO: Wrap this in a try/rescue block to hide the processing indicator and indicate something went wrong if error
|
205
|
+
this.processingIndicator.show();
|
206
|
+
|
207
|
+
if (this.settings.features.sort && !$.isEmptyObject(this.settings.dataset.sorts)) { data[this.settings.params.sorts] = this.settings.dataset.sorts; }
|
208
|
+
if (this.settings.features.paginate && this.settings.dataset.page) {
|
209
|
+
var page = this.settings.dataset.page,
|
210
|
+
perPage = this.settings.dataset.perPage;
|
211
|
+
data[this.settings.params.page] = page;
|
212
|
+
data[this.settings.params.perPage] = perPage;
|
213
|
+
data[this.settings.params.offset] = (page - 1) * perPage;
|
214
|
+
}
|
215
|
+
if (this.settings.dataset.ajaxData) { $.extend(data, this.settings.dataset.ajaxData); }
|
216
|
+
|
217
|
+
// If ajax, sends query to ajaxUrl with queries and sorts serialized and appended in ajax data
|
218
|
+
// otherwise, executes queries and sorts on in-page data
|
219
|
+
if (this.settings.dataset.ajax) {
|
220
|
+
var _this = this;
|
221
|
+
var options = {
|
222
|
+
type: _this.settings.dataset.ajaxMethod,
|
223
|
+
dataType: _this.settings.dataset.ajaxDataType,
|
224
|
+
data: data,
|
225
|
+
error: function(xhr, error) {
|
226
|
+
},
|
227
|
+
success: function(response) {
|
228
|
+
_this.$element.trigger('dynatable:ajax:success', response);
|
229
|
+
// Merge ajax results and meta-data into dynatables cached data
|
230
|
+
_this.records.updateFromJson(response);
|
231
|
+
// update table with new records
|
232
|
+
_this.dom.update();
|
233
|
+
|
234
|
+
if (!skipPushState && _this.state.initOnLoad()) {
|
235
|
+
_this.state.push(data);
|
236
|
+
}
|
237
|
+
},
|
238
|
+
complete: function() {
|
239
|
+
_this.processingIndicator.hide();
|
240
|
+
}
|
241
|
+
};
|
242
|
+
// Do not pass url to `ajax` options if blank
|
243
|
+
if (this.settings.dataset.ajaxUrl) {
|
244
|
+
options.url = this.settings.dataset.ajaxUrl;
|
245
|
+
|
246
|
+
// If ajaxUrl is blank, then we're using the current page URL,
|
247
|
+
// we need to strip out any query, sort, or page data controlled by dynatable
|
248
|
+
// that may have been in URL when page loaded, so that it doesn't conflict with
|
249
|
+
// what's passed in with the data ajax parameter
|
250
|
+
} else {
|
251
|
+
options.url = utility.refreshQueryString(window.location.href, {}, this.settings);
|
252
|
+
}
|
253
|
+
if (this.settings.dataset.ajaxCache !== null) { options.cache = this.settings.dataset.ajaxCache; }
|
254
|
+
|
255
|
+
$.ajax(options);
|
256
|
+
} else {
|
257
|
+
this.records.resetOriginal();
|
258
|
+
this.queries.run();
|
259
|
+
if (this.settings.features.sort) {
|
260
|
+
this.records.sort();
|
261
|
+
}
|
262
|
+
if (this.settings.features.paginate) {
|
263
|
+
this.records.paginate();
|
264
|
+
}
|
265
|
+
this.dom.update();
|
266
|
+
this.processingIndicator.hide();
|
267
|
+
|
268
|
+
if (!skipPushState && this.state.initOnLoad()) {
|
269
|
+
this.state.push(data);
|
270
|
+
}
|
271
|
+
}
|
272
|
+
this.$element.trigger('dynatable:afterProcess', data);
|
273
|
+
};
|
274
|
+
|
275
|
+
function defaultRowWriter(rowIndex, record, columns, cellWriter) {
|
276
|
+
var tr = '';
|
277
|
+
|
278
|
+
// grab the record's attribute for each column
|
279
|
+
for (var i = 0, len = columns.length; i < len; i++) {
|
280
|
+
tr += cellWriter(columns[i], record);
|
281
|
+
}
|
282
|
+
|
283
|
+
return '<tr>' + tr + '</tr>';
|
284
|
+
};
|
285
|
+
|
286
|
+
function defaultCellWriter(column, record) {
|
287
|
+
var html = column.attributeWriter(record),
|
288
|
+
td = '<td';
|
289
|
+
|
290
|
+
if (column.hidden || column.textAlign) {
|
291
|
+
td += ' style="';
|
292
|
+
|
293
|
+
// keep cells for hidden column headers hidden
|
294
|
+
if (column.hidden) {
|
295
|
+
td += 'display: none;';
|
296
|
+
}
|
297
|
+
|
298
|
+
// keep cells aligned as their column headers are aligned
|
299
|
+
if (column.textAlign) {
|
300
|
+
td += 'text-align: ' + column.textAlign + ';';
|
301
|
+
}
|
302
|
+
|
303
|
+
td += '"';
|
304
|
+
}
|
305
|
+
|
306
|
+
return td + '>' + html + '</td>';
|
307
|
+
};
|
308
|
+
|
309
|
+
function defaultAttributeWriter(record) {
|
310
|
+
// `this` is the column object in settings.columns
|
311
|
+
// TODO: automatically convert common types, such as arrays and objects, to string
|
312
|
+
return record[this.id];
|
313
|
+
};
|
314
|
+
|
315
|
+
function defaultAttributeReader(cell, record) {
|
316
|
+
return $(cell).html();
|
317
|
+
};
|
318
|
+
|
319
|
+
//-----------------------------------------------------------------
|
320
|
+
// Dynatable object model prototype
|
321
|
+
// (all object models get these default functions)
|
322
|
+
//-----------------------------------------------------------------
|
323
|
+
|
324
|
+
Model = {
|
325
|
+
initOnLoad: function() {
|
326
|
+
return true;
|
327
|
+
},
|
328
|
+
|
329
|
+
init: function() {}
|
330
|
+
};
|
331
|
+
|
332
|
+
for (model in modelPrototypes) {
|
333
|
+
if (modelPrototypes.hasOwnProperty(model)) {
|
334
|
+
var modelPrototype = modelPrototypes[model];
|
335
|
+
modelPrototype.prototype = Model;
|
336
|
+
}
|
337
|
+
}
|
338
|
+
|
339
|
+
//-----------------------------------------------------------------
|
340
|
+
// Dynatable object models
|
341
|
+
//-----------------------------------------------------------------
|
342
|
+
|
343
|
+
function Dom(obj, settings) {
|
344
|
+
var _this = this;
|
345
|
+
|
346
|
+
// update table contents with new records array
|
347
|
+
// from query (whether ajax or not)
|
348
|
+
this.update = function() {
|
349
|
+
var rows = '',
|
350
|
+
columns = settings.table.columns,
|
351
|
+
rowWriter = settings.writers._rowWriter,
|
352
|
+
cellWriter = settings.writers._cellWriter;
|
353
|
+
|
354
|
+
obj.$element.trigger('dynatable:beforeUpdate', rows);
|
355
|
+
|
356
|
+
// loop through records
|
357
|
+
for (var i = 0, len = settings.dataset.records.length; i < len; i++) {
|
358
|
+
var record = settings.dataset.records[i],
|
359
|
+
tr = rowWriter(i, record, columns, cellWriter);
|
360
|
+
rows += tr;
|
361
|
+
}
|
362
|
+
|
363
|
+
// Appended dynatable interactive elements
|
364
|
+
if (settings.features.recordCount) {
|
365
|
+
$('#dynatable-record-count-' + obj.element.id).replaceWith(obj.recordsCount.create());
|
366
|
+
}
|
367
|
+
if (settings.features.paginate) {
|
368
|
+
$('#dynatable-pagination-links-' + obj.element.id).replaceWith(obj.paginationLinks.create());
|
369
|
+
if (settings.features.perPageSelect) {
|
370
|
+
$('#dynatable-per-page-' + obj.element.id).val(parseInt(settings.dataset.perPage));
|
371
|
+
}
|
372
|
+
}
|
373
|
+
|
374
|
+
// Sort headers functionality
|
375
|
+
if (settings.features.sort && columns) {
|
376
|
+
obj.sortsHeaders.removeAllArrows();
|
377
|
+
for (var i = 0, len = columns.length; i < len; i++) {
|
378
|
+
var column = columns[i],
|
379
|
+
sortedByColumn = utility.allMatch(settings.dataset.sorts, column.sorts, function(sorts, sort) { return sort in sorts; }),
|
380
|
+
value = settings.dataset.sorts[column.sorts[0]];
|
381
|
+
|
382
|
+
if (sortedByColumn) {
|
383
|
+
obj.$element.find('[data-dynatable-column="' + column.id + '"]').find('.dynatable-sort-header').each(function(){
|
384
|
+
if (value == 1) {
|
385
|
+
obj.sortsHeaders.appendArrowUp($(this));
|
386
|
+
} else {
|
387
|
+
obj.sortsHeaders.appendArrowDown($(this));
|
388
|
+
}
|
389
|
+
});
|
390
|
+
}
|
391
|
+
}
|
392
|
+
}
|
393
|
+
|
394
|
+
// Query search functionality
|
395
|
+
if (settings.inputs.queries || settings.features.search) {
|
396
|
+
var allQueries = settings.inputs.queries || $();
|
397
|
+
if (settings.features.search) {
|
398
|
+
allQueries = allQueries.add('#dynatable-query-search-' + obj.element.id);
|
399
|
+
}
|
400
|
+
|
401
|
+
allQueries.each(function() {
|
402
|
+
var $this = $(this),
|
403
|
+
q = settings.dataset.queries[$this.data('dynatable-query')];
|
404
|
+
$this.val(q || '');
|
405
|
+
});
|
406
|
+
}
|
407
|
+
|
408
|
+
obj.$element.find(settings.table.bodyRowSelector).remove();
|
409
|
+
obj.$element.append(rows);
|
410
|
+
|
411
|
+
obj.$element.trigger('dynatable:afterUpdate', rows);
|
412
|
+
};
|
413
|
+
};
|
414
|
+
|
415
|
+
function DomColumns(obj, settings) {
|
416
|
+
var _this = this;
|
417
|
+
|
418
|
+
this.initOnLoad = function() {
|
419
|
+
return obj.$element.is('table');
|
420
|
+
};
|
421
|
+
|
422
|
+
this.init = function() {
|
423
|
+
settings.table.columns = [];
|
424
|
+
this.getFromTable();
|
425
|
+
};
|
426
|
+
|
427
|
+
// initialize table[columns] array
|
428
|
+
this.getFromTable = function() {
|
429
|
+
var $columns = obj.$element.find(settings.table.headRowSelector).children('th,td');
|
430
|
+
if ($columns.length) {
|
431
|
+
$columns.each(function(index){
|
432
|
+
_this.add($(this), index, true);
|
433
|
+
});
|
434
|
+
} else {
|
435
|
+
return $.error("Couldn't find any columns headers in '" + settings.table.headRowSelector + " th,td'. If your header row is different, specify the selector in the table: headRowSelector option.");
|
436
|
+
}
|
437
|
+
};
|
438
|
+
|
439
|
+
this.add = function($column, position, skipAppend, skipUpdate) {
|
440
|
+
var columns = settings.table.columns,
|
441
|
+
label = $column.text(),
|
442
|
+
id = $column.data('dynatable-column') || utility.normalizeText(label, settings.table.defaultColumnIdStyle),
|
443
|
+
dataSorts = $column.data('dynatable-sorts'),
|
444
|
+
sorts = dataSorts ? $.map(dataSorts.split(','), function(text) { return $.trim(text); }) : [id];
|
445
|
+
|
446
|
+
// If the column id is blank, generate an id for it
|
447
|
+
if ( !id ) {
|
448
|
+
this.generate($column);
|
449
|
+
id = $column.data('dynatable-column');
|
450
|
+
}
|
451
|
+
// Add column data to plugin instance
|
452
|
+
columns.splice(position, 0, {
|
453
|
+
index: position,
|
454
|
+
label: label,
|
455
|
+
id: id,
|
456
|
+
attributeWriter: settings.writers[id] || settings.writers._attributeWriter,
|
457
|
+
attributeReader: settings.readers[id] || settings.readers._attributeReader,
|
458
|
+
sorts: sorts,
|
459
|
+
hidden: $column.css('display') === 'none',
|
460
|
+
textAlign: $column.css('text-align')
|
461
|
+
});
|
462
|
+
|
463
|
+
// Modify header cell
|
464
|
+
$column
|
465
|
+
.attr('data-dynatable-column', id)
|
466
|
+
.addClass('dynatable-head');
|
467
|
+
if (settings.table.headRowClass) { $column.addClass(settings.table.headRowClass); }
|
468
|
+
|
469
|
+
// Append column header to table
|
470
|
+
if (!skipAppend) {
|
471
|
+
var domPosition = position + 1,
|
472
|
+
$sibling = obj.$element.find(settings.table.headRowSelector)
|
473
|
+
.children('th:nth-child(' + domPosition + '),td:nth-child(' + domPosition + ')').first(),
|
474
|
+
columnsAfter = columns.slice(position + 1, columns.length);
|
475
|
+
|
476
|
+
if ($sibling.length) {
|
477
|
+
$sibling.before($column);
|
478
|
+
// sibling column doesn't yet exist (maybe this is the last column in the header row)
|
479
|
+
} else {
|
480
|
+
obj.$element.find(settings.table.headRowSelector).append($column);
|
481
|
+
}
|
482
|
+
|
483
|
+
obj.sortsHeaders.attachOne($column.get());
|
484
|
+
|
485
|
+
// increment the index of all columns after this one that was just inserted
|
486
|
+
if (columnsAfter.length) {
|
487
|
+
for (var i = 0, len = columnsAfter.length; i < len; i++) {
|
488
|
+
columnsAfter[i].index += 1;
|
489
|
+
}
|
490
|
+
}
|
491
|
+
|
492
|
+
if (!skipUpdate) {
|
493
|
+
obj.dom.update();
|
494
|
+
}
|
495
|
+
}
|
496
|
+
|
497
|
+
return dt;
|
498
|
+
};
|
499
|
+
|
500
|
+
this.remove = function(columnIndexOrId) {
|
501
|
+
var columns = settings.table.columns,
|
502
|
+
length = columns.length;
|
503
|
+
|
504
|
+
if (typeof(columnIndexOrId) === "number") {
|
505
|
+
var column = columns[columnIndexOrId];
|
506
|
+
this.removeFromTable(column.id);
|
507
|
+
this.removeFromArray(columnIndexOrId);
|
508
|
+
} else {
|
509
|
+
// Traverse columns array in reverse order so that subsequent indices
|
510
|
+
// don't get messed up when we delete an item from the array in an iteration
|
511
|
+
for (var i = columns.length - 1; i >= 0; i--) {
|
512
|
+
var column = columns[i];
|
513
|
+
|
514
|
+
if (column.id === columnIndexOrId) {
|
515
|
+
this.removeFromTable(columnIndexOrId);
|
516
|
+
this.removeFromArray(i);
|
517
|
+
}
|
518
|
+
}
|
519
|
+
}
|
520
|
+
|
521
|
+
obj.dom.update();
|
522
|
+
};
|
523
|
+
|
524
|
+
this.removeFromTable = function(columnId) {
|
525
|
+
obj.$element.find(settings.table.headRowSelector).children('[data-dynatable-column="' + columnId + '"]').first()
|
526
|
+
.remove();
|
527
|
+
};
|
528
|
+
|
529
|
+
this.removeFromArray = function(index) {
|
530
|
+
var columns = settings.table.columns,
|
531
|
+
adjustColumns;
|
532
|
+
columns.splice(index, 1);
|
533
|
+
adjustColumns = columns.slice(index, columns.length);
|
534
|
+
for (var i = 0, len = adjustColumns.length; i < len; i++) {
|
535
|
+
adjustColumns[i].index -= 1;
|
536
|
+
}
|
537
|
+
};
|
538
|
+
|
539
|
+
this.generate = function($cell) {
|
540
|
+
var cell = $cell === undefined ? $('<th></th>') : $cell;
|
541
|
+
return this.attachGeneratedAttributes(cell);
|
542
|
+
};
|
543
|
+
|
544
|
+
this.attachGeneratedAttributes = function($cell) {
|
545
|
+
// Use increment to create unique column name that is the same each time the page is reloaded,
|
546
|
+
// in order to avoid errors with mismatched attribute names when loading cached `dataset.records` array
|
547
|
+
var increment = obj.$element.find(settings.table.headRowSelector).children('th[data-dynatable-generated]').length;
|
548
|
+
return $cell
|
549
|
+
.attr('data-dynatable-column', 'dynatable-generated-' + increment) //+ utility.randomHash(),
|
550
|
+
.attr('data-dynatable-no-sort', 'true')
|
551
|
+
.attr('data-dynatable-generated', increment);
|
552
|
+
};
|
553
|
+
};
|
554
|
+
|
555
|
+
function Records(obj, settings) {
|
556
|
+
var _this = this;
|
557
|
+
|
558
|
+
this.initOnLoad = function() {
|
559
|
+
return !settings.dataset.ajax;
|
560
|
+
};
|
561
|
+
|
562
|
+
this.init = function() {
|
563
|
+
if (settings.dataset.records === null) {
|
564
|
+
settings.dataset.records = this.getFromTable();
|
565
|
+
|
566
|
+
if (!settings.dataset.queryRecordCount) {
|
567
|
+
settings.dataset.queryRecordCount = this.count();
|
568
|
+
}
|
569
|
+
|
570
|
+
if (!settings.dataset.totalRecordCount){
|
571
|
+
settings.dataset.totalRecordCount = settings.dataset.queryRecordCount;
|
572
|
+
}
|
573
|
+
}
|
574
|
+
|
575
|
+
// Create cache of original full recordset (unpaginated and unqueried)
|
576
|
+
settings.dataset.originalRecords = $.extend(true, [], settings.dataset.records);
|
577
|
+
};
|
578
|
+
|
579
|
+
// merge ajax response json with cached data including
|
580
|
+
// meta-data and records
|
581
|
+
this.updateFromJson = function(data) {
|
582
|
+
var records;
|
583
|
+
if (settings.params.records === "_root") {
|
584
|
+
records = data;
|
585
|
+
} else if (settings.params.records in data) {
|
586
|
+
records = data[settings.params.records];
|
587
|
+
}
|
588
|
+
if (settings.params.record) {
|
589
|
+
var len = records.length - 1;
|
590
|
+
for (var i = 0; i < len; i++) {
|
591
|
+
records[i] = records[i][settings.params.record];
|
592
|
+
}
|
593
|
+
}
|
594
|
+
if (settings.params.queryRecordCount in data) {
|
595
|
+
settings.dataset.queryRecordCount = data[settings.params.queryRecordCount];
|
596
|
+
}
|
597
|
+
if (settings.params.totalRecordCount in data) {
|
598
|
+
settings.dataset.totalRecordCount = data[settings.params.totalRecordCount];
|
599
|
+
}
|
600
|
+
settings.dataset.records = records;
|
601
|
+
};
|
602
|
+
|
603
|
+
// For really advanced sorting,
|
604
|
+
// see http://james.padolsey.com/javascript/sorting-elements-with-jquery/
|
605
|
+
this.sort = function() {
|
606
|
+
var sort = [].sort,
|
607
|
+
sorts = settings.dataset.sorts,
|
608
|
+
sortsKeys = settings.dataset.sortsKeys,
|
609
|
+
sortTypes = settings.dataset.sortTypes;
|
610
|
+
|
611
|
+
var sortFunction = function(a, b) {
|
612
|
+
var comparison;
|
613
|
+
if ($.isEmptyObject(sorts)) {
|
614
|
+
comparison = obj.sorts.functions['originalPlacement'](a, b);
|
615
|
+
} else {
|
616
|
+
for (var i = 0, len = sortsKeys.length; i < len; i++) {
|
617
|
+
var attr = sortsKeys[i],
|
618
|
+
direction = sorts[attr],
|
619
|
+
sortType = sortTypes[attr] || obj.sorts.guessType(a, b, attr);
|
620
|
+
comparison = obj.sorts.functions[sortType](a, b, attr, direction);
|
621
|
+
// Don't need to sort any further unless this sort is a tie between a and b,
|
622
|
+
// so break the for loop unless tied
|
623
|
+
if (comparison !== 0) { break; }
|
624
|
+
}
|
625
|
+
}
|
626
|
+
return comparison;
|
627
|
+
}
|
628
|
+
|
629
|
+
return sort.call(settings.dataset.records, sortFunction);
|
630
|
+
};
|
631
|
+
|
632
|
+
this.paginate = function() {
|
633
|
+
var bounds = this.pageBounds(),
|
634
|
+
first = bounds[0], last = bounds[1];
|
635
|
+
settings.dataset.records = settings.dataset.records.slice(first, last);
|
636
|
+
};
|
637
|
+
|
638
|
+
this.resetOriginal = function() {
|
639
|
+
settings.dataset.records = settings.dataset.originalRecords || [];
|
640
|
+
};
|
641
|
+
|
642
|
+
this.pageBounds = function() {
|
643
|
+
var page = settings.dataset.page || 1,
|
644
|
+
first = (page - 1) * settings.dataset.perPage,
|
645
|
+
last = Math.min(first + settings.dataset.perPage, settings.dataset.queryRecordCount);
|
646
|
+
return [first,last];
|
647
|
+
};
|
648
|
+
|
649
|
+
// get initial recordset to populate table
|
650
|
+
// if ajax, call ajaxUrl
|
651
|
+
// otherwise, initialize from in-table records
|
652
|
+
this.getFromTable = function() {
|
653
|
+
var records = [],
|
654
|
+
columns = settings.table.columns,
|
655
|
+
tableRecords = obj.$element.find(settings.table.bodyRowSelector);
|
656
|
+
|
657
|
+
tableRecords.each(function(index){
|
658
|
+
var record = {};
|
659
|
+
record['dynatable-original-index'] = index;
|
660
|
+
$(this).find('th,td').each(function(index) {
|
661
|
+
if (columns[index] === undefined) {
|
662
|
+
// Header cell didn't exist for this column, so let's generate and append
|
663
|
+
// a new header cell with a randomly generated name (so we can store and
|
664
|
+
// retrieve the contents of this column for each record)
|
665
|
+
obj.domColumns.add(obj.domColumns.generate(), columns.length, false, true); // don't skipAppend, do skipUpdate
|
666
|
+
}
|
667
|
+
var value = columns[index].attributeReader(this, record),
|
668
|
+
attr = columns[index].id;
|
669
|
+
|
670
|
+
// If value from table is HTML, let's get and cache the text equivalent for
|
671
|
+
// the default string sorting, since it rarely makes sense for sort headers
|
672
|
+
// to sort based on HTML tags.
|
673
|
+
if (typeof(value) === "string" && value.match(/\s*\<.+\>/)) {
|
674
|
+
if (! record['dynatable-sortable-text']) {
|
675
|
+
record['dynatable-sortable-text'] = {};
|
676
|
+
}
|
677
|
+
record['dynatable-sortable-text'][attr] = $.trim($('<div></div>').html(value).text());
|
678
|
+
}
|
679
|
+
|
680
|
+
record[attr] = value;
|
681
|
+
});
|
682
|
+
// Allow configuration function which alters record based on attributes of
|
683
|
+
// table row (e.g. from html5 data- attributes)
|
684
|
+
if (typeof(settings.readers._rowReader) === "function") {
|
685
|
+
settings.readers._rowReader(index, this, record);
|
686
|
+
}
|
687
|
+
records.push(record);
|
688
|
+
});
|
689
|
+
return records; // 1st row is header
|
690
|
+
};
|
691
|
+
|
692
|
+
// count records from table
|
693
|
+
this.count = function() {
|
694
|
+
return settings.dataset.records.length;
|
695
|
+
};
|
696
|
+
};
|
697
|
+
|
698
|
+
function RecordsCount(obj, settings) {
|
699
|
+
this.initOnLoad = function() {
|
700
|
+
return settings.features.recordCount;
|
701
|
+
};
|
702
|
+
|
703
|
+
this.init = function() {
|
704
|
+
this.attach();
|
705
|
+
};
|
706
|
+
|
707
|
+
this.create = function() {
|
708
|
+
var recordsShown = obj.records.count(),
|
709
|
+
recordsQueryCount = settings.dataset.queryRecordCount,
|
710
|
+
recordsTotal = settings.dataset.totalRecordCount,
|
711
|
+
text = settings.inputs.recordCountText,
|
712
|
+
collection_name = settings.params.records;
|
713
|
+
|
714
|
+
if (recordsShown < recordsQueryCount && settings.features.paginate) {
|
715
|
+
var bounds = obj.records.pageBounds();
|
716
|
+
text += "<span class='dynatable-record-bounds'>" + (bounds[0] + 1) + " to " + bounds[1] + "</span> of ";
|
717
|
+
} else if (recordsShown === recordsQueryCount && settings.features.paginate) {
|
718
|
+
text += recordsShown + " of ";
|
719
|
+
}
|
720
|
+
text += recordsQueryCount + " " + collection_name;
|
721
|
+
if (recordsQueryCount < recordsTotal) {
|
722
|
+
text += " (filtered from " + recordsTotal + " total records)";
|
723
|
+
}
|
724
|
+
|
725
|
+
return $('<span></span>', {
|
726
|
+
id: 'dynatable-record-count-' + obj.element.id,
|
727
|
+
'class': 'dynatable-record-count',
|
728
|
+
html: text
|
729
|
+
});
|
730
|
+
};
|
731
|
+
|
732
|
+
this.attach = function() {
|
733
|
+
var $target = settings.inputs.recordCountTarget ? $(settings.inputs.recordCountTarget) : obj.$element;
|
734
|
+
$target[settings.inputs.recordCountPlacement](this.create());
|
735
|
+
};
|
736
|
+
};
|
737
|
+
|
738
|
+
function ProcessingIndicator(obj, settings) {
|
739
|
+
this.init = function() {
|
740
|
+
this.attach();
|
741
|
+
};
|
742
|
+
|
743
|
+
this.create = function() {
|
744
|
+
var $processing = $('<div></div>', {
|
745
|
+
html: '<span>' + settings.inputs.processingText + '</span>',
|
746
|
+
id: 'dynatable-processing-' + obj.element.id,
|
747
|
+
'class': 'dynatable-processing',
|
748
|
+
style: 'position: absolute; display: none;'
|
749
|
+
});
|
750
|
+
|
751
|
+
return $processing;
|
752
|
+
};
|
753
|
+
|
754
|
+
this.position = function() {
|
755
|
+
var $processing = $('#dynatable-processing-' + obj.element.id),
|
756
|
+
$span = $processing.children('span'),
|
757
|
+
spanHeight = $span.outerHeight(),
|
758
|
+
spanWidth = $span.outerWidth(),
|
759
|
+
$covered = obj.$element,
|
760
|
+
offset = $covered.offset(),
|
761
|
+
height = $covered.outerHeight(), width = $covered.outerWidth();
|
762
|
+
|
763
|
+
$processing
|
764
|
+
.offset({left: offset.left, top: offset.top})
|
765
|
+
.width(width)
|
766
|
+
.height(height)
|
767
|
+
$span
|
768
|
+
.offset({left: offset.left + ( (width - spanWidth) / 2 ), top: offset.top + ( (height - spanHeight) / 2 )});
|
769
|
+
|
770
|
+
return $processing;
|
771
|
+
};
|
772
|
+
|
773
|
+
this.attach = function() {
|
774
|
+
obj.$element.before(this.create());
|
775
|
+
};
|
776
|
+
|
777
|
+
this.show = function() {
|
778
|
+
$('#dynatable-processing-' + obj.element.id).show();
|
779
|
+
this.position();
|
780
|
+
};
|
781
|
+
|
782
|
+
this.hide = function() {
|
783
|
+
$('#dynatable-processing-' + obj.element.id).hide();
|
784
|
+
};
|
785
|
+
};
|
786
|
+
|
787
|
+
function State(obj, settings) {
|
788
|
+
this.initOnLoad = function() {
|
789
|
+
// Check if pushState option is true, and if browser supports it
|
790
|
+
return settings.features.pushState && history.pushState;
|
791
|
+
};
|
792
|
+
|
793
|
+
this.init = function() {
|
794
|
+
window.onpopstate = function(event) {
|
795
|
+
if (event.state && event.state.dynatable) {
|
796
|
+
obj.state.pop(event);
|
797
|
+
}
|
798
|
+
}
|
799
|
+
};
|
800
|
+
|
801
|
+
this.push = function(data) {
|
802
|
+
var urlString = window.location.search,
|
803
|
+
urlOptions,
|
804
|
+
path,
|
805
|
+
params,
|
806
|
+
hash,
|
807
|
+
newParams,
|
808
|
+
cacheStr,
|
809
|
+
cache,
|
810
|
+
// replaceState on initial load, then pushState after that
|
811
|
+
firstPush = !(window.history.state && window.history.state.dynatable),
|
812
|
+
pushFunction = firstPush ? 'replaceState' : 'pushState';
|
813
|
+
|
814
|
+
if (urlString && /^\?/.test(urlString)) { urlString = urlString.substring(1); }
|
815
|
+
$.extend(urlOptions, data);
|
816
|
+
|
817
|
+
params = utility.refreshQueryString(urlString, data, settings);
|
818
|
+
if (params) { params = '?' + params; }
|
819
|
+
hash = window.location.hash;
|
820
|
+
path = window.location.pathname;
|
821
|
+
|
822
|
+
obj.$element.trigger('dynatable:push', data);
|
823
|
+
|
824
|
+
cache = { dynatable: { dataset: settings.dataset } };
|
825
|
+
if (!firstPush) { cache.dynatable.scrollTop = $(window).scrollTop(); }
|
826
|
+
cacheStr = JSON.stringify(cache);
|
827
|
+
|
828
|
+
// Mozilla has a 640k char limit on what can be stored in pushState.
|
829
|
+
// See "limit" in https://developer.mozilla.org/en/DOM/Manipulating_the_browser_history#The_pushState().C2.A0method
|
830
|
+
// and "dataStr.length" in http://wine.git.sourceforge.net/git/gitweb.cgi?p=wine/wine-gecko;a=patch;h=43a11bdddc5fc1ff102278a120be66a7b90afe28
|
831
|
+
//
|
832
|
+
// Likewise, other browsers may have varying (undocumented) limits.
|
833
|
+
// Also, Firefox's limit can be changed in about:config as browser.history.maxStateObjectSize
|
834
|
+
// Since we don't know what the actual limit will be in any given situation, we'll just try caching and rescue
|
835
|
+
// any exceptions by retrying pushState without caching the records.
|
836
|
+
//
|
837
|
+
// I have absolutely no idea why perPageOptions suddenly becomes an array-like object instead of an array,
|
838
|
+
// but just recently, this started throwing an error if I don't convert it:
|
839
|
+
// 'Uncaught Error: DATA_CLONE_ERR: DOM Exception 25'
|
840
|
+
cache.dynatable.dataset.perPageOptions = $.makeArray(cache.dynatable.dataset.perPageOptions);
|
841
|
+
|
842
|
+
try {
|
843
|
+
window.history[pushFunction](cache, "Dynatable state", path + params + hash);
|
844
|
+
} catch(error) {
|
845
|
+
// Make cached records = null, so that `pop` will rerun process to retrieve records
|
846
|
+
cache.dynatable.dataset.records = null;
|
847
|
+
window.history[pushFunction](cache, "Dynatable state", path + params + hash);
|
848
|
+
}
|
849
|
+
};
|
850
|
+
|
851
|
+
this.pop = function(event) {
|
852
|
+
var data = event.state.dynatable;
|
853
|
+
settings.dataset = data.dataset;
|
854
|
+
|
855
|
+
if (data.scrollTop) { $(window).scrollTop(data.scrollTop); }
|
856
|
+
|
857
|
+
// If dataset.records is cached from pushState
|
858
|
+
if ( data.dataset.records ) {
|
859
|
+
obj.dom.update();
|
860
|
+
} else {
|
861
|
+
obj.process(true);
|
862
|
+
}
|
863
|
+
};
|
864
|
+
};
|
865
|
+
|
866
|
+
function Sorts(obj, settings) {
|
867
|
+
this.initOnLoad = function() {
|
868
|
+
return settings.features.sort;
|
869
|
+
};
|
870
|
+
|
871
|
+
this.init = function() {
|
872
|
+
var sortsUrl = window.location.search.match(new RegExp(settings.params.sorts + '[^&=]*=[^&]*', 'g'));
|
873
|
+
settings.dataset.sorts = sortsUrl ? utility.deserialize(sortsUrl)[settings.params.sorts] : {};
|
874
|
+
settings.dataset.sortsKeys = sortsUrl ? utility.keysFromObject(settings.dataset.sorts) : [];
|
875
|
+
};
|
876
|
+
|
877
|
+
this.add = function(attr, direction) {
|
878
|
+
var sortsKeys = settings.dataset.sortsKeys,
|
879
|
+
index = $.inArray(attr, sortsKeys);
|
880
|
+
settings.dataset.sorts[attr] = direction;
|
881
|
+
if (index === -1) { sortsKeys.push(attr); }
|
882
|
+
return dt;
|
883
|
+
};
|
884
|
+
|
885
|
+
this.remove = function(attr) {
|
886
|
+
var sortsKeys = settings.dataset.sortsKeys,
|
887
|
+
index = $.inArray(attr, sortsKeys);
|
888
|
+
delete settings.dataset.sorts[attr];
|
889
|
+
if (index !== -1) { sortsKeys.splice(index, 1); }
|
890
|
+
return dt;
|
891
|
+
};
|
892
|
+
|
893
|
+
this.clear = function() {
|
894
|
+
settings.dataset.sorts = {};
|
895
|
+
settings.dataset.sortsKeys.length = 0;
|
896
|
+
};
|
897
|
+
|
898
|
+
// Try to intelligently guess which sort function to use
|
899
|
+
// based on the type of attribute values.
|
900
|
+
// Consider using something more robust than `typeof` (http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/)
|
901
|
+
this.guessType = function(a, b, attr) {
|
902
|
+
var types = {
|
903
|
+
string: 'string',
|
904
|
+
number: 'number',
|
905
|
+
'boolean': 'number',
|
906
|
+
object: 'number' // dates and null values are also objects, this works...
|
907
|
+
},
|
908
|
+
attrType = a[attr] ? typeof(a[attr]) : typeof(b[attr]),
|
909
|
+
type = types[attrType] || 'number';
|
910
|
+
return type;
|
911
|
+
};
|
912
|
+
|
913
|
+
// Built-in sort functions
|
914
|
+
// (the most common use-cases I could think of)
|
915
|
+
this.functions = {
|
916
|
+
number: function(a, b, attr, direction) {
|
917
|
+
return a[attr] === b[attr] ? 0 : (direction > 0 ? a[attr] - b[attr] : b[attr] - a[attr]);
|
918
|
+
},
|
919
|
+
string: function(a, b, attr, direction) {
|
920
|
+
var aAttr = (a['dynatable-sortable-text'] && a['dynatable-sortable-text'][attr]) ? a['dynatable-sortable-text'][attr] : a[attr],
|
921
|
+
bAttr = (b['dynatable-sortable-text'] && b['dynatable-sortable-text'][attr]) ? b['dynatable-sortable-text'][attr] : b[attr],
|
922
|
+
comparison;
|
923
|
+
aAttr = aAttr.toLowerCase();
|
924
|
+
bAttr = bAttr.toLowerCase();
|
925
|
+
comparison = aAttr === bAttr ? 0 : (direction > 0 ? aAttr > bAttr : bAttr > aAttr);
|
926
|
+
// force false boolean value to -1, true to 1, and tie to 0
|
927
|
+
return comparison === false ? -1 : (comparison - 0);
|
928
|
+
},
|
929
|
+
originalPlacement: function(a, b) {
|
930
|
+
return a['dynatable-original-index'] - b['dynatable-original-index'];
|
931
|
+
}
|
932
|
+
};
|
933
|
+
};
|
934
|
+
|
935
|
+
// turn table headers into links which add sort to sorts array
|
936
|
+
function SortsHeaders(obj, settings) {
|
937
|
+
var _this = this;
|
938
|
+
|
939
|
+
this.initOnLoad = function() {
|
940
|
+
return settings.features.sort;
|
941
|
+
};
|
942
|
+
|
943
|
+
this.init = function() {
|
944
|
+
this.attach();
|
945
|
+
};
|
946
|
+
|
947
|
+
this.create = function(cell) {
|
948
|
+
var $cell = $(cell),
|
949
|
+
$link = $('<a></a>', {
|
950
|
+
'class': 'dynatable-sort-header',
|
951
|
+
href: '#',
|
952
|
+
html: $cell.html()
|
953
|
+
}),
|
954
|
+
id = $cell.data('dynatable-column'),
|
955
|
+
column = utility.findObjectInArray(settings.table.columns, {id: id});
|
956
|
+
|
957
|
+
$link.bind('click', function(e) {
|
958
|
+
_this.toggleSort(e, $link, column);
|
959
|
+
obj.process();
|
960
|
+
|
961
|
+
e.preventDefault();
|
962
|
+
});
|
963
|
+
|
964
|
+
if (this.sortedByColumn($link, column)) {
|
965
|
+
if (this.sortedByColumnValue(column) == 1) {
|
966
|
+
this.appendArrowUp($link);
|
967
|
+
} else {
|
968
|
+
this.appendArrowDown($link);
|
969
|
+
}
|
970
|
+
}
|
971
|
+
|
972
|
+
return $link;
|
973
|
+
};
|
974
|
+
|
975
|
+
this.removeAll = function() {
|
976
|
+
obj.$element.find(settings.table.headRowSelector).children('th,td').each(function(){
|
977
|
+
_this.removeAllArrows();
|
978
|
+
_this.removeOne(this);
|
979
|
+
});
|
980
|
+
};
|
981
|
+
|
982
|
+
this.removeOne = function(cell) {
|
983
|
+
var $cell = $(cell),
|
984
|
+
$link = $cell.find('.dynatable-sort-header');
|
985
|
+
if ($link.length) {
|
986
|
+
var html = $link.html();
|
987
|
+
$link.remove();
|
988
|
+
$cell.html($cell.html() + html);
|
989
|
+
}
|
990
|
+
};
|
991
|
+
|
992
|
+
this.attach = function() {
|
993
|
+
obj.$element.find(settings.table.headRowSelector).children('th,td').each(function(){
|
994
|
+
_this.attachOne(this);
|
995
|
+
});
|
996
|
+
};
|
997
|
+
|
998
|
+
this.attachOne = function(cell) {
|
999
|
+
var $cell = $(cell);
|
1000
|
+
if (!$cell.data('dynatable-no-sort')) {
|
1001
|
+
$cell.html(this.create(cell));
|
1002
|
+
}
|
1003
|
+
};
|
1004
|
+
|
1005
|
+
this.appendArrowUp = function($link) {
|
1006
|
+
this.removeArrow($link);
|
1007
|
+
$link.append("<span class='dynatable-arrow'> ▲</span>");
|
1008
|
+
};
|
1009
|
+
|
1010
|
+
this.appendArrowDown = function($link) {
|
1011
|
+
this.removeArrow($link);
|
1012
|
+
$link.append("<span class='dynatable-arrow'> ▼</span>");
|
1013
|
+
};
|
1014
|
+
|
1015
|
+
this.removeArrow = function($link) {
|
1016
|
+
// Not sure why `parent()` is needed, the arrow should be inside the link from `append()` above
|
1017
|
+
$link.find('.dynatable-arrow').remove();
|
1018
|
+
};
|
1019
|
+
|
1020
|
+
this.removeAllArrows = function() {
|
1021
|
+
obj.$element.find('.dynatable-arrow').remove();
|
1022
|
+
};
|
1023
|
+
|
1024
|
+
this.toggleSort = function(e, $link, column) {
|
1025
|
+
var sortedByColumn = this.sortedByColumn($link, column),
|
1026
|
+
value = this.sortedByColumnValue(column);
|
1027
|
+
// Clear existing sorts unless this is a multisort event
|
1028
|
+
if (!settings.inputs.multisort || !utility.anyMatch(e, settings.inputs.multisort, function(evt, key) { return e[key]; })) {
|
1029
|
+
this.removeAllArrows();
|
1030
|
+
obj.sorts.clear();
|
1031
|
+
}
|
1032
|
+
|
1033
|
+
// If sorts for this column are already set
|
1034
|
+
if (sortedByColumn) {
|
1035
|
+
// If ascending, then make descending
|
1036
|
+
if (value == 1) {
|
1037
|
+
for (var i = 0, len = column.sorts.length; i < len; i++) {
|
1038
|
+
obj.sorts.add(column.sorts[i], -1);
|
1039
|
+
}
|
1040
|
+
this.appendArrowDown($link);
|
1041
|
+
// If descending, remove sort
|
1042
|
+
} else {
|
1043
|
+
for (var i = 0, len = column.sorts.length; i < len; i++) {
|
1044
|
+
obj.sorts.remove(column.sorts[i]);
|
1045
|
+
}
|
1046
|
+
this.removeArrow($link);
|
1047
|
+
}
|
1048
|
+
// Otherwise, if not already set, set to ascending
|
1049
|
+
} else {
|
1050
|
+
for (var i = 0, len = column.sorts.length; i < len; i++) {
|
1051
|
+
obj.sorts.add(column.sorts[i], 1);
|
1052
|
+
}
|
1053
|
+
this.appendArrowUp($link);
|
1054
|
+
}
|
1055
|
+
};
|
1056
|
+
|
1057
|
+
this.sortedByColumn = function($link, column) {
|
1058
|
+
return utility.allMatch(settings.dataset.sorts, column.sorts, function(sorts, sort) { return sort in sorts; });
|
1059
|
+
};
|
1060
|
+
|
1061
|
+
this.sortedByColumnValue = function(column) {
|
1062
|
+
return settings.dataset.sorts[column.sorts[0]];
|
1063
|
+
};
|
1064
|
+
};
|
1065
|
+
|
1066
|
+
function Queries(obj, settings) {
|
1067
|
+
var _this = this;
|
1068
|
+
|
1069
|
+
this.initOnLoad = function() {
|
1070
|
+
return settings.inputs.queries || settings.features.search;
|
1071
|
+
};
|
1072
|
+
|
1073
|
+
this.init = function() {
|
1074
|
+
var queriesUrl = window.location.search.match(new RegExp(settings.params.queries + '[^&=]*=[^&]*', 'g'));
|
1075
|
+
|
1076
|
+
settings.dataset.queries = queriesUrl ? utility.deserialize(queriesUrl)[settings.params.queries] : {};
|
1077
|
+
if (settings.dataset.queries === "") { settings.dataset.queries = {}; }
|
1078
|
+
|
1079
|
+
if (settings.inputs.queries) {
|
1080
|
+
this.setupInputs();
|
1081
|
+
}
|
1082
|
+
};
|
1083
|
+
|
1084
|
+
this.add = function(name, value) {
|
1085
|
+
// reset to first page since query will change records
|
1086
|
+
if (settings.features.paginate) {
|
1087
|
+
settings.dataset.page = 1;
|
1088
|
+
}
|
1089
|
+
settings.dataset.queries[name] = value;
|
1090
|
+
return dt;
|
1091
|
+
};
|
1092
|
+
|
1093
|
+
this.remove = function(name) {
|
1094
|
+
delete settings.dataset.queries[name];
|
1095
|
+
return dt;
|
1096
|
+
};
|
1097
|
+
|
1098
|
+
this.run = function() {
|
1099
|
+
for (query in settings.dataset.queries) {
|
1100
|
+
if (settings.dataset.queries.hasOwnProperty(query)) {
|
1101
|
+
var value = settings.dataset.queries[query];
|
1102
|
+
if (_this.functions[query] === undefined) {
|
1103
|
+
// Try to lazily evaluate query from column names if not explicitly defined
|
1104
|
+
var queryColumn = utility.findObjectInArray(settings.table.columns, {id: query});
|
1105
|
+
if (queryColumn) {
|
1106
|
+
_this.functions[query] = function(record, queryValue) {
|
1107
|
+
return record[query] == queryValue;
|
1108
|
+
};
|
1109
|
+
} else {
|
1110
|
+
$.error("Query named '" + query + "' called, but not defined in queries.functions");
|
1111
|
+
continue; // to skip to next query
|
1112
|
+
}
|
1113
|
+
}
|
1114
|
+
// collect all records that return true for query
|
1115
|
+
settings.dataset.records = $.map(settings.dataset.records, function(record) {
|
1116
|
+
return _this.functions[query](record, value) ? record : null;
|
1117
|
+
});
|
1118
|
+
}
|
1119
|
+
}
|
1120
|
+
settings.dataset.queryRecordCount = obj.records.count();
|
1121
|
+
};
|
1122
|
+
|
1123
|
+
// Shortcut for performing simple query from built-in search
|
1124
|
+
this.runSearch = function(q) {
|
1125
|
+
var origQueries = $.extend({}, settings.dataset.queries);
|
1126
|
+
if (q) {
|
1127
|
+
this.add('search', q);
|
1128
|
+
} else {
|
1129
|
+
this.remove('search');
|
1130
|
+
}
|
1131
|
+
if (!utility.objectsEqual(settings.dataset.queries, origQueries)) {
|
1132
|
+
obj.process();
|
1133
|
+
}
|
1134
|
+
};
|
1135
|
+
|
1136
|
+
this.setupInputs = function() {
|
1137
|
+
settings.inputs.queries.each(function() {
|
1138
|
+
var $this = $(this),
|
1139
|
+
event = $this.data('dynatable-query-event') || settings.inputs.queryEvent,
|
1140
|
+
query = $this.data('dynatable-query') || $this.attr('name') || this.id,
|
1141
|
+
queryFunction = function(e) {
|
1142
|
+
var q = $(this).val();
|
1143
|
+
if (q === "") { q = undefined; }
|
1144
|
+
if (q === settings.dataset.queries[query]) { return false; }
|
1145
|
+
if (q) {
|
1146
|
+
_this.add(query, q);
|
1147
|
+
} else {
|
1148
|
+
_this.remove(query);
|
1149
|
+
}
|
1150
|
+
obj.process();
|
1151
|
+
e.preventDefault();
|
1152
|
+
};
|
1153
|
+
|
1154
|
+
$this
|
1155
|
+
.attr('data-dynatable-query', query)
|
1156
|
+
.bind(event, queryFunction)
|
1157
|
+
.bind('keypress', function(e) {
|
1158
|
+
if (e.which == 13) {
|
1159
|
+
queryFunction.call(this, e);
|
1160
|
+
}
|
1161
|
+
});
|
1162
|
+
|
1163
|
+
if (settings.dataset.queries[query]) { $this.val(decodeURIComponent(settings.dataset.queries[query])); }
|
1164
|
+
});
|
1165
|
+
};
|
1166
|
+
|
1167
|
+
// Query functions for in-page querying
|
1168
|
+
// each function should take a record and a value as input
|
1169
|
+
// and output true of false as to whether the record is a match or not
|
1170
|
+
this.functions = {
|
1171
|
+
search: function(record, queryValue) {
|
1172
|
+
var contains = false;
|
1173
|
+
// Loop through each attribute of record
|
1174
|
+
for (attr in record) {
|
1175
|
+
if (record.hasOwnProperty(attr)) {
|
1176
|
+
var attrValue = record[attr];
|
1177
|
+
if (typeof(attrValue) === "string" && attrValue.toLowerCase().indexOf(queryValue.toLowerCase()) !== -1) {
|
1178
|
+
contains = true;
|
1179
|
+
// Don't need to keep searching attributes once found
|
1180
|
+
break;
|
1181
|
+
} else {
|
1182
|
+
continue;
|
1183
|
+
}
|
1184
|
+
}
|
1185
|
+
}
|
1186
|
+
return contains;
|
1187
|
+
}
|
1188
|
+
};
|
1189
|
+
};
|
1190
|
+
|
1191
|
+
function InputsSearch(obj, settings) {
|
1192
|
+
var _this = this;
|
1193
|
+
|
1194
|
+
this.initOnLoad = function() {
|
1195
|
+
return settings.features.search;
|
1196
|
+
};
|
1197
|
+
|
1198
|
+
this.init = function() {
|
1199
|
+
this.attach();
|
1200
|
+
};
|
1201
|
+
|
1202
|
+
this.create = function() {
|
1203
|
+
var $search = $('<input />', {
|
1204
|
+
type: 'search',
|
1205
|
+
id: 'dynatable-query-search-' + obj.element.id,
|
1206
|
+
'data-dynatable-query': 'search',
|
1207
|
+
value: settings.dataset.queries.search
|
1208
|
+
}),
|
1209
|
+
$searchSpan = $('<span></span>', {
|
1210
|
+
id: 'dynatable-search-' + obj.element.id,
|
1211
|
+
'class': 'dynatable-search',
|
1212
|
+
text: 'Search: '
|
1213
|
+
}).append($search);
|
1214
|
+
|
1215
|
+
$search
|
1216
|
+
.bind(settings.inputs.queryEvent, function() {
|
1217
|
+
obj.queries.runSearch($(this).val());
|
1218
|
+
})
|
1219
|
+
.bind('keypress', function(e) {
|
1220
|
+
if (e.which == 13) {
|
1221
|
+
obj.queries.runSearch($(this).val());
|
1222
|
+
e.preventDefault();
|
1223
|
+
}
|
1224
|
+
});
|
1225
|
+
return $searchSpan;
|
1226
|
+
};
|
1227
|
+
|
1228
|
+
this.attach = function() {
|
1229
|
+
var $target = settings.inputs.searchTarget ? $(settings.inputs.searchTarget) : obj.$element;
|
1230
|
+
$target[settings.inputs.searchPlacement](this.create());
|
1231
|
+
};
|
1232
|
+
};
|
1233
|
+
|
1234
|
+
// provide a public function for selecting page
|
1235
|
+
function PaginationPage(obj, settings) {
|
1236
|
+
this.initOnLoad = function() {
|
1237
|
+
return settings.features.paginate;
|
1238
|
+
};
|
1239
|
+
|
1240
|
+
this.init = function() {
|
1241
|
+
var pageUrl = window.location.search.match(new RegExp(settings.params.page + '=([^&]*)'));
|
1242
|
+
// If page is present in URL parameters and pushState is enabled
|
1243
|
+
// (meaning that it'd be possible for dynatable to have put the
|
1244
|
+
// page parameter in the URL)
|
1245
|
+
if (pageUrl && settings.features.pushState) {
|
1246
|
+
this.set(pageUrl[1]);
|
1247
|
+
} else {
|
1248
|
+
this.set(1);
|
1249
|
+
}
|
1250
|
+
};
|
1251
|
+
|
1252
|
+
this.set = function(page) {
|
1253
|
+
settings.dataset.page = parseInt(page, 10);
|
1254
|
+
}
|
1255
|
+
};
|
1256
|
+
|
1257
|
+
function PaginationPerPage(obj, settings) {
|
1258
|
+
var _this = this;
|
1259
|
+
|
1260
|
+
this.initOnLoad = function() {
|
1261
|
+
return settings.features.paginate;
|
1262
|
+
};
|
1263
|
+
|
1264
|
+
this.init = function() {
|
1265
|
+
var perPageUrl = window.location.search.match(new RegExp(settings.params.perPage + '=([^&]*)'));
|
1266
|
+
|
1267
|
+
// If perPage is present in URL parameters and pushState is enabled
|
1268
|
+
// (meaning that it'd be possible for dynatable to have put the
|
1269
|
+
// perPage parameter in the URL)
|
1270
|
+
if (perPageUrl && settings.features.pushState) {
|
1271
|
+
// Don't reset page to 1 on init, since it might override page
|
1272
|
+
// set on init from URL
|
1273
|
+
this.set(perPageUrl[1], true);
|
1274
|
+
} else {
|
1275
|
+
this.set(settings.dataset.perPageDefault, true);
|
1276
|
+
}
|
1277
|
+
|
1278
|
+
if (settings.features.perPageSelect) {
|
1279
|
+
this.attach();
|
1280
|
+
}
|
1281
|
+
};
|
1282
|
+
|
1283
|
+
this.create = function() {
|
1284
|
+
var $select = $('<select>', {
|
1285
|
+
id: 'dynatable-per-page-' + obj.element.id,
|
1286
|
+
'class': 'dynatable-per-page-select'
|
1287
|
+
});
|
1288
|
+
|
1289
|
+
for (var i = 0, len = settings.dataset.perPageOptions.length; i < len; i++) {
|
1290
|
+
var number = settings.dataset.perPageOptions[i],
|
1291
|
+
selected = settings.dataset.perPage == number ? 'selected="selected"' : '';
|
1292
|
+
$select.append('<option value="' + number + '" ' + selected + '>' + number + '</option>');
|
1293
|
+
}
|
1294
|
+
|
1295
|
+
$select.bind('change', function(e) {
|
1296
|
+
_this.set($(this).val());
|
1297
|
+
obj.process();
|
1298
|
+
});
|
1299
|
+
|
1300
|
+
return $('<span />', {
|
1301
|
+
'class': 'dynatable-per-page'
|
1302
|
+
}).append("<span class='dynatable-per-page-label'>" + settings.inputs.perPageText + "</span>").append($select);
|
1303
|
+
};
|
1304
|
+
|
1305
|
+
this.attach = function() {
|
1306
|
+
var $target = settings.inputs.perPageTarget ? $(settings.inputs.perPageTarget) : obj.$element;
|
1307
|
+
$target[settings.inputs.perPagePlacement](this.create());
|
1308
|
+
};
|
1309
|
+
|
1310
|
+
this.set = function(number, skipResetPage) {
|
1311
|
+
if (!skipResetPage) { obj.paginationPage.set(1); }
|
1312
|
+
settings.dataset.perPage = parseInt(number);
|
1313
|
+
};
|
1314
|
+
};
|
1315
|
+
|
1316
|
+
// pagination links which update dataset.page attribute
|
1317
|
+
function PaginationLinks(obj, settings) {
|
1318
|
+
var _this = this;
|
1319
|
+
|
1320
|
+
this.initOnLoad = function() {
|
1321
|
+
return settings.features.paginate;
|
1322
|
+
};
|
1323
|
+
|
1324
|
+
this.init = function() {
|
1325
|
+
this.attach();
|
1326
|
+
};
|
1327
|
+
|
1328
|
+
this.create = function() {
|
1329
|
+
var pageLinks = '<ul id="' + 'dynatable-pagination-links-' + obj.element.id + '" class="' + settings.inputs.paginationClass + '">',
|
1330
|
+
pageLinkClass = settings.inputs.paginationLinkClass,
|
1331
|
+
activePageClass = settings.inputs.paginationActiveClass,
|
1332
|
+
disabledPageClass = settings.inputs.paginationDisabledClass,
|
1333
|
+
pages = Math.ceil(settings.dataset.queryRecordCount / settings.dataset.perPage),
|
1334
|
+
page = settings.dataset.page,
|
1335
|
+
breaks = [
|
1336
|
+
settings.inputs.paginationGap[0],
|
1337
|
+
settings.dataset.page - settings.inputs.paginationGap[1],
|
1338
|
+
settings.dataset.page + settings.inputs.paginationGap[2],
|
1339
|
+
(pages + 1) - settings.inputs.paginationGap[3]
|
1340
|
+
];
|
1341
|
+
|
1342
|
+
pageLinks += '<li><span>Pages: </span></li>';
|
1343
|
+
|
1344
|
+
for (var i = 1; i <= pages; i++) {
|
1345
|
+
if ( (i > breaks[0] && i < breaks[1]) || (i > breaks[2] && i < breaks[3])) {
|
1346
|
+
// skip to next iteration in loop
|
1347
|
+
continue;
|
1348
|
+
} else {
|
1349
|
+
var li = obj.paginationLinks.buildLink(i, i, pageLinkClass, page == i, activePageClass),
|
1350
|
+
breakIndex,
|
1351
|
+
nextBreak;
|
1352
|
+
|
1353
|
+
// If i is not between one of the following
|
1354
|
+
// (1 + (settings.paginationGap[0]))
|
1355
|
+
// (page - settings.paginationGap[1])
|
1356
|
+
// (page + settings.paginationGap[2])
|
1357
|
+
// (pages - settings.paginationGap[3])
|
1358
|
+
breakIndex = $.inArray(i, breaks);
|
1359
|
+
nextBreak = breaks[breakIndex + 1];
|
1360
|
+
if (breakIndex > 0 && i !== 1 && nextBreak && nextBreak > (i + 1)) {
|
1361
|
+
var ellip = '<li><span class="dynatable-page-break">…</span></li>';
|
1362
|
+
li = breakIndex < 2 ? ellip + li : li + ellip;
|
1363
|
+
}
|
1364
|
+
|
1365
|
+
if (settings.inputs.paginationPrev && i === 1) {
|
1366
|
+
var prevLi = obj.paginationLinks.buildLink(page - 1, settings.inputs.paginationPrev, pageLinkClass + ' ' + settings.inputs.paginationPrevClass, page === 1, disabledPageClass);
|
1367
|
+
li = prevLi + li;
|
1368
|
+
}
|
1369
|
+
if (settings.inputs.paginationNext && i === pages) {
|
1370
|
+
var nextLi = obj.paginationLinks.buildLink(page + 1, settings.inputs.paginationNext, pageLinkClass + ' ' + settings.inputs.paginationNextClass, page === pages, disabledPageClass);
|
1371
|
+
li += nextLi;
|
1372
|
+
}
|
1373
|
+
|
1374
|
+
pageLinks += li;
|
1375
|
+
}
|
1376
|
+
}
|
1377
|
+
|
1378
|
+
pageLinks += '</ul>';
|
1379
|
+
|
1380
|
+
// only bind page handler to non-active and non-disabled page links
|
1381
|
+
var selector = '#dynatable-pagination-links-' + obj.element.id + ' a.' + pageLinkClass + ':not(.' + activePageClass + ',.' + disabledPageClass + ')';
|
1382
|
+
// kill any existing delegated-bindings so they don't stack up
|
1383
|
+
$(document).undelegate(selector, 'click.dynatable');
|
1384
|
+
$(document).delegate(selector, 'click.dynatable', function(e) {
|
1385
|
+
$this = $(this);
|
1386
|
+
$this.closest(settings.inputs.paginationClass).find('.' + activePageClass).removeClass(activePageClass);
|
1387
|
+
$this.addClass(activePageClass);
|
1388
|
+
|
1389
|
+
obj.paginationPage.set($this.data('dynatable-page'));
|
1390
|
+
obj.process();
|
1391
|
+
e.preventDefault();
|
1392
|
+
});
|
1393
|
+
|
1394
|
+
return pageLinks;
|
1395
|
+
};
|
1396
|
+
|
1397
|
+
this.buildLink = function(page, label, linkClass, conditional, conditionalClass) {
|
1398
|
+
var link = '<a data-dynatable-page=' + page + ' class="' + linkClass,
|
1399
|
+
li = '<li';
|
1400
|
+
|
1401
|
+
if (conditional) {
|
1402
|
+
link += ' ' + conditionalClass;
|
1403
|
+
li += ' class="' + conditionalClass + '"';
|
1404
|
+
}
|
1405
|
+
|
1406
|
+
link += '">' + label + '</a>';
|
1407
|
+
li += '>' + link + '</li>';
|
1408
|
+
|
1409
|
+
return li;
|
1410
|
+
};
|
1411
|
+
|
1412
|
+
this.attach = function() {
|
1413
|
+
// append page links *after* delegate-event-binding so it doesn't need to
|
1414
|
+
// find and select all page links to bind event
|
1415
|
+
var $target = settings.inputs.paginationLinkTarget ? $(settings.inputs.paginationLinkTarget) : obj.$element;
|
1416
|
+
$target[settings.inputs.paginationLinkPlacement](obj.paginationLinks.create());
|
1417
|
+
};
|
1418
|
+
};
|
1419
|
+
|
1420
|
+
utility = dt.utility = {
|
1421
|
+
normalizeText: function(text, style) {
|
1422
|
+
text = this.textTransform[style](text);
|
1423
|
+
return text;
|
1424
|
+
},
|
1425
|
+
textTransform: {
|
1426
|
+
trimDash: function(text) {
|
1427
|
+
return text.replace(/^\s+|\s+$/g, "").replace(/\s+/g, "-");
|
1428
|
+
},
|
1429
|
+
camelCase: function(text) {
|
1430
|
+
text = this.trimDash(text);
|
1431
|
+
return text
|
1432
|
+
.replace(/(\-[a-zA-Z])/g, function($1){return $1.toUpperCase().replace('-','');})
|
1433
|
+
.replace(/([A-Z])([A-Z]+)/g, function($1,$2,$3){return $2 + $3.toLowerCase();})
|
1434
|
+
.replace(/^[A-Z]/, function($1){return $1.toLowerCase();});
|
1435
|
+
},
|
1436
|
+
dashed: function(text) {
|
1437
|
+
text = this.trimDash(text);
|
1438
|
+
return this.lowercase(text);
|
1439
|
+
},
|
1440
|
+
underscore: function(text) {
|
1441
|
+
text = this.trimDash(text);
|
1442
|
+
return this.lowercase(text.replace(/(-)/g, '_'));
|
1443
|
+
},
|
1444
|
+
lowercase: function(text) {
|
1445
|
+
return text.replace(/([A-Z])/g, function($1){return $1.toLowerCase();});
|
1446
|
+
}
|
1447
|
+
},
|
1448
|
+
// Deserialize params in URL to object
|
1449
|
+
// see http://stackoverflow.com/questions/1131630/javascript-jquery-param-inverse-function/3401265#3401265
|
1450
|
+
deserialize: function(query) {
|
1451
|
+
if (!query) return {};
|
1452
|
+
// modified to accept an array of partial URL strings
|
1453
|
+
if (typeof(query) === "object") { query = query.join('&'); }
|
1454
|
+
|
1455
|
+
var hash = {},
|
1456
|
+
vars = query.split("&");
|
1457
|
+
|
1458
|
+
for (var i = 0; i < vars.length; i++) {
|
1459
|
+
var pair = vars[i].split("="),
|
1460
|
+
k = decodeURIComponent(pair[0]),
|
1461
|
+
v, m;
|
1462
|
+
|
1463
|
+
if (!pair[1]) { continue };
|
1464
|
+
v = decodeURIComponent(pair[1].replace(/\+/g, ' '));
|
1465
|
+
|
1466
|
+
// modified to parse multi-level parameters (e.g. "hi[there][dude]=whatsup" => hi: {there: {dude: "whatsup"}})
|
1467
|
+
while (m = k.match(/([^&=]+)\[([^&=]+)\]$/)) {
|
1468
|
+
var origV = v;
|
1469
|
+
k = m[1];
|
1470
|
+
v = {};
|
1471
|
+
|
1472
|
+
// If nested param ends in '][', then the regex above erroneously included half of a trailing '[]',
|
1473
|
+
// which indicates the end-value is part of an array
|
1474
|
+
if (m[2].substr(m[2].length-2) == '][') { // must use substr for IE to understand it
|
1475
|
+
v[m[2].substr(0,m[2].length-2)] = [origV];
|
1476
|
+
} else {
|
1477
|
+
v[m[2]] = origV;
|
1478
|
+
}
|
1479
|
+
}
|
1480
|
+
|
1481
|
+
// If it is the first entry with this name
|
1482
|
+
if (typeof hash[k] === "undefined") {
|
1483
|
+
if (k.substr(k.length-2) != '[]') { // not end with []. cannot use negative index as IE doesn't understand it
|
1484
|
+
hash[k] = v;
|
1485
|
+
} else {
|
1486
|
+
hash[k] = [v];
|
1487
|
+
}
|
1488
|
+
// If subsequent entry with this name and not array
|
1489
|
+
} else if (typeof hash[k] === "string") {
|
1490
|
+
hash[k] = v; // replace it
|
1491
|
+
// modified to add support for objects
|
1492
|
+
} else if (typeof hash[k] === "object") {
|
1493
|
+
hash[k] = $.extend({}, hash[k], v);
|
1494
|
+
// If subsequent entry with this name and is array
|
1495
|
+
} else {
|
1496
|
+
hash[k].push(v);
|
1497
|
+
}
|
1498
|
+
}
|
1499
|
+
return hash;
|
1500
|
+
},
|
1501
|
+
refreshQueryString: function(urlString, data, settings) {
|
1502
|
+
var _this = this,
|
1503
|
+
queryString = urlString.split('?'),
|
1504
|
+
path = queryString.shift(),
|
1505
|
+
urlOptions;
|
1506
|
+
|
1507
|
+
urlOptions = this.deserialize(urlString);
|
1508
|
+
|
1509
|
+
// Loop through each dynatable param and update the URL with it
|
1510
|
+
for (attr in settings.params) {
|
1511
|
+
if (settings.params.hasOwnProperty(attr)) {
|
1512
|
+
var label = settings.params[attr];
|
1513
|
+
// Skip over parameters matching attributes for disabled features (i.e. leave them untouched),
|
1514
|
+
// because if the feature is turned off, then parameter name is a coincidence and it's unrelated to dynatable.
|
1515
|
+
if (
|
1516
|
+
(!settings.features.sort && attr == "sorts") ||
|
1517
|
+
(!settings.features.paginate && _this.anyMatch(attr, ["page", "perPage", "offset"], function(attr, param) { return attr == param; }))
|
1518
|
+
) {
|
1519
|
+
continue;
|
1520
|
+
}
|
1521
|
+
|
1522
|
+
// Delete page and offset from url params if on page 1 (default)
|
1523
|
+
if ((attr === "page" || attr === "offset") && data["page"] === 1) {
|
1524
|
+
if (urlOptions[label]) {
|
1525
|
+
delete urlOptions[label];
|
1526
|
+
}
|
1527
|
+
continue;
|
1528
|
+
}
|
1529
|
+
|
1530
|
+
// Delete perPage from url params if default perPage value
|
1531
|
+
if (attr === "perPage" && data[label] == settings.dataset.perPageDefault) {
|
1532
|
+
if (urlOptions[label]) {
|
1533
|
+
delete urlOptions[label];
|
1534
|
+
}
|
1535
|
+
continue;
|
1536
|
+
}
|
1537
|
+
|
1538
|
+
// For queries, we're going to handle each possible query parameter individually here instead of
|
1539
|
+
// handling the entire queries object below, since we need to make sure that this is a query controlled by dynatable.
|
1540
|
+
if (attr == "queries" && data[label]) {
|
1541
|
+
var queries = settings.inputs.queries || [],
|
1542
|
+
inputQueries = $.makeArray(queries.map(function() { return $(this).attr('name') }));
|
1543
|
+
|
1544
|
+
if (settings.features.search) { inputQueries.push('search'); }
|
1545
|
+
|
1546
|
+
for (var i = 0, len = inputQueries.length; i < len; i++) {
|
1547
|
+
var attr = inputQueries[i];
|
1548
|
+
if (data[label][attr]) {
|
1549
|
+
if (typeof urlOptions[label] === 'undefined') { urlOptions[label] = {}; }
|
1550
|
+
urlOptions[label][attr] = data[label][attr];
|
1551
|
+
} else {
|
1552
|
+
delete urlOptions[label][attr];
|
1553
|
+
}
|
1554
|
+
}
|
1555
|
+
continue;
|
1556
|
+
}
|
1557
|
+
|
1558
|
+
// If we haven't returned true by now, then we actually want to update the parameter in the URL
|
1559
|
+
if (data[label]) {
|
1560
|
+
urlOptions[label] = data[label];
|
1561
|
+
} else {
|
1562
|
+
delete urlOptions[label];
|
1563
|
+
}
|
1564
|
+
}
|
1565
|
+
}
|
1566
|
+
return decodeURI($.param(urlOptions));
|
1567
|
+
},
|
1568
|
+
// Get array of keys from object
|
1569
|
+
// see http://stackoverflow.com/questions/208016/how-to-list-the-properties-of-a-javascript-object/208020#208020
|
1570
|
+
keysFromObject: function(obj){
|
1571
|
+
var keys = [];
|
1572
|
+
for (var key in obj){
|
1573
|
+
keys.push(key);
|
1574
|
+
}
|
1575
|
+
return keys;
|
1576
|
+
},
|
1577
|
+
// Find an object in an array of objects by attributes.
|
1578
|
+
// E.g. find object with {id: 'hi', name: 'there'} in an array of objects
|
1579
|
+
findObjectInArray: function(array, objectAttr) {
|
1580
|
+
var _this = this,
|
1581
|
+
foundObject;
|
1582
|
+
for (var i = 0, len = array.length; i < len; i++) {
|
1583
|
+
var item = array[i];
|
1584
|
+
// For each object in array, test to make sure all attributes in objectAttr match
|
1585
|
+
if (_this.allMatch(item, objectAttr, function(item, key, value) { return item[key] == value; })) {
|
1586
|
+
foundObject = item;
|
1587
|
+
break;
|
1588
|
+
}
|
1589
|
+
}
|
1590
|
+
return foundObject;
|
1591
|
+
},
|
1592
|
+
// Return true if supplied test function passes for ALL items in an array
|
1593
|
+
allMatch: function(item, arrayOrObject, test) {
|
1594
|
+
// start off with true result by default
|
1595
|
+
var match = true,
|
1596
|
+
isArray = $.isArray(arrayOrObject);
|
1597
|
+
// Loop through all items in array
|
1598
|
+
$.each(arrayOrObject, function(key, value) {
|
1599
|
+
var result = isArray ? test(item, value) : test(item, key, value);
|
1600
|
+
// If a single item tests false, go ahead and break the array by returning false
|
1601
|
+
// and return false as result,
|
1602
|
+
// otherwise, continue with next iteration in loop
|
1603
|
+
// (if we make it through all iterations without overriding match with false,
|
1604
|
+
// then we can return the true result we started with by default)
|
1605
|
+
if (!result) { return match = false; }
|
1606
|
+
});
|
1607
|
+
return match;
|
1608
|
+
},
|
1609
|
+
// Return true if supplied test function passes for ANY items in an array
|
1610
|
+
anyMatch: function(item, arrayOrObject, test) {
|
1611
|
+
var match = false,
|
1612
|
+
isArray = $.isArray(arrayOrObject);
|
1613
|
+
|
1614
|
+
$.each(arrayOrObject, function(key, value) {
|
1615
|
+
var result = isArray ? test(item, value) : test(item, key, value);
|
1616
|
+
if (result) {
|
1617
|
+
// As soon as a match is found, set match to true, and return false to stop the `$.each` loop
|
1618
|
+
match = true;
|
1619
|
+
return false;
|
1620
|
+
}
|
1621
|
+
});
|
1622
|
+
return match;
|
1623
|
+
},
|
1624
|
+
// Return true if two objects are equal
|
1625
|
+
// (i.e. have the same attributes and attribute values)
|
1626
|
+
objectsEqual: function(a, b) {
|
1627
|
+
for (attr in a) {
|
1628
|
+
if (a.hasOwnProperty(attr)) {
|
1629
|
+
if (!b.hasOwnProperty(attr) || a[attr] !== b[attr]) {
|
1630
|
+
return false;
|
1631
|
+
}
|
1632
|
+
}
|
1633
|
+
}
|
1634
|
+
for (attr in b) {
|
1635
|
+
if (b.hasOwnProperty(attr) && !a.hasOwnProperty(attr)) {
|
1636
|
+
return false;
|
1637
|
+
}
|
1638
|
+
}
|
1639
|
+
return true;
|
1640
|
+
},
|
1641
|
+
// Taken from http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/105074#105074
|
1642
|
+
randomHash: function() {
|
1643
|
+
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
|
1644
|
+
}
|
1645
|
+
};
|
1646
|
+
|
1647
|
+
//-----------------------------------------------------------------
|
1648
|
+
// Build the dynatable plugin
|
1649
|
+
//-----------------------------------------------------------------
|
1650
|
+
|
1651
|
+
// Object.create support test, and fallback for browsers without it
|
1652
|
+
if ( typeof Object.create !== "function" ) {
|
1653
|
+
Object.create = function (o) {
|
1654
|
+
function F() {}
|
1655
|
+
F.prototype = o;
|
1656
|
+
return new F();
|
1657
|
+
};
|
1658
|
+
}
|
1659
|
+
|
1660
|
+
//-----------------------------------------------------------------
|
1661
|
+
// Global dynatable plugin setting defaults
|
1662
|
+
//-----------------------------------------------------------------
|
1663
|
+
|
1664
|
+
$.dynatableSetup = function(options) {
|
1665
|
+
defaults = mergeSettings(options);
|
1666
|
+
};
|
1667
|
+
|
1668
|
+
// Create dynatable plugin based on a defined object
|
1669
|
+
$.dynatable = function( object ) {
|
1670
|
+
$.fn['dynatable'] = function( options ) {
|
1671
|
+
return this.each(function() {
|
1672
|
+
if ( ! $.data( this, 'dynatable' ) ) {
|
1673
|
+
$.data( this, 'dynatable', Object.create(object).init(this, options) );
|
1674
|
+
}
|
1675
|
+
});
|
1676
|
+
};
|
1677
|
+
};
|
1678
|
+
|
1679
|
+
$.dynatable(dt);
|
1680
|
+
|
1681
|
+
})(jQuery);
|