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