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