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.
- checksums.yaml +7 -0
- data/.gitignore +27 -0
- data/Changelog.md +3 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/README.md +69 -0
- data/Rakefile +2 -0
- data/lib/tablerender-rails.rb +12 -0
- data/lib/tablerender-rails/engine.rb +8 -0
- data/lib/tablerender-rails/railtie.rb +5 -0
- data/lib/tablerender-rails/version.rb +5 -0
- data/tablerender-rails.gemspec +22 -0
- data/vendor/assets/javascripts/tablerender.js +1483 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
@@ -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
|
data/Changelog.md
ADDED
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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 );
|
data/Rakefile
ADDED
@@ -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);
|