· 5 years ago · Mar 27, 2020, 02:54 PM
1/*!
2 * FooTable - Awesome Responsive Tables
3 * Version : 2.0.1.4
4 * http://fooplugins.com/plugins/footable-jquery/
5 *
6 * Requires jQuery - http://jquery.com/
7 *
8 * Copyright 2014 Steven Usher & Brad Vincent
9 * Released under the MIT license
10 * You are free to use FooTable in commercial projects as long as this copyright header is left intact.
11 *
12 * Date: 16 Feb 2014
13 */
14(function ($, w, undefined) {
15 w.footable = {
16 options: {
17 delay: 100, // The number of millseconds to wait before triggering the react event
18 breakpoints: { // The different screen resolution breakpoints
19 phone: 480,
20 tablet: 1024
21 },
22 parsers: { // The default parser to parse the value out of a cell (values are used in building up row detail)
23 alpha: function (cell) {
24 return $(cell).data('value') || $.trim($(cell).text());
25 },
26 numeric: function (cell) {
27 var val = $(cell).data('value') || $(cell).text().replace(/[^0-9.\-]/g, '');
28 val = parseFloat(val);
29 if (isNaN(val)) val = 0;
30 return val;
31 }
32 },
33 addRowToggle: true,
34 calculateWidthOverride: null,
35 toggleSelector: ' > tbody > tr:not(.footable-row-detail)', //the selector to show/hide the detail row
36 columnDataSelector: '> thead > tr:last-child > th, > thead > tr:last-child > td', //the selector used to find the column data in the thead
37 detailSeparator: ':', //the separator character used when building up the detail row
38 toggleHTMLElement: '<span />', // override this if you want to insert a click target rather than use a background image.
39 createGroupedDetail: function (data) {
40 var groups = { '_none': { 'name': null, 'data': [] } };
41 for (var i = 0; i < data.length; i++) {
42 var groupid = data[i].group;
43 if (groupid !== null) {
44 if (!(groupid in groups))
45 groups[groupid] = { 'name': data[i].groupName || data[i].group, 'data': [] };
46
47 groups[groupid].data.push(data[i]);
48 } else {
49 groups._none.data.push(data[i]);
50 }
51 }
52 return groups;
53 },
54 createDetail: function (element, data, createGroupedDetail, separatorChar, classes) {
55 /// <summary>This function is used by FooTable to generate the detail view seen when expanding a collapsed row.</summary>
56 /// <param name="element">This is the div that contains all the detail row information, anything could be added to it.</param>
57 /// <param name="data">
58 /// This is an array of objects containing the cell information for the current row.
59 /// These objects look like the below:
60 /// obj = {
61 /// 'name': String, // The name of the column
62 /// 'value': Object, // The value parsed from the cell using the parsers. This could be a string, a number or whatever the parser outputs.
63 /// 'display': String, // This is the actual HTML from the cell, so if you have images etc you want moved this is the one to use and is the default value used.
64 /// 'group': String, // This is the identifier used in the data-group attribute of the column.
65 /// 'groupName': String // This is the actual name of the group the column belongs to.
66 /// }
67 /// </param>
68 /// <param name="createGroupedDetail">The grouping function to group the data</param>
69 /// <param name="separatorChar">The separator charactor used</param>
70 /// <param name="classes">The array of class names used to build up the detail row</param>
71
72 var groups = createGroupedDetail(data);
73 for (var group in groups) {
74 if (groups[group].data.length === 0) continue;
75 if (group !== '_none') element.append('<div class="' + classes.detailInnerGroup + '">' + groups[group].name + '</div>');
76
77 for (var j = 0; j < groups[group].data.length; j++) {
78 var separator = (groups[group].data[j].name) ? separatorChar : '';
79 element.append('<div class="' + classes.detailInnerRow + '"><div class="' + classes.detailInnerName + '">' + groups[group].data[j].name + separator + '</div><div class="' + classes.detailInnerValue + '">' + groups[group].data[j].display + '</div></div>');
80 }
81 }
82 },
83 classes: {
84 main: 'footable',
85 loading: 'footable-loading',
86 loaded: 'footable-loaded',
87 toggle: 'footable-toggle',
88 disabled: 'footable-disabled',
89 detail: 'footable-row-detail',
90 detailCell: 'footable-row-detail-cell',
91 detailInner: 'footable-row-detail-inner',
92 detailInnerRow: 'footable-row-detail-row',
93 detailInnerGroup: 'footable-row-detail-group',
94 detailInnerName: 'footable-row-detail-name',
95 detailInnerValue: 'footable-row-detail-value',
96 detailShow: 'footable-detail-show'
97 },
98 triggers: {
99 initialize: 'footable_initialize', //trigger this event to force FooTable to reinitialize
100 resize: 'footable_resize', //trigger this event to force FooTable to resize
101 redraw: 'footable_redraw', //trigger this event to force FooTable to redraw
102 toggleRow: 'footable_toggle_row', //trigger this event to force FooTable to toggle a row
103 expandFirstRow: 'footable_expand_first_row', //trigger this event to force FooTable to expand the first row
104 expandAll: 'footable_expand_all', //trigger this event to force FooTable to expand all rows
105 collapseAll: 'footable_collapse_all' //trigger this event to force FooTable to collapse all rows
106 },
107 events: {
108 alreadyInitialized: 'footable_already_initialized', //fires when the FooTable has already been initialized
109 initializing: 'footable_initializing', //fires before FooTable starts initializing
110 initialized: 'footable_initialized', //fires after FooTable has finished initializing
111 resizing: 'footable_resizing', //fires before FooTable resizes
112 resized: 'footable_resized', //fires after FooTable has resized
113 redrawn: 'footable_redrawn', //fires after FooTable has redrawn
114 breakpoint: 'footable_breakpoint', //fires inside the resize function, when a breakpoint is hit
115 columnData: 'footable_column_data', //fires when setting up column data. Plugins should use this event to capture their own info about a column
116 rowDetailUpdating: 'footable_row_detail_updating', //fires before a detail row is updated
117 rowDetailUpdated: 'footable_row_detail_updated', //fires when a detail row is being updated
118 rowCollapsed: 'footable_row_collapsed', //fires when a row is collapsed
119 rowExpanded: 'footable_row_expanded', //fires when a row is expanded
120 rowRemoved: 'footable_row_removed', //fires when a row is removed
121 reset: 'footable_reset' //fires when FooTable is reset
122 },
123 debug: false, // Whether or not to log information to the console.
124 log: null
125 },
126
127 version: {
128 major: 0, minor: 5,
129 toString: function () {
130 return w.footable.version.major + '.' + w.footable.version.minor;
131 },
132 parse: function (str) {
133 version = /(\d+)\.?(\d+)?\.?(\d+)?/.exec(str);
134 return {
135 major: parseInt(version[1], 10) || 0,
136 minor: parseInt(version[2], 10) || 0,
137 patch: parseInt(version[3], 10) || 0
138 };
139 }
140 },
141
142 plugins: {
143 _validate: function (plugin) {
144 ///<summary>Simple validation of the <paramref name="plugin"/> to make sure any members called by FooTable actually exist.</summary>
145 ///<param name="plugin">The object defining the plugin, this should implement a string property called "name" and a function called "init".</param>
146
147 if (!$.isFunction(plugin)) {
148 if (w.footable.options.debug === true) console.error('Validation failed, expected type "function", received type "{0}".', typeof plugin);
149 return false;
150 }
151 var p = new plugin();
152 if (typeof p['name'] !== 'string') {
153 if (w.footable.options.debug === true) console.error('Validation failed, plugin does not implement a string property called "name".', p);
154 return false;
155 }
156 if (!$.isFunction(p['init'])) {
157 if (w.footable.options.debug === true) console.error('Validation failed, plugin "' + p['name'] + '" does not implement a function called "init".', p);
158 return false;
159 }
160 if (w.footable.options.debug === true) console.log('Validation succeeded for plugin "' + p['name'] + '".', p);
161 return true;
162 },
163 registered: [], // An array containing all registered plugins.
164 register: function (plugin, options) {
165 ///<summary>Registers a <paramref name="plugin"/> and its default <paramref name="options"/> with FooTable.</summary>
166 ///<param name="plugin">The plugin that should implement a string property called "name" and a function called "init".</param>
167 ///<param name="options">The default options to merge with the FooTable's base options.</param>
168
169 if (w.footable.plugins._validate(plugin)) {
170 w.footable.plugins.registered.push(plugin);
171 if (typeof options === 'object') $.extend(true, w.footable.options, options);
172 }
173 },
174 load: function(instance){
175 var loaded = [], registered, i;
176 for(i = 0; i < w.footable.plugins.registered.length; i++){
177 try {
178 registered = w.footable.plugins.registered[i];
179 loaded.push(new registered(instance));
180 } catch (err) {
181 if (w.footable.options.debug === true) console.error(err);
182 }
183 }
184 return loaded;
185 },
186 init: function (instance) {
187 ///<summary>Loops through all registered plugins and calls the "init" method supplying the current <paramref name="instance"/> of the FooTable as the first parameter.</summary>
188 ///<param name="instance">The current instance of the FooTable that the plugin is being initialized for.</param>
189
190 for (var i = 0; i < instance.plugins.length; i++) {
191 try {
192 instance.plugins[i]['init'](instance);
193 } catch (err) {
194 if (w.footable.options.debug === true) console.error(err);
195 }
196 }
197 }
198 }
199 };
200
201 var instanceCount = 0;
202
203 $.fn.footable = function (options) {
204 ///<summary>The main constructor call to initialize the plugin using the supplied <paramref name="options"/>.</summary>
205 ///<param name="options">
206 ///<para>A JSON object containing user defined options for the plugin to use. Any options not supplied will have a default value assigned.</para>
207 ///<para>Check the documentation or the default options object above for more information on available options.</para>
208 ///</param>
209
210 options = options || {};
211 var o = $.extend(true, {}, w.footable.options, options); //merge user and default options
212 return this.each(function () {
213 instanceCount++;
214 var footable = new Footable(this, o, instanceCount);
215 $(this).data('footable', footable);
216 });
217 };
218
219 //helper for using timeouts
220 function Timer() {
221 ///<summary>Simple timer object created around a timeout.</summary>
222 var t = this;
223 t.id = null;
224 t.busy = false;
225 t.start = function (code, milliseconds) {
226 ///<summary>Starts the timer and waits the specified amount of <paramref name="milliseconds"/> before executing the supplied <paramref name="code"/>.</summary>
227 ///<param name="code">The code to execute once the timer runs out.</param>
228 ///<param name="milliseconds">The time in milliseconds to wait before executing the supplied <paramref name="code"/>.</param>
229
230 if (t.busy) {
231 return;
232 }
233 t.stop();
234 t.id = setTimeout(function () {
235 code();
236 t.id = null;
237 t.busy = false;
238 }, milliseconds);
239 t.busy = true;
240 };
241 t.stop = function () {
242 ///<summary>Stops the timer if its runnning and resets it back to its starting state.</summary>
243
244 if (t.id !== null) {
245 clearTimeout(t.id);
246 t.id = null;
247 t.busy = false;
248 }
249 };
250 }
251
252 function Footable(t, o, id) {
253 ///<summary>Inits a new instance of the plugin.</summary>
254 ///<param name="t">The main table element to apply this plugin to.</param>
255 ///<param name="o">The options supplied to the plugin. Check the defaults object to see all available options.</param>
256 ///<param name="id">The id to assign to this instance of the plugin.</param>
257
258 var ft = this;
259 ft.id = id;
260 ft.table = t;
261 ft.options = o;
262 ft.breakpoints = [];
263 ft.breakpointNames = '';
264 ft.columns = {};
265 ft.plugins = w.footable.plugins.load(ft);
266
267 var opt = ft.options,
268 cls = opt.classes,
269 evt = opt.events,
270 trg = opt.triggers,
271 indexOffset = 0;
272
273 // This object simply houses all the timers used in the FooTable.
274 ft.timers = {
275 resize: new Timer(),
276 register: function (name) {
277 ft.timers[name] = new Timer();
278 return ft.timers[name];
279 }
280 };
281
282 ft.init = function () {
283 var $window = $(w), $table = $(ft.table);
284
285 w.footable.plugins.init(ft);
286
287 if ($table.hasClass(cls.loaded)) {
288 //already loaded FooTable for the table, so don't init again
289 ft.raise(evt.alreadyInitialized);
290 return;
291 }
292
293 //raise the initializing event
294 ft.raise(evt.initializing);
295
296 $table.addClass(cls.loading);
297
298 // Get the column data once for the life time of the plugin
299 $table.find(opt.columnDataSelector).each(function () {
300 var data = ft.getColumnData(this);
301 ft.columns[data.index] = data;
302 });
303
304 // Create a nice friendly array to work with out of the breakpoints object.
305 for (var name in opt.breakpoints) {
306 ft.breakpoints.push({ 'name': name, 'width': opt.breakpoints[name] });
307 ft.breakpointNames += (name + ' ');
308 }
309
310 // Sort the breakpoints so the smallest is checked first
311 ft.breakpoints.sort(function (a, b) {
312 return a['width'] - b['width'];
313 });
314
315 $table
316 .unbind(trg.initialize)
317 //bind to FooTable initialize trigger
318 .bind(trg.initialize, function () {
319 //remove previous "state" (to "force" a resize)
320 $table.removeData('footable_info');
321 $table.data('breakpoint', '');
322
323 //trigger the FooTable resize
324 $table.trigger(trg.resize);
325
326 //remove the loading class
327 $table.removeClass(cls.loading);
328
329 //add the FooTable and loaded class
330 $table.addClass(cls.loaded).addClass(cls.main);
331
332 //raise the initialized event
333 ft.raise(evt.initialized);
334 })
335 .unbind(trg.redraw)
336 //bind to FooTable redraw trigger
337 .bind(trg.redraw, function () {
338 ft.redraw();
339 })
340 .unbind(trg.resize)
341 //bind to FooTable resize trigger
342 .bind(trg.resize, function () {
343 ft.resize();
344 })
345 .unbind(trg.expandFirstRow)
346 //bind to FooTable expandFirstRow trigger
347 .bind(trg.expandFirstRow, function () {
348 $table.find(opt.toggleSelector).first().not('.' + cls.detailShow).trigger(trg.toggleRow);
349 })
350 .unbind(trg.expandAll)
351 //bind to FooTable expandFirstRow trigger
352 .bind(trg.expandAll, function () {
353 $table.find(opt.toggleSelector).not('.' + cls.detailShow).trigger(trg.toggleRow);
354 })
355 .unbind(trg.collapseAll)
356 //bind to FooTable expandFirstRow trigger
357 .bind(trg.collapseAll, function () {
358 $table.find('.' + cls.detailShow).trigger(trg.toggleRow);
359 });
360
361 //trigger a FooTable initialize
362 $table.trigger(trg.initialize);
363
364 //bind to window resize
365 $window
366 .bind('resize.footable', function () {
367 ft.timers.resize.stop();
368 ft.timers.resize.start(function () {
369 ft.raise(trg.resize);
370 }, opt.delay);
371 });
372 };
373
374 ft.addRowToggle = function () {
375 if (!opt.addRowToggle) return;
376
377 var $table = $(ft.table),
378 hasToggleColumn = false;
379
380 //first remove all toggle spans
381 $table.find('span.' + cls.toggle).remove();
382
383 for (var c in ft.columns) {
384 var col = ft.columns[c];
385 if (col.toggle) {
386 hasToggleColumn = true;
387 var selector = '> tbody > tr:not(.' + cls.detail + ',.' + cls.disabled + ') > td:nth-child(' + (parseInt(col.index, 10) + 1) + ')';
388 $table.find(selector).not('.' + cls.detailCell).prepend($(opt.toggleHTMLElement).addClass(cls.toggle));
389 return;
390 }
391 }
392 //check if we have an toggle column. If not then add it to the first column just to be safe
393 if (!hasToggleColumn) {
394 $table
395 .find('> tbody > tr:not(.' + cls.detail + ',.' + cls.disabled + ') > td:first-child')
396 .not('.' + cls.detailCell)
397 .prepend($(opt.toggleHTMLElement).addClass(cls.toggle));
398 }
399 };
400
401 ft.setColumnClasses = function () {
402 $table = $(ft.table);
403 for (var c in ft.columns) {
404 var col = ft.columns[c];
405 if (col.className !== null) {
406 var selector = '', first = true;
407 $.each(col.matches, function (m, match) { //support for colspans
408 if (!first) selector += ', ';
409 selector += '> tbody > tr:not(.' + cls.detail + ') > td:nth-child(' + (parseInt(match, 10) + 1) + ')';
410 first = false;
411 });
412 //add the className to the cells specified by data-class="blah"
413 $table.find(selector).not('.' + cls.detailCell).addClass(col.className);
414 }
415 }
416 };
417
418 //moved this out into it's own function so that it can be called from other add-ons
419 ft.bindToggleSelectors = function () {
420 var $table = $(ft.table);
421
422 if (!ft.hasAnyBreakpointColumn()) return;
423
424 $table.find(opt.toggleSelector).unbind(trg.toggleRow).bind(trg.toggleRow, function (e) {
425 var $row = $(this).is('tr') ? $(this) : $(this).parents('tr:first');
426 ft.toggleDetail($row);
427 });
428
429 $table.find(opt.toggleSelector).unbind('click.footable').bind('click.footable', function (e) {
430 if ($table.is('.breakpoint') && $(e.target).is('td,.'+ cls.toggle)) {
431 $(this).trigger(trg.toggleRow);
432 }
433 });
434 };
435
436 ft.parse = function (cell, column) {
437 var parser = opt.parsers[column.type] || opt.parsers.alpha;
438 return parser(cell);
439 };
440
441 ft.getColumnData = function (th) {
442 var $th = $(th), hide = $th.data('hide'), index = $th.index();
443 hide = hide || '';
444 hide = jQuery.map(hide.split(','), function (a) {
445 return jQuery.trim(a);
446 });
447 var data = {
448 'index': index,
449 'hide': { },
450 'type': $th.data('type') || 'alpha',
451 'name': $th.data('name') || $.trim($th.text()),
452 'ignore': $th.data('ignore') || false,
453 'toggle': $th.data('toggle') || false,
454 'className': $th.data('class') || null,
455 'matches': [],
456 'names': { },
457 'group': $th.data('group') || null,
458 'groupName': null
459 };
460
461 if (data.group !== null) {
462 var $group = $(ft.table).find('> thead > tr.footable-group-row > th[data-group="' + data.group + '"], > thead > tr.footable-group-row > td[data-group="' + data.group + '"]').first();
463 data.groupName = ft.parse($group, { 'type': 'alpha' });
464 }
465
466 var pcolspan = parseInt($th.prev().attr('colspan') || 0, 10);
467 indexOffset += pcolspan > 1 ? pcolspan - 1 : 0;
468 var colspan = parseInt($th.attr('colspan') || 0, 10), curindex = data.index + indexOffset;
469 if (colspan > 1) {
470 var names = $th.data('names');
471 names = names || '';
472 names = names.split(',');
473 for (var i = 0; i < colspan; i++) {
474 data.matches.push(i + curindex);
475 if (i < names.length) data.names[i + curindex] = names[i];
476 }
477 } else {
478 data.matches.push(curindex);
479 }
480
481 data.hide['default'] = ($th.data('hide') === "all") || ($.inArray('default', hide) >= 0);
482
483 var hasBreakpoint = false;
484 for (var name in opt.breakpoints) {
485 data.hide[name] = ($th.data('hide') === "all") || ($.inArray(name, hide) >= 0);
486 hasBreakpoint = hasBreakpoint || data.hide[name];
487 }
488 data.hasBreakpoint = hasBreakpoint;
489 var e = ft.raise(evt.columnData, { 'column': { 'data': data, 'th': th } });
490 return e.column.data;
491 };
492
493 ft.getViewportWidth = function () {
494 return window.innerWidth || (document.body ? document.body.offsetWidth : 0);
495 };
496
497 ft.calculateWidth = function ($table, info) {
498 if (jQuery.isFunction(opt.calculateWidthOverride)) {
499 return opt.calculateWidthOverride($table, info);
500 }
501 if (info.viewportWidth < info.width) info.width = info.viewportWidth;
502 if (info.parentWidth < info.width) info.width = info.parentWidth;
503 return info;
504 };
505
506 ft.hasBreakpointColumn = function (breakpoint) {
507 for (var c in ft.columns) {
508 if (ft.columns[c].hide[breakpoint]) {
509 if (ft.columns[c].ignore) {
510 continue;
511 }
512 return true;
513 }
514 }
515 return false;
516 };
517
518 ft.hasAnyBreakpointColumn = function () {
519 for (var c in ft.columns) {
520 if (ft.columns[c].hasBreakpoint) {
521 return true;
522 }
523 }
524 return false;
525 };
526
527 ft.resize = function () {
528 var $table = $(ft.table);
529
530 if (!$table.is(':visible')) {
531 return;
532 } //we only care about FooTables that are visible
533
534 if (!ft.hasAnyBreakpointColumn()) {
535 return;
536 } //we only care about FooTables that have breakpoints
537
538 var info = {
539 'width': $table.width(), //the table width
540 'viewportWidth': ft.getViewportWidth(), //the width of the viewport
541 'parentWidth': $table.parent().width() //the width of the parent
542 };
543
544 info = ft.calculateWidth($table, info);
545
546 var pinfo = $table.data('footable_info');
547 $table.data('footable_info', info);
548 ft.raise(evt.resizing, { 'old': pinfo, 'info': info });
549
550 // This (if) statement is here purely to make sure events aren't raised twice as mobile safari seems to do
551 if (!pinfo || (pinfo && pinfo.width && pinfo.width !== info.width)) {
552
553 var current = null, breakpoint;
554 for (var i = 0; i < ft.breakpoints.length; i++) {
555 breakpoint = ft.breakpoints[i];
556 if (breakpoint && breakpoint.width && info.width <= breakpoint.width) {
557 current = breakpoint;
558 break;
559 }
560 }
561
562 var breakpointName = (current === null ? 'default' : current['name']),
563 hasBreakpointFired = ft.hasBreakpointColumn(breakpointName),
564 previousBreakpoint = $table.data('breakpoint');
565
566 $table
567 .data('breakpoint', breakpointName)
568 .removeClass('default breakpoint').removeClass(ft.breakpointNames)
569 .addClass(breakpointName + (hasBreakpointFired ? ' breakpoint' : ''));
570
571 //only do something if the breakpoint has changed
572 if (breakpointName !== previousBreakpoint) {
573 //trigger a redraw
574 $table.trigger(trg.redraw);
575 //raise a breakpoint event
576 ft.raise(evt.breakpoint, { 'breakpoint': breakpointName, 'info': info });
577 }
578 }
579
580 ft.raise(evt.resized, { 'old': pinfo, 'info': info });
581 };
582
583 ft.redraw = function () {
584 //add the toggler to each row
585 ft.addRowToggle();
586
587 //bind the toggle selector click events
588 ft.bindToggleSelectors();
589
590 //set any cell classes defined for the columns
591 ft.setColumnClasses();
592
593 var $table = $(ft.table),
594 breakpointName = $table.data('breakpoint'),
595 hasBreakpointFired = ft.hasBreakpointColumn(breakpointName);
596
597 $table
598 .find('> tbody > tr:not(.' + cls.detail + ')').data('detail_created', false).end()
599 .find('> thead > tr:last-child > th')
600 .each(function () {
601 var data = ft.columns[$(this).index()], selector = '', first = true;
602 $.each(data.matches, function (m, match) {
603 if (!first) {
604 selector += ', ';
605 }
606 var count = match + 1;
607 selector += '> tbody > tr:not(.' + cls.detail + ') > td:nth-child(' + count + ')';
608 selector += ', > tfoot > tr:not(.' + cls.detail + ') > td:nth-child(' + count + ')';
609 selector += ', > colgroup > col:nth-child(' + count + ')';
610 first = false;
611 });
612
613 selector += ', > thead > tr[data-group-row="true"] > th[data-group="' + data.group + '"]';
614 var $column = $table.find(selector).add(this);
615 if (breakpointName !== '') {
616 if (data.hide[breakpointName] === false) $column.addClass('footable-visible').show();
617 else $column.removeClass('footable-visible').hide();
618 }
619
620 if ($table.find('> thead > tr.footable-group-row').length === 1) {
621 var $groupcols = $table.find('> thead > tr:last-child > th[data-group="' + data.group + '"]:visible, > thead > tr:last-child > th[data-group="' + data.group + '"]:visible'),
622 $group = $table.find('> thead > tr.footable-group-row > th[data-group="' + data.group + '"], > thead > tr.footable-group-row > td[data-group="' + data.group + '"]'),
623 groupspan = 0;
624
625 $.each($groupcols, function () {
626 groupspan += parseInt($(this).attr('colspan') || 1, 10);
627 });
628
629 if (groupspan > 0) $group.attr('colspan', groupspan).show();
630 else $group.hide();
631 }
632 })
633 .end()
634 .find('> tbody > tr.' + cls.detailShow).each(function () {
635 ft.createOrUpdateDetailRow(this);
636 });
637
638 $table.find('> tbody > tr.' + cls.detailShow + ':visible').each(function () {
639 var $next = $(this).next();
640 if ($next.hasClass(cls.detail)) {
641 if (!hasBreakpointFired) $next.hide();
642 else $next.show();
643 }
644 });
645
646 // adding .footable-first-column and .footable-last-column to the first and last th and td of each row in order to allow
647 // for styling if the first or last column is hidden (which won't work using :first-child or :last-child)
648 $table.find('> thead > tr > th.footable-last-column, > tbody > tr > td.footable-last-column').removeClass('footable-last-column');
649 $table.find('> thead > tr > th.footable-first-column, > tbody > tr > td.footable-first-column').removeClass('footable-first-column');
650 $table.find('> thead > tr, > tbody > tr')
651 .find('> th.footable-visible:last, > td.footable-visible:last')
652 .addClass('footable-last-column')
653 .end()
654 .find('> th.footable-visible:first, > td.footable-visible:first')
655 .addClass('footable-first-column');
656
657 ft.raise(evt.redrawn);
658 };
659
660 ft.toggleDetail = function (row) {
661 var $row = (row.jquery) ? row : $(row),
662 $next = $row.next();
663
664 //check if the row is already expanded
665 if ($row.hasClass(cls.detailShow)) {
666 $row.removeClass(cls.detailShow);
667
668 //only hide the next row if it's a detail row
669 if ($next.hasClass(cls.detail)) $next.hide();
670
671 ft.raise(evt.rowCollapsed, { 'row': $row[0] });
672
673 } else {
674 ft.createOrUpdateDetailRow($row[0]);
675 $row.addClass(cls.detailShow)
676 .next().show();
677
678 ft.raise(evt.rowExpanded, { 'row': $row[0] });
679 }
680 };
681
682 ft.removeRow = function (row) {
683 var $row = (row.jquery) ? row : $(row);
684 if ($row.hasClass(cls.detail)) {
685 $row = $row.prev();
686 }
687 var $next = $row.next();
688 if ($row.data('detail_created') === true) {
689 //remove the detail row
690 $next.remove();
691 }
692 $row.remove();
693
694 //raise event
695 ft.raise(evt.rowRemoved);
696 };
697
698 ft.appendRow = function (row) {
699 var $row = (row.jquery) ? row : $(row);
700 $(ft.table).find('tbody').append($row);
701
702 //redraw the table
703 ft.redraw();
704 };
705
706 ft.getColumnFromTdIndex = function (index) {
707 /// <summary>Returns the correct column data for the supplied index taking into account colspans.</summary>
708 /// <param name="index">The index to retrieve the column data for.</param>
709 /// <returns type="json">A JSON object containing the column data for the supplied index.</returns>
710 var result = null;
711 for (var column in ft.columns) {
712 if ($.inArray(index, ft.columns[column].matches) >= 0) {
713 result = ft.columns[column];
714 break;
715 }
716 }
717 return result;
718 };
719
720 ft.createOrUpdateDetailRow = function (actualRow) {
721 var $row = $(actualRow), $next = $row.next(), $detail, values = [];
722 if ($row.data('detail_created') === true) return true;
723
724 if ($row.is(':hidden')) return false; //if the row is hidden for some reason (perhaps filtered) then get out of here
725 ft.raise(evt.rowDetailUpdating, { 'row': $row, 'detail': $next });
726 $row.find('> td:hidden').each(function () {
727 var index = $(this).index(), column = ft.getColumnFromTdIndex(index), name = column.name;
728 if (column.ignore === true) return true;
729
730 if (index in column.names) name = column.names[index];
731 values.push({ 'name': name, 'value': ft.parse(this, column), 'display': $.trim($(this).html()), 'group': column.group, 'groupName': column.groupName });
732 return true;
733 });
734 if (values.length === 0) return false; //return if we don't have any data to show
735 var colspan = $row.find('> td:visible').length;
736 var exists = $next.hasClass(cls.detail);
737 if (!exists) { // Create
738 $next = $('<tr class="' + cls.detail + '"><td class="' + cls.detailCell + '"><div class="' + cls.detailInner + '"></div></td></tr>');
739 $row.after($next);
740 }
741 $next.find('> td:first').attr('colspan', colspan);
742 $detail = $next.find('.' + cls.detailInner).empty();
743 opt.createDetail($detail, values, opt.createGroupedDetail, opt.detailSeparator, cls);
744 $row.data('detail_created', true);
745 ft.raise(evt.rowDetailUpdated, { 'row': $row, 'detail': $next });
746 return !exists;
747 };
748
749 ft.raise = function (eventName, args) {
750
751 if (ft.options.debug === true && $.isFunction(ft.options.log)) ft.options.log(eventName, 'event');
752
753 args = args || { };
754 var def = { 'ft': ft };
755 $.extend(true, def, args);
756 var e = $.Event(eventName, def);
757 if (!e.ft) {
758 $.extend(true, e, def);
759 } //pre jQuery 1.6 which did not allow data to be passed to event object constructor
760 $(ft.table).trigger(e);
761 return e;
762 };
763
764 //reset the state of FooTable
765 ft.reset = function() {
766 var $table = $(ft.table);
767 $table.removeData('footable_info')
768 .data('breakpoint', '')
769 .removeClass(cls.loading)
770 .removeClass(cls.loaded);
771
772 $table.find(opt.toggleSelector).unbind(trg.toggleRow).unbind('click.footable');
773
774 $table.find('> tbody > tr').removeClass(cls.detailShow);
775
776 $table.find('> tbody > tr.' + cls.detail).remove();
777
778 ft.raise(evt.reset);
779 };
780
781 ft.init();
782 return ft;
783 }
784})(jQuery, window);