tablerender-rails 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 73749b610f7831b91eaf7b853226ede2eeea6f5c
4
+ data.tar.gz: fc3c2b65ffc797851559362f6368425607d933f6
5
+ SHA512:
6
+ metadata.gz: c599c4cb0c90bb719784f8fd33aff3f762cd4fe5c0c8af97d902be5b1d1987b633279e70b67f421052153ee3c598d47ab2f764a5ccf07c96256f61f75ebf3b54
7
+ data.tar.gz: 1b4d4f0ffe9396ca3eb9243432603e3b2d0c4cbc62b10eca9aec0b6aad49e4e22dd37a4f5dbffb995eee7e29ac0666bda0ff6743b061bdc38c0d9299f29d3eb9
@@ -0,0 +1,27 @@
1
+ .DS_Store
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ .project
8
+ Gemfile.lock
9
+ InstalledFiles
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
20
+
21
+
22
+
23
+ vendor/assets/javascripts/index.html
24
+ vendor/assets/javascripts/jquery.js
25
+ vendor/assets/javascripts/tablerender.css
26
+ vendor/assets/javascripts/test_cases_body.js
27
+ vendor/assets/javascripts/test_cases_columns.js
@@ -0,0 +1,3 @@
1
+ v0.0.1
2
+ ======
3
+ - initial release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in tablerender-rails.gemspec
4
+ gemspec
@@ -0,0 +1,20 @@
1
+ Copyright 2011 Fabio Tunno
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,69 @@
1
+ TableRender is a jQuery based plugin.
2
+
3
+ _How can I render a table with too much data?_
4
+
5
+ TableRender is a jQuery-based plugin that parses and creates more than 100.000 table rows just in time.
6
+
7
+ Given a JSON object, TableRender parses, renders, filters and sorts all data.
8
+
9
+ Methods:
10
+
11
+ * `addRow`, `addRows`
12
+ * `removeRow`, `removeRows`
13
+ * `replaceRow`, `replaceRows`
14
+ * `search`
15
+ * `filter`
16
+ * `sort`
17
+
18
+ Supported browsers:
19
+
20
+ * Internet Explorer 6 (not tested)
21
+ * Internet Explorer 7
22
+ * Internet Explorer 8
23
+ * Internet Explorer 9
24
+ * Firefox
25
+ * Google Chrome
26
+ * Safari
27
+ * Opera
28
+
29
+ Requirements:
30
+
31
+ * jQuery 1.4.2 or later
32
+ * IntroSort script (used while sorting)
33
+
34
+ Example
35
+
36
+ $('#example').table({
37
+ columns: [
38
+ {
39
+ key: 'title',
40
+ label: 'Title'
41
+ },{
42
+ key: 'name',
43
+ label: 'Name'
44
+ },{
45
+ key: 'surname',
46
+ label: 'Surname'
47
+ }
48
+ ],
49
+ rowHeight: 20,
50
+ headHeight: 20,
51
+ borderHeight: 0,
52
+ sortable: false,
53
+ selection: true,
54
+ multiselection: true,
55
+ canBeSorted: function(column){
56
+ return column != 3
57
+ }
58
+ });
59
+
60
+ var jsonData = [];
61
+ for ( var i=0; i < 100000; i++ ) {
62
+ jsonData.push({
63
+ title: 'Title ' + i,
64
+ name: 'Name ' + i,
65
+ surname: 'Surname ' + i
66
+ });
67
+ }
68
+
69
+ $('#example').table().data( jsonData );
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
@@ -0,0 +1,12 @@
1
+ require "rails"
2
+ require "tablerender-rails/version"
3
+
4
+ module Tablerender
5
+ module Rails
6
+ if ::Rails.version < "3.1"
7
+ require 'tablerender-rails/railtie'
8
+ else
9
+ require 'tablerender-rails/engine'
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,8 @@
1
+ require 'rails'
2
+
3
+ module Tablerender
4
+ module Rails
5
+ class Engine < ::Rails::Engine
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module Tablerender
2
+ module Rails
3
+ class Railtie < ::Rails::Railtie; end
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Tablerender
2
+ module Rails
3
+ VERSION = "0.0.3"
4
+ end
5
+ end
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/tablerender-rails/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Fabio Tunno", "Claudio Poli"]
6
+ gem.email = ["fabio@audiobox.fm"]
7
+ gem.description = 'Adds tablerender.js to the asset pipeline.'
8
+ gem.summary = 'JavaScript Table Rendered for Rails 3.1+'
9
+ gem.homepage = 'https://github.com/masterkain/tablerender-rails'
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = "tablerender-rails"
15
+ gem.require_paths = ['lib']
16
+ gem.version = Tablerender::Rails::VERSION
17
+
18
+ gem.add_dependency "railties", ">= 3.1"
19
+
20
+ gem.add_development_dependency "bundler", ">= 1.0.0"
21
+ gem.add_development_dependency "rails", ">= 3.1"
22
+ end
@@ -0,0 +1,1483 @@
1
+ (function ($) {
2
+ var VERSION = "version 0.2b";
3
+ /**
4
+ * Initialization function
5
+ *
6
+ * @param obj the HTML element to wrap
7
+ * @param opts the initial options
8
+ * @return TableRender instance
9
+ */
10
+ window.TableRender = function(element, opts) {
11
+
12
+ var obj = element;
13
+ $(element).data("_tablerender", this);
14
+
15
+ this.version = VERSION;
16
+
17
+ // Get initial options correctly
18
+ var options = $.extend(true, {
19
+ // String: table header stylesheet class
20
+ headCss: '',
21
+
22
+ // String: table body stylesheet class
23
+ bodyCss: '',
24
+
25
+ rowCss: 'row',
26
+
27
+ overflow: 'overflow-y: scroll',
28
+
29
+ // Array: columns list ( ie: [ { key: 'key', label: 'label', hidden: false}, { key: 'key', label: 'label', hidden: true} ] )
30
+ columns: [],
31
+
32
+ // int: row height in pixel
33
+ rowHeight: 20,
34
+
35
+ // int: header height
36
+ headHeight: 20,
37
+
38
+ // top position of the body
39
+ bodyTopPosition: 0,
40
+
41
+ // int: row border in pixel ( used if border css setting is set )
42
+ borderHeight: 0,
43
+
44
+ // enables or disables the resize function
45
+ allowResizeHeader: true,
46
+
47
+ // boolean: enabled or disable the column sort function
48
+ sortable: false,
49
+
50
+ // function: used to customize the table header render
51
+ columnRender: headRender,
52
+
53
+ // Function that renders the entire header
54
+ headerRender: undefined,
55
+
56
+ // function: used to customize the table rows render
57
+ rowRender: _rowRender,
58
+
59
+ // boolean: enable or disable animations used on remove rows event.
60
+ animate: false,
61
+
62
+ // boolean: enable or disable the row selection management
63
+ selection: false,
64
+
65
+ // boolean: enable or disable the multiselection management ( used with 'selection = true' only )
66
+ multiselection: false,
67
+
68
+ // hash: contains the sortable function,
69
+ sort: {},
70
+
71
+ // function: enable or disable sortable function for a specified column
72
+ canBeSorted: canBeSorted,
73
+
74
+ // boolean: force to empty table content on scroll
75
+ empties: true,
76
+
77
+ // int: indicates how many rows to render before and after paging
78
+ threshold: 15
79
+
80
+ }, opts),
81
+
82
+ // shortcut to 'this' instance
83
+ self = this,
84
+
85
+ $self = $(self); // shortcut to jQuery functions
86
+ var
87
+ // Table header wrapper that contains all columns and scrollbar placeholder
88
+ header_container = $('<div class="table_header_container" style="position:absolute;top:0;left:0;right:0;height:' + options.headHeight + 'px;"></div>').appendTo(obj),
89
+ // Table header that contains all columns
90
+ head = $('<div class="table_head ' + options.headCss + '"></div>').appendTo(header_container),
91
+ // Table body wrapper that contains the table rows container
92
+ body_container = $('<div class="table_body_container" style="' + options.overflow + ';position:absolute;top:' + (options.bodyTopPosition || options.headHeight) + 'px;left:0;right:0;bottom:0;"></div>').appendTo(obj).bind('scroll', _scroll),
93
+ // Table rows container
94
+ body = $('<div class="table_body ' + options.bodyCss + '" style="position:relative;" ></div>').appendTo(body_container);
95
+
96
+
97
+
98
+
99
+ var
100
+ // True once header has been drawed
101
+ _header_drawn = false,
102
+
103
+ _waiting = false,
104
+
105
+ // collection that contains all rows data
106
+ _data = [],
107
+
108
+ // collection that contains all rows data currently filtered
109
+ _currentData = [],
110
+
111
+ // collection that contains all rendered rows as HTML object
112
+ _shownData = [],
113
+
114
+ // collection that contains all selected rows index
115
+ _selectedIndexes = [],
116
+
117
+ // stores the sortable data
118
+ _asc = [],
119
+
120
+ // store scroll thread handle id
121
+ _scrollTimer,
122
+
123
+ // Stores the query text used to filter collection rows
124
+ _queryText,
125
+
126
+ // true if table is currently sorted
127
+ _sorted = false,
128
+
129
+ // Current coordinates about viewport currenlty shown
130
+ _oldViewPort = {
131
+ from: 0,
132
+ to: 0,
133
+ height: 0
134
+ },
135
+
136
+ // Coordinates about new viewport to show
137
+ _viewPort = {
138
+ from: 0,
139
+ to: 0,
140
+ height: 0
141
+ },
142
+ // Collection of coloumns
143
+ _columns = []; // HTML columns object collection
144
+
145
+
146
+ /**
147
+ * PUBLIC METHODS
148
+ */
149
+
150
+
151
+ this.option = function (name, value) {
152
+ if (value === undefined || value === null) {
153
+ return options[name];
154
+ } else {
155
+ options[name] = value;
156
+ }
157
+ };
158
+
159
+
160
+ /**
161
+ * Adds column at the specified index
162
+ *
163
+ * @param columnData is an Object containing the column data
164
+ * @param index is an integer used as index of the column
165
+ */
166
+ this.addColumn = function (columnData, index) {
167
+ index = typeof index == 'number' ? index : _columns.length;
168
+
169
+ if ((index >= _columns.length) || (_columns[index] === undefined || _columns[index] === null)
170
+
171
+ ) {
172
+ // We are adding a column in an empty position.
173
+ // So, we have to replace the existing undefined object with the new column data
174
+ _columns[index] = columnData;
175
+
176
+ } else {
177
+ // Add a column into an already non-empty position.
178
+ _columns.splice(index, 0, columnData);
179
+ }
180
+
181
+ if (_header_drawn) {
182
+ // Redraw header
183
+ this.drawHeader();
184
+ }
185
+
186
+ // TODO: do we have to redraw the body of the table?
187
+ _refreshViewPort();
188
+
189
+ $self.trigger('add_column', [columnData, index]);
190
+ };
191
+
192
+
193
+ /**
194
+ * Removes column at the specified index or key
195
+ * @param index is an Integer or a 'key' of the column is being to be removed
196
+ */
197
+ this.removeColumn = function (index) {
198
+
199
+ if (typeof index != 'number' && typeof index != 'string') {
200
+ return false;
201
+ }
202
+
203
+ var col_index = -1;
204
+ if (typeof index == 'number') {
205
+ if (_columns[index]) {
206
+ col_index = index;
207
+ }
208
+ } else {
209
+
210
+ col_index = getColumnIndexByKey(index);
211
+
212
+ }
213
+
214
+ if (col_index == -1) {
215
+ return false;
216
+ }
217
+
218
+
219
+ var col_data = _columns.splice(col_index, 1);
220
+
221
+ if (_header_drawn) {
222
+ // Redraw header
223
+ this.drawHeader();
224
+ }
225
+
226
+ // TODO: do we have to redraw the body of the table?
227
+ _refreshViewPort();
228
+
229
+ $self.trigger('remove_column', [col_data, col_index]);
230
+
231
+ return col_data;
232
+
233
+ };
234
+
235
+
236
+
237
+ this.drawHeader = function () {
238
+ // Clear the header (remove all columns)
239
+ head.html('');
240
+
241
+ if ( options.headerRender && options.headerRender ){
242
+ var new_head = options.headerRender( this.columns() );
243
+ head.replaceWith( new_head );
244
+ head = $(new_head);
245
+ } else {
246
+ var cols = this.columns();
247
+ $.each( cols, function (i, item) {
248
+
249
+ var element = $(options.columnRender(i, item, cols));
250
+
251
+ if (element.length) {
252
+ $(element).appendTo(head);
253
+ if (options.sortable) {
254
+ $(element).bind('click', function (e) {
255
+ self.sort(i, true); // bind the sortable event
256
+ });
257
+ }
258
+ }
259
+
260
+ });
261
+ }
262
+
263
+ return (_header_drawn = true);
264
+ };
265
+
266
+
267
+ /**
268
+ * Shows a columns at the specified index
269
+ */
270
+ this.showColumn = function (index) {
271
+ showHideColumn(index, true);
272
+ };
273
+
274
+ /**
275
+ * Hides a columns at the specified index
276
+ */
277
+ this.hideColumn = function (index) {
278
+ showHideColumn(index, false);
279
+ };
280
+
281
+
282
+ this.columns = function (columns) {
283
+ if ( typeof columns == 'object' ){
284
+ _columns = columns;
285
+ this.drawHeader();
286
+ _refreshViewPort();
287
+ }
288
+ return $.map(_columns.slice(0), function (col, index) {
289
+ if (col) {
290
+ return col;
291
+ }
292
+ });
293
+ };
294
+
295
+
296
+
297
+ /**
298
+ * Use this method to set new collection data.
299
+ * If not arguments passed, this method returns the entire collection data
300
+ */
301
+ this.data = function (data) {
302
+ if (data === undefined) return _data;
303
+
304
+ this.clearSelection();
305
+ _data = _currentData = [];
306
+
307
+ _queryText = undefined;
308
+
309
+ _data = _currentData = data;
310
+
311
+ showData(data);
312
+
313
+ $self.trigger('newData', [data]);
314
+ };
315
+
316
+ /**
317
+ * Returns the row data at the specified position
318
+ */
319
+ this.currentDataAt = function (index) {
320
+ return _currentData[index];
321
+ };
322
+
323
+ /**
324
+ * Resizes the table
325
+ */
326
+ this.resize = function () {
327
+ var _height = _currentData.length * (options.rowHeight + options.borderHeight); // calculate maximum height
328
+ body.css('height', _height); // set height to body
329
+ newViewPort();
330
+
331
+ if ( options.allowResizeHeader ){
332
+ head.width(body.width()); // Set the table header width including scrollbar width fix
333
+ }
334
+ $self.trigger('layout'); // fire event
335
+ };
336
+
337
+ /**
338
+ * Returns the HTML object representing the row at the specified position
339
+ */
340
+ this.rowAt = function (index) {
341
+ index = originalIndexToCurrentIndex(index);
342
+
343
+ var _row = _shownData[index];
344
+ if (_row) {
345
+ $(_row).css({
346
+ 'top': (index * (options.rowHeight + options.borderHeight)),
347
+ 'height': options.rowHeight
348
+ });
349
+ }
350
+
351
+ return _row;
352
+ };
353
+
354
+ /**
355
+ * Returns the data row object at the specified position
356
+ */
357
+ this.dataAt = function (index) {
358
+ return _data[index];
359
+ };
360
+
361
+
362
+ /**
363
+ * Returns the data row object associated with given row.
364
+ * Row is the HTML object
365
+ */
366
+ this.rowToData = function (row) {
367
+ var index = this.rowToIndex(row);
368
+ return _data[index];
369
+ };
370
+
371
+ /**
372
+ * Returns the index associated with given row.
373
+ * Row is the HTML object
374
+ */
375
+ this.rowToIndex = function (row) {
376
+ var index = $(row)[0].offsetTop / (options.rowHeight + options.borderHeight);
377
+ return currentIndexToOriginalIndex(index);
378
+ };
379
+
380
+ /***************
381
+ * SELECTION
382
+ ***************/
383
+
384
+
385
+ /**
386
+ * Marks the row at the specified index as selected
387
+ */
388
+ this.selectRow = function (index) {
389
+
390
+ if (!options.selection) return;
391
+ if (!options.multiselection) this.clearSelection();
392
+
393
+ var
394
+ currentIndex = originalIndexToCurrentIndex(index),
395
+ viewPort = getViewPort();
396
+
397
+ selectRow(currentIndex);
398
+ if (currentIndex >= viewPort.from && currentIndex <= viewPort.to) {
399
+ var row = this.rowAt(index);
400
+ $self.trigger('rowSelection', [index, row, true, _currentData[currentIndex]]);
401
+ }
402
+ return this;
403
+ };
404
+
405
+
406
+ /**
407
+ * Marks the row at the specified index as unselected
408
+ */
409
+ this.unselectRow = function (index) {
410
+ if (!options.selection) return;
411
+
412
+ var
413
+ row = this.rowAt(index);
414
+ currentIndex = originalIndexToCurrentIndex(index);
415
+
416
+ unselectRow(currentIndex);
417
+
418
+ if (currentIndex >= viewPort.from && currentIndex <= viewPort.to) {
419
+ // row = this.rowAt(index);
420
+ $self.trigger('rowSelection', [index, row, false, _currentData[currentIndex]]);
421
+ }
422
+
423
+ return this;
424
+ };
425
+
426
+ /**
427
+ * Marks all row as unselected
428
+ */
429
+ this.clearSelection = function () {
430
+ var indexes = selectedIndexes(),
431
+ viewPort = getViewPort();
432
+ _selectedIndexes = [];
433
+ for (var i = indexes.length - 1; i >= 0; i--) {
434
+ var index = indexes[i];
435
+ if (index >= viewPort.from && index <= viewPort.to) {
436
+ unselectRow(indexes[i]);
437
+ var row = this.rowAt(index);
438
+ $self.trigger('rowSelection', [index, row, false, _currentData[index]]);
439
+ }
440
+ }
441
+ };
442
+
443
+ /**
444
+ * Returns all selected row indexes
445
+ */
446
+ this.selectedIndexes = function () {
447
+ var indexes = _selectedIndexes,
448
+ result = [];
449
+ for (var i = 0, l = indexes.length; i < l; i++)
450
+ result.push(currentIndexToOriginalIndex(indexes[i]));
451
+ return result;
452
+ };
453
+
454
+
455
+ /**
456
+ * Returns all selected row data
457
+ */
458
+ this.selectedData = function () {
459
+ var indexes = this.selectedIndexes(),
460
+ result = [];
461
+ for (var i = 0, l = indexes.length; i < l; i++)
462
+ result.push(_data[indexes[i]]);
463
+ return result;
464
+ };
465
+
466
+
467
+ /**
468
+ * Returns the last selcted row index
469
+ */
470
+ this.lastSelectedIndex = function () {
471
+ return this.selectedIndexes()[_selectedIndexes.length - 1];
472
+ };
473
+
474
+ /**
475
+ * Returns the last selected row data
476
+ */
477
+ this.lastSelectedData = function () {
478
+ return _data[this.lastSelectedIndex()];
479
+ };
480
+
481
+
482
+ /**
483
+ * Scrolls to row at the specified index
484
+ */
485
+ this.goTo = function (index) {
486
+
487
+ index = originalIndexToCurrentIndex(index);
488
+ var
489
+ // calculate row position
490
+ pos = (index * (options.rowHeight + options.borderHeight)),
491
+ // current scroll position
492
+ scrollTop = body_container[0].scrollTop,
493
+ _height = body_container[0].offsetHeight,
494
+ viewPort = getViewPort();
495
+
496
+ if ((pos >= scrollTop) && ((pos + options.rowHeight) <= (scrollTop + _height))) {
497
+ // track already shown
498
+ } else {
499
+ if ((pos + options.rowHeight) > (scrollTop + _height)) {
500
+ // row is positioned downside current viewport
501
+ scrollTop = scrollTop + ((pos + options.rowHeight) - (scrollTop + _height));
502
+ } else {
503
+ // row is positioned upside current viewport
504
+ scrollTop = pos;
505
+ }
506
+ }
507
+ // set new scrollTop position
508
+ body_container[0].scrollTop = parseInt(scrollTop, 10);
509
+
510
+ var row = null;
511
+ if (index >= viewPort.from && index <= viewPort.to) row = this.rowAt(index);
512
+
513
+ $self.trigger('scrollTo', [index, row]); // fire event
514
+ return row; // return row
515
+ };
516
+
517
+ /**************
518
+ * SORTABLE
519
+ **************/
520
+
521
+ /**
522
+ * Sorts table by specified column
523
+ * @param col integer value representing the column index
524
+ * @param ignoreCase if true compares objects by case-insensitive mode
525
+ */
526
+ this.sort = function (col, ignoreCase) {
527
+ var column = options.columns[col]; // get column data
528
+ if (options.canBeSorted(column) === false) return this;
529
+
530
+ var asc = true;
531
+ if (_asc[0] == col) {
532
+ asc = !_asc[1]; // get ascendent/descendent flag
533
+ } //else {
534
+ _currentData = $.introSort(_currentData, function (aRow, bRow) {
535
+ var aDatum = aRow[column.key],
536
+ bDatum = bRow[column.key];
537
+ if (ignoreCase && (typeof aDatum == 'string')) {
538
+ // transform into lower case if ignoreCase flag is true
539
+ aDatum = aDatum.toLowerCase();
540
+ bDatum = bDatum.toLowerCase();
541
+ }
542
+ if (options.sort[column.key] && options.sort[column.key].apply) {
543
+ return options.sort[column.key](aRow, bRow, asc);
544
+ } else {
545
+ return asc ? (aDatum < bDatum) : (aDatum > bDatum);
546
+ }
547
+ }, function (datum1, index1, datum2, index2) {
548
+ datum1._current_index = index1;
549
+ datum2._current_index = index2;
550
+ });
551
+
552
+ // empties older selected rows
553
+ _selectedIndexes = [];
554
+
555
+ _asc = [col, asc];
556
+ $self.trigger('columnSort', [col, column, _columns[col], asc, _columns]); // fire event
557
+ var viewPort = getViewPort();
558
+ newViewPort();
559
+ removeOlderRows(viewPort.from, viewPort.to);
560
+ _showData(true);
561
+
562
+ return this;
563
+ };
564
+
565
+ /*****************
566
+ * FILTERING
567
+ *****************/
568
+
569
+ /**
570
+ * Returns new filtered data objects
571
+ */
572
+ this.dataFilter = function (query) {
573
+ return filter(query, _data).data;
574
+ };
575
+
576
+ /**
577
+ * Returns new filtered data indexes
578
+ */
579
+ this.indexesFilter = function (query) {
580
+ return filter(query, _data).indexes;
581
+ };
582
+
583
+ /**
584
+ * Searches given text in the collection
585
+ * Returns new data collection length
586
+ */
587
+ this.search = function (text) {
588
+ if (text === undefined || text === null) text = '';
589
+ text = $.trim("" + text);
590
+ _queryText = text;
591
+
592
+ if (!text) {
593
+ return this.data(this.data());
594
+ }
595
+
596
+ this.clearSelection();
597
+
598
+ var result = filter(text, _data, true);
599
+
600
+ showData(result.data);
601
+
602
+ return result.data.length;
603
+ };
604
+
605
+ /*******************
606
+ * UTILITY
607
+ *******************/
608
+
609
+ /**
610
+ * Converts given index into current index shown
611
+ */
612
+ this.originalIndexToCurrentIndex = function (index) {
613
+ return originalIndexToCurrentIndex(index);
614
+ };
615
+
616
+ /**
617
+ * Returns given index into global index
618
+ */
619
+ this.currentIndexToOriginalIndex = function (index) {
620
+ return currentIndexToOriginalIndex(index);
621
+ };
622
+
623
+ /********************
624
+ * MANIPULATION
625
+ *******************/
626
+
627
+ /**
628
+ * Adds single row to collection at the specified position
629
+ */
630
+ this.addRow = function (position, row) {
631
+ return this.addRows.apply(this, arguments);
632
+ };
633
+
634
+ /**
635
+ * Adds more than one row to colelction at the specified position
636
+ * @param position integer representing the position which add new rows to
637
+ * @param Object... new rows data
638
+ */
639
+ this.addRows = function (position /*, rows ... */ ) {
640
+ var rows = Array.prototype.slice.call(arguments, 0);
641
+
642
+ if (isNaN(position)) {
643
+ position = _data.length;
644
+ } else {
645
+ rows.splice(0, 1);
646
+ }
647
+
648
+ args = rows.slice(0);
649
+
650
+ /*
651
+ * Building args
652
+ * It should be: [ position, 0, rows... ]
653
+ */
654
+
655
+ args.splice(0, 0, position, 0);
656
+
657
+ /*
658
+ * Add new row to collection
659
+ * It should be: _data.splice( position, 0, rows... )
660
+ */
661
+ Array.prototype.splice.apply(_data, args);
662
+
663
+
664
+ var positionToRedraw = position;
665
+ if (isFiltered()) {
666
+ positionToRedraw = _currentData.length;
667
+ var result = filter(_queryText, rows);
668
+ for (var i = 0, l = result.data.length; i < l; i++) {
669
+ result.data[i]._original_index = position + i;
670
+ result.data[i]._current_index = _currentData.push(result.data[i]) - 1;
671
+ }
672
+ }
673
+
674
+ var viewPort = getViewPort();
675
+
676
+ this.resize();
677
+
678
+ if (positionToRedraw >= viewPort.from && positionToRedraw <= viewPort.to) {
679
+ removeOlderRows(viewPort.from, viewPort.to);
680
+ _showData(true);
681
+ }
682
+
683
+ $self.trigger('newRows', [position, rows]);
684
+
685
+ return this;
686
+ };
687
+
688
+ /**
689
+ * Removes single row from collection at the specified postion
690
+ */
691
+ this.removeRow = function (position) {
692
+ return this.removeRows.apply(this, arguments);
693
+ };
694
+
695
+ /**
696
+ * Removes more than one row from collection
697
+ * @param Integer... all position to remove
698
+ */
699
+ this.removeRows = function ( /*position ... */ ) {
700
+
701
+ var
702
+ indexes = Array.prototype.slice.call(arguments, 0),
703
+ removedRows = [],
704
+ viewPort = getViewPort(),
705
+ redraw = false;
706
+
707
+ // Sort indexes from greater to lesser
708
+ indexes = unique(indexes).sort(function (a, b) {
709
+ return a < b;
710
+ });
711
+
712
+
713
+ for (var i = 0, j = 0, l = indexes.length, b, c, num = 1; i < l; i++) {
714
+ b = indexes[i];
715
+ c = indexes[i + 1];
716
+
717
+ $self.trigger('removeData', [b, _data[b]]); // fire event on single row
718
+ redraw = (b >= viewPort.from && b <= viewPort.to);
719
+
720
+ unselectRowOnRemoveRow(b);
721
+
722
+ // calculate sequential indexes
723
+ if ((b - 1) == c) {
724
+ num++;
725
+ continue;
726
+ }
727
+
728
+ var removed = Array.prototype.splice.apply(_data, [indexes[j + (num - 1)], num]);
729
+ removedRows = removedRows.concat(removed);
730
+
731
+ j = i + 1;
732
+ num = 1;
733
+ }
734
+
735
+ if (removedRows.length && isFiltered()) {
736
+ var result = filter(_queryText, removedRows);
737
+ if (result.data.length) {
738
+ for (var i = result.data.length - 1; i >= 0; i--) {
739
+ var datum = result.data[i];
740
+ if (datum._current_index === undefined) continue;
741
+ Array.prototype.splice.apply(_currentData, [datum._current_index, 1]);
742
+
743
+ redraw = redraw || (datum._current_index >= viewPort.from && datum._current_index <= viewPort.to);
744
+ }
745
+ } else {
746
+ redraw = false;
747
+ }
748
+ }
749
+
750
+ if (redraw) {
751
+ this.resize();
752
+
753
+ removeOlderRows(viewPort.from, viewPort.to);
754
+ _showData(true);
755
+ }
756
+
757
+ return this;
758
+
759
+ };
760
+
761
+ /**
762
+ * Replaces single row at the specified position with new given row data
763
+ */
764
+ this.replaceRow = function (position, row) {
765
+ return this.replaceRows.apply(this, [
766
+ [position, row]
767
+ ]);
768
+ };
769
+
770
+ /**
771
+ * Replaces more than one row from collection a the specified position
772
+ * @param Array... a grouped 'position, row' for each row you want to replace
773
+ */
774
+ this.replaceRows = function ( /* [position, row] ... */ ) {
775
+
776
+ var args = Array.prototype.slice.call(arguments, 0),
777
+ redraw = false;
778
+ viewPort = getViewPort();
779
+
780
+ for (var i_arg = 0, l_arg = args.length; i_arg < l_arg; i_arg++) {
781
+ var
782
+ arg = args[i_arg],
783
+ position = arg[0],
784
+ row = arg[1],
785
+ index = position;
786
+
787
+ $self.trigger('replaceData', [position, _data[position], row]);
788
+
789
+ redraw = redraw || (index >= viewPort.from && index <= viewPort.to);
790
+
791
+ var removedRow = Array.prototype.splice.apply(_data, [index, 1, row]);
792
+
793
+ index = !isFiltered() ? index : (function () {
794
+ if (filter(_queryText, [removedRow[0]], false).data.length) return removedRow[0]._current_index;
795
+ else return undefined;
796
+ })();
797
+
798
+ if (index !== undefined && isFiltered()) {
799
+
800
+ /*
801
+ if (filter(_queryText, [row]).data.length) {
802
+ row._original_index = position;
803
+ row._current_index = index;
804
+ Array.prototype.splice.apply(_currentData, [index, 1, row]);
805
+ } else {
806
+ */
807
+ Array.prototype.splice.apply(_currentData, [index, 1, row]);
808
+
809
+ // Restore index correctly
810
+ row._current_index = removedRow[0]._current_index;
811
+ row._original_index = removedRow[0]._original_index;
812
+ //}
813
+ redraw = redraw || (index >= viewPort.from && index <= viewPort.to);
814
+ }
815
+ }
816
+
817
+ if (redraw) {
818
+ this.resize();
819
+ removeOlderRows(viewPort.from, viewPort.to);
820
+ _showData(true);
821
+ }
822
+
823
+ return this;
824
+ };
825
+
826
+
827
+
828
+ /**
829
+ * EVENTS
830
+ */
831
+
832
+
833
+ /**
834
+ * Adds event to each row
835
+ */
836
+ this.addRowsEvent = function (type, fn) {
837
+ body.delegate('div.' + options.rowCss, type, fn);
838
+ return self;
839
+ };
840
+
841
+ /**
842
+ * Adds event to TableRender object
843
+ */
844
+ this.addTableEvent = function (type, fn) {
845
+ $self.bind(type, fn);
846
+ return self;
847
+ };
848
+
849
+ /**
850
+ * Removes event from table object
851
+ */
852
+ this.removeTableEvent = function (type, fn) {
853
+ $self.unbind(type, fn);
854
+ return self;
855
+ };
856
+
857
+ /**
858
+ * Removes event from table rows
859
+ */
860
+ this.removeRowsEvent = function (type, fn) {
861
+ body.undelegate('div.' + options.rowCss, type, fn);
862
+ return self;
863
+ };
864
+
865
+
866
+
867
+ /********************
868
+ * PRIVATE METHODS
869
+ ********************/
870
+
871
+
872
+ function getColumnIndexByKey(key) {
873
+ var index = -1;
874
+ $.each(_columns, function (i, item) {
875
+ if (item.key == key) {
876
+ index = i;
877
+ return false;
878
+ }
879
+ });
880
+ return index;
881
+ }
882
+
883
+
884
+ /**
885
+ * Shows or hides a column at the specified index
886
+ */
887
+ function showHideColumn(index, show) {
888
+
889
+ if (typeof index != 'number' && typeof index != 'string') {
890
+ return false;
891
+ }
892
+
893
+ var col_index = -1;
894
+ if (typeof index == 'number') {
895
+ if (_columns[index]) {
896
+ col_index = index;
897
+ }
898
+ } else {
899
+
900
+ col_index = getColumnIndexByKey(index);
901
+
902
+ }
903
+
904
+ if (col_index == -1) {
905
+ return false;
906
+ }
907
+
908
+ _columns[col_index].hidden = !show;
909
+
910
+ if (_header_drawn) {
911
+ // Redraw header
912
+ self.drawHeader();
913
+ }
914
+
915
+ // TODO: do we have to redraw the body of the table?
916
+ _refreshViewPort();
917
+
918
+
919
+ $self.trigger(((show ? 'show' : 'hide') + '_column'), [_columns[col_index], col_index]);
920
+ return true;
921
+ }
922
+
923
+
924
+ /*****************
925
+ * FILTERING
926
+ *****************/
927
+
928
+ /**
929
+ * Filters data collection
930
+ * @param query String used to filter data
931
+ * @param data Array the collection to filter
932
+ * @param attachIndex boolean if true new indexes will be attached to each object
933
+ */
934
+ function filter(query, data, attachIndex) {
935
+
936
+ query = ("" + query).toLowerCase();
937
+ var result = {
938
+ indexes: [],
939
+ data: []
940
+ };
941
+
942
+ for (var i = 0, l = data.length; i < l; i++) {
943
+ var found = false;
944
+ for (var c = 0, lc = _columns.length; c < lc; c++) {
945
+ if (attachIndex) {
946
+ data[i]._original_index = i; // store original index
947
+ }
948
+ var str = data[i][ _columns[c].key ];
949
+ if (("" + str).toLowerCase().indexOf(query) != -1) {
950
+ result.indexes.push(i);
951
+ var currentIndex = result.data.push( data[i] );
952
+ if (attachIndex) {
953
+ data[i]._current_index = (currentIndex - 1);
954
+ }
955
+ found = true;
956
+ break;
957
+ }
958
+ }
959
+ if (!found && attachIndex) data[i]._current_index = i;
960
+ }
961
+ return result;
962
+ }
963
+
964
+ /******************
965
+ * SELECTION
966
+ ******************/
967
+
968
+ /**
969
+ * Marks row as selected intercepting row events
970
+ */
971
+ function rowSelection(e) {
972
+ var
973
+ // get current HTML row object
974
+ currentRow = this,
975
+ // get current HTML row index
976
+ index = currentRow.offsetTop / (options.rowHeight + options.borderHeight);
977
+
978
+ if (!options.multiselection)
979
+ // mark all other selected row as unselected
980
+ self.clearSelection();
981
+
982
+
983
+ if (!(e.shiftKey || e.metaKey || e.ctrlKey))
984
+ // clear selected row
985
+ self.clearSelection();
986
+
987
+ if (e.shiftKey && options.multiselection) {
988
+ // Shift is pressed
989
+ var
990
+ _lastSelectedIndex = lastSelectedIndex(),
991
+ // get last selected index
992
+ from = Math.min(_lastSelectedIndex + 1, index),
993
+ to = Math.max(_lastSelectedIndex, index),
994
+ viewPort = getViewPort();
995
+
996
+ // select all rows between interval
997
+ for (var i = from; i <= to && _currentData[i]; i++) {
998
+ if ($.inArray(i, selectedIndexes()) == -1) {
999
+ selectRow(i);
1000
+ if (i >= viewPort.from && i <= viewPort.to) {
1001
+ var row = self.rowAt(i);
1002
+ $self.trigger('rowSelection', [i, row, true, _currentData[i]]);
1003
+ }
1004
+ }
1005
+ }
1006
+
1007
+ } else if (e.ctrlKey || e.metaKey) { /* Ctrl is pressed ( CTRL on Mac is identified by metaKey property ) */
1008
+
1009
+ // toggle selection
1010
+ if ($.inArray(index, selectedIndexes()) > -1) {
1011
+ unselectRow(index);
1012
+ $self.trigger('rowSelection', [index, this, false, _currentData[index]]);
1013
+ } else {
1014
+ selectRow(index);
1015
+ $self.trigger('rowSelection', [index, this, true, _currentData[index]]);
1016
+ }
1017
+
1018
+ } else {
1019
+ // simple click
1020
+ selectRow(index);
1021
+ $self.trigger('rowSelection', [index, this, true, _currentData[index]]);
1022
+ }
1023
+
1024
+ }
1025
+
1026
+ /**
1027
+ * Returns all selected indexes
1028
+ */
1029
+ function selectedIndexes() {
1030
+ return _selectedIndexes;
1031
+ }
1032
+
1033
+ /**
1034
+ * Returns last selected index
1035
+ */
1036
+ function lastSelectedIndex() {
1037
+ return selectedIndexes()[selectedIndexes().length - 1];
1038
+ }
1039
+
1040
+ /**
1041
+ * Adds the specified row index to selected row indexes collection
1042
+ */
1043
+ function selectRow(index) {
1044
+ if (index === undefined || index < 0 || index >= _currentData.length) return;
1045
+ selectedIndexes().push(index);
1046
+ }
1047
+
1048
+ /**
1049
+ * Remove the specified row index from selected row indexes collection
1050
+ */
1051
+ function unselectRow(index) {
1052
+ if (index === undefined || index < 0 || index >= _currentData.length) return;
1053
+
1054
+ var pos = $.inArray(index, selectedIndexes());
1055
+ if (pos == -1) return;
1056
+
1057
+ selectedIndexes().splice(pos, 1);
1058
+ }
1059
+
1060
+ /*************************
1061
+ * MANIPULATING
1062
+ *************************/
1063
+
1064
+ /**
1065
+ * Marks the row at the given position as unselect and fires the correct event
1066
+ */
1067
+ function unselectRowOnRemoveRow(index) {
1068
+ var pos = $.inArray(index, self.selectedIndexes());
1069
+ if (pos != -1) {
1070
+ if (isFiltered()) {
1071
+ var result = filter(_queryText, [_data[index]]);
1072
+ if (result.data.length) {
1073
+ var datum = result.data[0];
1074
+ if (datum._current_index !== undefined) {
1075
+ unselectRow(datum._current_index);
1076
+ var row = self.rowAt(datum._current_index);
1077
+ $self.trigger('rowSelection', [index, row, false, _currentData[datum._current_index]]);
1078
+ }
1079
+ }
1080
+ } else {
1081
+ unselectRow(index);
1082
+ var row = self.rowAt(index);
1083
+ $self.trigger('rowSelection', [index, row, false, _currentData[index]]);
1084
+ }
1085
+ }
1086
+
1087
+ var currentIndex = !isFiltered() ? index : (function () {
1088
+ var datum = _data[index];
1089
+ if (filter(_queryText, [datum]).data.length) return datum._current_index;
1090
+ else return undefined;
1091
+ })();
1092
+
1093
+ if (currentIndex === undefined) return;
1094
+
1095
+ /** Replace current selected indexes */
1096
+ var indexes = unique(selectedIndexes()).sort(function (a, b) {
1097
+ return a > b;
1098
+ });
1099
+ _selectedIndexes = [];
1100
+ for (var i = 0; i < indexes.length; i++) {
1101
+ _selectedIndexes.push(currentIndex > indexes[i] ? indexes[i] : (indexes[i] - 1));
1102
+ }
1103
+
1104
+ }
1105
+
1106
+ /********************
1107
+ * UTILITY
1108
+ ********************/
1109
+
1110
+ /**
1111
+ * Returns the global index by given current index
1112
+ */
1113
+ function currentIndexToOriginalIndex(index) {
1114
+ if (!isFiltered()) return index;
1115
+ var datum = _currentData[index];
1116
+ if (datum._original_index === undefined) return index;
1117
+ return datum._original_index;
1118
+ }
1119
+
1120
+ /**
1121
+ * Returns the current index by given global index
1122
+ */
1123
+ function originalIndexToCurrentIndex(index) {
1124
+ if (!isFiltered()) return index;
1125
+ var datum = _data[index];
1126
+ if (datum._current_index === undefined) return index;
1127
+ return datum._current_index;
1128
+ }
1129
+
1130
+ /**
1131
+ * Returns true if shown data is filtered
1132
+ */
1133
+ function isFiltered() {
1134
+ return (_queryText !== undefined && _queryText.length);
1135
+ }
1136
+
1137
+ /**
1138
+ * Prevent bug:
1139
+ * jQuery.unique doesn't work fine with array of integers
1140
+ */
1141
+ function unique(array) {
1142
+ var result = [];
1143
+ for (var i = 0, n = array.length; i < n; i++) {
1144
+ var found = false;
1145
+ for (var x = i + 1; x < n; x++) {
1146
+ if (array[x] == array[i]) {
1147
+ found = true;
1148
+ break;
1149
+ }
1150
+ }
1151
+ if (found) continue;
1152
+ result.push(array[i]);
1153
+ }
1154
+ return result;
1155
+ }
1156
+
1157
+ /*******************
1158
+ * LAYOUT
1159
+ *******************/
1160
+
1161
+ /**
1162
+ * Resets all viewport coordinates
1163
+ */
1164
+ function resetViewPorts() {
1165
+ _oldViewPort = {
1166
+ from: -1,
1167
+ to: -1,
1168
+ height: 0
1169
+ };
1170
+ _viewPort = {
1171
+ from: 0,
1172
+ to: 0,
1173
+ height: 0
1174
+ };
1175
+ }
1176
+
1177
+ /**
1178
+ * Returns new viewport coordinates
1179
+ */
1180
+ function newViewPort() {
1181
+ _oldViewPort = _viewPort;
1182
+ return (_viewPort = getViewPort());
1183
+ }
1184
+
1185
+ /**
1186
+ * Calculates the viewport coordinates
1187
+ */
1188
+ function getViewPort() {
1189
+ var
1190
+ scrollTop = body_container[0].scrollTop,
1191
+ bodyHeight = body_container[0].offsetHeight,
1192
+ // calculate the start index
1193
+ from = parseInt(scrollTop / (options.rowHeight + options.borderHeight), 10),
1194
+ // calculate the end index
1195
+ to = from + parseInt((body_container[0].offsetHeight / (options.rowHeight + options.borderHeight)) * 1.5, 10);
1196
+ return {
1197
+ from: Math.max(from - options.threshold, 0),
1198
+ to: to + options.threshold,
1199
+ height: from + to
1200
+ };
1201
+ }
1202
+
1203
+ /**********************
1204
+ * RENDERING
1205
+ **********************/
1206
+
1207
+ /**
1208
+ * Manages the scroll event
1209
+ */
1210
+ function _scroll(e) {
1211
+ if (_scrollTimer) {
1212
+ clearTimeout(_scrollTimer);
1213
+ }
1214
+ _scrollTimer = setTimeout(function () {
1215
+ if (_waiting) return;
1216
+ var scrollTop = body_container[0].scrollTop;
1217
+ newViewPort();
1218
+ _showData();
1219
+ $self.trigger('scroll', [scrollTop, body[0].offsetHeight, body_container[0].offsetHeight]); // fire event
1220
+ }, 1);
1221
+ e.preventDefault(); // prevent default function
1222
+ return false; // stop event
1223
+ }
1224
+
1225
+ /**
1226
+ * Prepares table to add data
1227
+ */
1228
+ function showData(data) {
1229
+
1230
+ _waiting = true;
1231
+
1232
+ _currentData = data;
1233
+ _shownData = new Array(data.length);
1234
+
1235
+ body_container[0].scrollTop = 0;
1236
+
1237
+ body[0].innerHTML = '';
1238
+
1239
+ body.addClass('loading');
1240
+
1241
+ self.resize();
1242
+
1243
+ resetViewPorts();
1244
+ newViewPort();
1245
+
1246
+ //setTimeout(function() {
1247
+ _showData();
1248
+ _waiting = false;
1249
+ //}, 1000);
1250
+ body.removeClass('loading');
1251
+ }
1252
+
1253
+
1254
+
1255
+ function _refreshViewPort(){
1256
+ var
1257
+ from = _viewPort.from,
1258
+ to = _viewPort.to,
1259
+ total = to - from;
1260
+
1261
+ Array.prototype.splice.call( _shownData, [ from, total ].concat( new Array(total) ) );
1262
+ body[0].innerHTML = '';
1263
+ renderTable(from, to);
1264
+ }
1265
+
1266
+ /**
1267
+ * Renders table showing data
1268
+ * @param skipRemove if true older rows will be kept in table
1269
+ */
1270
+ function _showData(skipRemove) {
1271
+ var x1 = _oldViewPort.from,
1272
+ x2 = _oldViewPort.to,
1273
+ y1 = _viewPort.from,
1274
+ y2 = _viewPort.to,
1275
+ from = y1,
1276
+ to = y2,
1277
+ removeFrom, removeTo;
1278
+
1279
+ if (y1 > x1 && y1 < x2) {
1280
+ from = x2;
1281
+ to = Math.max(to, x2);
1282
+ removeFrom = x1;
1283
+ removeTo = y1 - 1;
1284
+ } else if (y2 > x1 && y2 < x2) {
1285
+ removeFrom = to + 1;
1286
+ removeTo = x2;
1287
+ to = Math.min(to, x1);
1288
+ } else if ((y1 > x2 || y2 < x1) && (x1 != -1 && x2 != -1)) {
1289
+ removeFrom = x1;
1290
+ removeTo = x2;
1291
+ }
1292
+
1293
+ renderTable(from, to);
1294
+
1295
+ if (!options.empties || skipRemove) return;
1296
+
1297
+ removeOlderRows(removeFrom, removeTo);
1298
+ }
1299
+
1300
+ /**
1301
+ * Removes rows that are no longer shown
1302
+ */
1303
+ function removeOlderRows(from, to) {
1304
+ for (var i = from; i <= to; i++) {
1305
+ if (_shownData[i] === undefined) continue;
1306
+ _shownData[i].parentNode.removeChild(_shownData[i]);
1307
+ _shownData[i] = undefined;
1308
+ }
1309
+ }
1310
+
1311
+ /**
1312
+ * Builds table
1313
+ */
1314
+ function renderTable(from, to) {
1315
+ for (var i = from; i <= to && _currentData[i]; i++) {
1316
+ var row = (_shownData[i] === undefined) ? renderRow(i) : redrawRow(i);
1317
+
1318
+ if (row && (!row.parentNode || row.parentNode !== body[0])) {
1319
+ var older_row = _shownData[i];
1320
+
1321
+ if ( older_row && (older_row.parentNode === body[0]) ){
1322
+ // Row has been already injected to body. Replace child with the new one
1323
+ body[0].replaceChild(row, older_row);
1324
+ } else {
1325
+ body[0].appendChild(row);
1326
+ }
1327
+ _shownData[i] = row;
1328
+ }
1329
+ // (function (_row, _index) {
1330
+ // return setTimeout(function () {
1331
+ // if ($.inArray(_index, _selectedIndexes) > -1) {
1332
+ // $self.trigger('rowSelection', [_index, _row, true, _currentData[_index]]);
1333
+ // }
1334
+ // }, 1);
1335
+ // })(row, i);
1336
+ }
1337
+ }
1338
+
1339
+
1340
+ /**
1341
+ * Build single row
1342
+ */
1343
+ function renderRow(index) {
1344
+ if (!_currentData[index]) return null;
1345
+
1346
+ var
1347
+ datum = _currentData[index],
1348
+
1349
+ row = $(options.rowRender(datum, self.columns(), index, $.inArray(index, _selectedIndexes) > -1))[0];
1350
+
1351
+ $(row).css({
1352
+ 'top': (index * (options.rowHeight + options.borderHeight)),
1353
+ 'height': options.rowHeight
1354
+ });
1355
+
1356
+ return row;
1357
+ }
1358
+
1359
+
1360
+ /**
1361
+ * Draw row at the specified position
1362
+ */
1363
+ function redrawRow(index) {
1364
+ var row;
1365
+ if ((row = _shownData[index]) === undefined)
1366
+ return; // no row to redraw was found
1367
+ var newTop = (index * (options.rowHeight + options.borderHeight)); // calculate new row position
1368
+ if (row.offsetTop != newTop) {
1369
+ if (options.animate) {
1370
+ $(row).animate({
1371
+ top: newTop
1372
+ });
1373
+ } else {
1374
+ row.style.top = newTop + 'px'; // set new position ( faster than jQuery function )
1375
+ }
1376
+ }
1377
+ return row;
1378
+ }
1379
+
1380
+ /**
1381
+ * Renders single row
1382
+ * This method can be overwritten using 'options.rowRender'
1383
+ */
1384
+ function _rowRender(datum, columns, index, selected) {
1385
+ // Faster than jQuery functions
1386
+
1387
+ var row = document.createElement('div');
1388
+ row.id = "row_" + index;
1389
+
1390
+ var cols = columns;
1391
+ $.each(cols, function(i, col){
1392
+ var el = document.createElement('div');
1393
+ el.id = "row_" + index + "_column_" + col.key;
1394
+ el.innerHTML = datum[ col.key ];
1395
+ $(el).addClass("column col_" + i);
1396
+ if ( col.hidden ){
1397
+ $(el).addClass('column_hidden');
1398
+ }
1399
+ $(row).append( el );
1400
+ });
1401
+
1402
+ if (row) {
1403
+ $(row).attr('style', "position:absolute;left:0px;right:0px;").addClass(options.rowCss);
1404
+ if ( selected ){
1405
+ $(row).addClass("selected");
1406
+ }
1407
+ }
1408
+ return $(row); // browser compatibility; return a jQuery object
1409
+ }
1410
+
1411
+ /**
1412
+ * Renders the table header
1413
+ * This method can be overwritten using 'options.rowRender'
1414
+ */
1415
+ function headRender(index, columnData, columns) {
1416
+ var el = $('<div style="float:left;" class="column col_' + index + ' col_' + columnData.key + '" >' + columnData.label + '</div>');
1417
+ if (columnData.hidden) {
1418
+ el.addClass('column_hidden');
1419
+ } else {
1420
+ el.removeClass('column_hidden');
1421
+ }
1422
+ return el;
1423
+ }
1424
+
1425
+ /**
1426
+ * Returns true if column can be sorted
1427
+ * This method can be overwritten using 'options.rowRender'
1428
+ */
1429
+ function canBeSorted() {
1430
+ return true;
1431
+ }
1432
+
1433
+
1434
+
1435
+
1436
+
1437
+ $(options.columns).each(function (i, col) {
1438
+ // add column
1439
+ self.addColumn(col, i);
1440
+ });
1441
+
1442
+ if (options.selection) {
1443
+ // bind the selection event
1444
+ body.delegate('div.' + options.rowCss, 'click', rowSelection);
1445
+ }
1446
+
1447
+
1448
+ this.drawHeader();
1449
+
1450
+ }
1451
+
1452
+ /**
1453
+ * Attachs TableRender functions to matched HTML object
1454
+ * @param {Object} opt
1455
+ */
1456
+ $.fn.tablerender = function (opt) {
1457
+ var _arguments = Array.prototype.slice.call(arguments, 0);
1458
+ // Instanciates new TableRender class
1459
+ var element = this[0];
1460
+ var klass = $(element).data("_tablerender");
1461
+ if (!klass) {
1462
+ klass = new TableRender(element, typeof opt == 'object' ? opt : {});
1463
+ } else {
1464
+ var
1465
+ action = _arguments[0],
1466
+ args = _arguments.slice(1);
1467
+
1468
+ if (klass[action] && klass[action].apply) {
1469
+ return klass[action].apply(klass, args);
1470
+ } else {
1471
+ return null
1472
+ }
1473
+ }
1474
+ return this;
1475
+ };
1476
+
1477
+
1478
+ if (!$.introSort) {
1479
+ if (console) {
1480
+ console.warn("No $.introSort function found");
1481
+ }
1482
+ }
1483
+ })(jQuery);