raydreams.js
10.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
/**
* Ray Tools
* Copyright (c) 2016-2018 Tag Guillory
* Created : 2016-Feb-18
* Last Update : 2018-Feb-11
* Version : 0.9
**/
(function ($) {
// object that holds all the settings and properties which is externally visible i.e. NOT private
var base = {
datasource: { data: [], keyfield: null },
headers: [], //column definitions
pageSize: 25, // current page size
currentPageIdx: 0, // current page index - zero based
maxPageButtons: 10, // the maximum number of pager buttons to display
parentElem: null, // the base HTML element
data: loadData, // function reference to set the data
rowNumbers: true, // whether to add a column with line numbers in it
currentSort: null, // the current field and direction of sorting
currentSelection: null, // last clicked row
onRowClick: null // external handler when a row is clicked
};
// iterates the data and fills in the table body
function loadData(data, keyField) {
// remove all data records from the table
base.parentElem.find("tbody > tr").remove();
base.datasource.data = data;
base.datasource.keyfield = keyField;
var startRow = (base.currentPageIdx * base.pageSize) + 1; // row we started counting on
var curRow = startRow;
// foreach record of data
for (var row = startRow - 1; row < ((base.currentPageIdx + 1) * base.pageSize) && row < data.length; ++row)
{
// start a new row
var rowStr = jQuery('<tr></tr>');
if (keyField != null)
//rowStr.attr( 'data-ray-key', data[row][keyField] );
rowStr.data( 'ray-key', data[row][keyField] );
// add a column to hold row numbers
if (base.rowNumbers) {
rowStr.append('<td>' + (row + 1) + '</td>');
}
// set the col index if there are row numbers
var col = (base.rowNumbers) ? 1 : 0;
// for each header column
for (; col < base.headers.length; ++col) {
// get this field name
var fieldName = base.headers[col].field;
// start the td
var cell = jQuery('<td></td>');
// the render func returns false
if ( base.headers[col].renderIf != null && !base.headers[col].renderIf(data[row]) )
{
cell.append(" ");
rowStr.append(cell);
continue;
}
// foreach icon in the column
if (base.headers[col].icons != null && base.headers[col].icons.length > 0)
{
jQuery.each(base.headers[col].icons, function (idx, ic) {
var colBtn = jQuery("<span class='glyphicon' aria-hidden='true' />");
colBtn.addClass(ic.glyph);
if (ic.handler != null)
cell.on('click', null, {handler:ic.handler, data: { rowIdx: row, id: data[row][keyField] } }, doIconClick );
if (ic.data != null)
colBtn.data('ray-data', data[row][ic.data]);
cell.append(colBtn);
});
}
// add cell data
if (fieldName != null)
{
if ( base.headers[col].format != null )
cell.append( base.headers[col].format( data[row] ) );
else
cell.append(data[row][fieldName]);
}
// if empty add a space
if ( jQuery.trim(cell.html()).length < 1 ) {
cell.append(" ");
}
rowStr.append(cell);
}
// add onClick event handler to the row
rowStr.on('click', null, { rowIdx: row, id: data[row][keyField] }, doRowClick );
// append the row
$(base.parentElem).find('table > tbody').append(rowStr);
++curRow;
}
// update the footer
renderFooter({ start: startRow, end: curRow - 1, total: data.length });
};
// sets all the options
jQuery.fn.raytable = function (options) {
// remember the base element
base.parentElem = $(this);
// test the root tag is either div or table, we want to put a div around a table
if (base.parentElem.prop("tagName").toLowerCase() != 'div') {
alert('Parent element must be a div tag!');
return;
}
// if there are no options, then don't change anything, else merge existing options for the same instance
// get the input options
base.datasource.data = options.datasource.data;
base.datasource.keyfield = options.datasource.keyfield;
if (options.pagesize != null && options.pagesize > 0)
base.pageSize = options.pagesize;
if (options.maxPageButtons != null && options.maxPageButtons > 0)
base.maxPageButtons = options.maxPageButtons;
base.rowNumbers = options.rowNumbers;
base.onRowClick = options.rowClickHandler;
// set the headers
if (options.columns != null && options.columns.length > 0) {
base.headers = options.columns;
}
else // use the HTML table headers
{
var ths = base.parentElem.find('thead tr').children();
// create header objects based on the html tags
for (var j = 0; j < ths.length; ++j) {
// find the data-ray-field attr
var field = $(ths[j]).data('ray-field');
var title = $(ths[j]).text();
base.headers.push({ field: field, title: title });
}
}
// add a column to hold row numbers
if (base.rowNumbers) {
options.columns.unshift({ field: "rowNum", title: "Row" });
}
// render header
renderTable();
// if data has been specified, then go ahead an load it, even empty data will cause this to run
loadData(base.datasource.data, base.datasource.keyfield);
// render footer
renderFooter();
// returning base exposes it publicly
return base;
};
// creates the skeleton of the table
function renderTable() {
// skeleton of the table
var skel = jQuery('<table class="table table-striped table-bordered" style="margin-bottom:0px;"><thead><tr></tr></thead><tbody></tbody></table>');
// add each header
jQuery.each(base.headers, function (idx, h) {
var cell = jQuery('<th>'+h.title+'</th>');
if (h.width != undefined)
cell.css('width', h.width+'px');
if (h.sort)
{
var sortBtn = jQuery("<span class='glyphicon glyphicon-sort-by-attributes' style='color:LightGray' aria-hidden='true' />");
cell.append(' ');
cell.append(sortBtn);
sortBtn.on('click', null, h.field, doSortCol);
}
skel.find('tr').append(cell);
});
// add the table header and body to the parent elem
base.parentElem.append(skel);
// add a place holder for the footer
base.parentElem.append('<div id="raytable-footer" style="padding:5px;"><span id="raytable-footer-summary" style="float:right;">0 - 0 of 0 items</span></div>');
}
// creates and appends the footer to the bottom of the table
// params include { start: startRow, end: endRow, total: dataLength }
function renderFooter(params) {
if (!params)
return;
// clear out the footer
base.parentElem.find('#raytable-footer').empty();
// update the summary
base.parentElem.find('#raytable-footer-summary').text(params.start + ' - ' + params.end + ' of ' + params.total + ' items');
// render pagination control
var pager = jQuery('<ul class="pagination pagination-sm" id="raytable-footer-pager" style="margin-top:0px;"></ul>');
base.parentElem.find('#raytable-footer').append(pager);
// there are more items than page size
if ( params.total > base.pageSize )
{
// the maximum number of pages needed
var maxPage = Math.ceil(params.total / base.pageSize);
if ( base.maxPageButtons < 2 )
base.maxPageButtons = 2;
if ( base.maxPageButtons > maxPage )
base.maxPageButtons = maxPage;
var startPage = base.currentPageIdx - Math.floor(base.maxPageButtons / 2);
if (startPage < 0)
{ startPage = 0; }
if ( maxPage - startPage < base.maxPageButtons )
{ startPage = maxPage - base.maxPageButtons; }
var first = jQuery('<li><a href="#" data="0" aria-label="Previous">«</a></li>');
first.on("click", changePage);
pager.append(first);
var page = 0;
for (page = startPage; page < startPage + base.maxPageButtons && page * base.pageSize < params.total ; ++page)
{
var li = (page == base.currentPageIdx) ? jQuery('<li class="active"></li>') : jQuery('<li></li>');
var anchor = jQuery('<a href="#" data="' + page + '">' + (page + 1) + '</a>');
li.append(anchor);
anchor.on("click", changePage);
pager.append(li);
}
var last = jQuery('<li><a href="#" data="' + (maxPage-1) + '" aria-label="Next">»</a></li>');
last.on("click", changePage);
pager.append(last);
}
var summary = '<span id="raytable-footer-summary" style="float:right;">' + params.start + ' - ' + params.end + ' of ' + params.total + ' items</span>';
base.parentElem.find('#raytable-footer').append(summary);
}
// loads a different page of data
function changePage(event) {
// get the page from the button element
var button = jQuery(event.target);
var page = parseInt(button.attr('data'));
// validata page bounds
base.currentPageIdx = page;
// reload the data
loadData(base.datasource.data, base.datasource.keyfield);
}
// when a glyph icon is clicked
function doIconClick(event) {
event.stopPropagation();
// set external handler
var handler = event.data.handler;
// set the current selection
base.currentSelection = event.data.data;
// forward the event
event.data = event.data.data;
handler(event);
}
// when a row is clicked on
function doRowClick(event) {
base.currentSelection = event.data;
base.onRowClick(event);
}
// sorts the bound data
function doSortCol(event) {
if (base.currentSort == null)
{ base.currentSort = { field: event.data, direction: 1 }; }
else if (base.currentSort.field != event.data)
{ base.currentSort = { field: event.data, direction: base.currentSort.direction }; }
else
{ base.currentSort = { field: event.data, direction: base.currentSort.direction * -1 }; }
// sort the data
base.datasource.data.sort( dynamicSort(event.data));
// reload the date and page back to start
base.currentPageIdx = 0;
loadData(base.datasource.data, base.datasource.keyfield);
// change the glyph 1=asc, -1=desc
if (base.currentSort.direction > 0)
{
base.parentElem.find('thead th span').each(
function (idx, elem) {
jQuery(elem).removeClass('glyphicon-sort-by-attributes-alt');
jQuery(elem).addClass('glyphicon-sort-by-attributes');
jQuery(elem).css('color', 'LightGray');
}
);
}
else
{
base.parentElem.find('thead th span').each(
function (idx, elem) {
jQuery(elem).removeClass('glyphicon-sort-by-attributes');
jQuery(elem).addClass('glyphicon-sort-by-attributes-alt');
jQuery(elem).css('color', 'LightGray');
}
);
}
jQuery(event.target).css('color', 'Black');
}
// sort by a specified property, use prefix '-' to reverse the sort direction
function dynamicSort(property) {
var sortOrder = base.currentSort.direction;
return function (a, b) {
var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
return result * sortOrder;
}
}
// internal debug handler
function debug(event) {
alert('Debugging');
}
}(jQuery));