tablerender-rails 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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);