1/* 2* FooTable v3 - FooTable is a jQuery plugin that aims to make HTML tables on smaller devices look awesome. 3* @version 3.1.6 4* @link http://fooplugins.com 5* @copyright Steven Usher & Brad Vincent 2015 6* @license Released under the GPLv3 license. 7*/ 8(function($, F){ 9 // add in console we use in case it's missing 10 window.console = window.console || { log:function(){}, error:function(){} }; 11 12 /** 13 * The jQuery plugin initializer. 14 * @function jQuery.fn.footable 15 * @param {(object|FooTable.Defaults)} [options] - The options to initialize the plugin with. 16 * @param {function} [ready] - A callback function to execute for each initialized plugin. 17 * @returns {jQuery} 18 */ 19 $.fn.footable = function (options, ready) { 20 options = options || {}; 21 // make sure we only work with tables 22 return this.filter('table').each(function (i, tbl) { 23 F.init(tbl, options, ready); 24 }); 25 }; 26 27 var debug_defaults = { 28 events: [] 29 }; 30 F.__debug__ = JSON.parse(localStorage.getItem('footable_debug')) || false; 31 F.__debug_options__ = JSON.parse(localStorage.getItem('footable_debug_options')) || debug_defaults; 32 33 /** 34 * Gets or sets the internal debug variable which enables some additional logging to the console. 35 * When enabled this value is stored in the localStorage so it can persist across page reloads. 36 * @param {boolean} value - Whether or not to enable additional logging. 37 * @param {object} [options] - Any debug specific options. 38 * @returns {(boolean|undefined)} 39 */ 40 F.debug = function(value, options){ 41 if (!F.is.boolean(value)) return F.__debug__; 42 F.__debug__ = value; 43 if (F.__debug__){ 44 localStorage.setItem('footable_debug', JSON.stringify(F.__debug__)); 45 F.__debug_options__ = $.extend(true, {}, debug_defaults, options || {}); 46 if (F.is.hash(options)){ 47 localStorage.setItem('footable_debug_options', JSON.stringify(F.__debug_options__)); 48 } 49 } else { 50 localStorage.removeItem('footable_debug'); 51 localStorage.removeItem('footable_debug_options'); 52 } 53 }; 54 55 /** 56 * Gets the FooTable instance of the supplied table if one exists. 57 * @param {(jQuery|jQuery.selector|HTMLTableElement)} table - The jQuery table object, selector or the HTMLTableElement to retrieve FooTable from. 58 * @returns {(FooTable.Table|undefined)} 59 */ 60 F.get = function(table){ 61 return $(table).first().data('__FooTable__'); 62 }; 63 64 /** 65 * Initializes a new instance of FooTable on the supplied table. 66 * @param {(jQuery|jQuery.selector|HTMLTableElement)} table - The jQuery table object, selector or the HTMLTableElement to initialize FooTable on. 67 * @param {object} options - The options to initialize FooTable with. 68 * @param {function} [ready] - A callback function to execute once the plugin is initialized. 69 * @returns {FooTable.Table} 70 */ 71 F.init = function(table, options, ready){ 72 var ft = F.get(table); 73 if (ft instanceof F.Table) ft.destroy(); 74 return new F.Table(table, options, ready); 75 }; 76 77 /** 78 * Gets the FooTable.Row instance for the supplied element. 79 * @param {(jQuery|jQuery.selector|HTMLTableElement)} element - A jQuery object, selector or the HTMLElement of an element to retrieve the FooTable.Row for. 80 * @returns {FooTable.Row} 81 */ 82 F.getRow = function(element){ 83 // to get the FooTable.Row object simply walk up the DOM, find the TR and grab the __FooTableRow__ data value 84 var $row = $(element).closest('tr'); 85 // if this is a detail row get the previous row in the table to get the main TR element 86 if ($row.hasClass('footable-detail-row')){ 87 $row = $row.prev(); 88 } 89 // grab the row object 90 return $row.data('__FooTableRow__'); 91 }; 92 93 // The below are external type definitions mainly used as pointers to jQuery docs for important information 94 /** 95 * jQuery is a fast, small, and feature-rich JavaScript library. It makes things like HTML document traversal and manipulation, event handling, animation, and Ajax much simpler with an easy-to-use API 96 * that works across a multitude of browsers. With a combination of versatility and extensibility, jQuery has changed the way that millions of people write JavaScript. 97 * @name jQuery 98 * @constructor 99 * @returns {jQuery} 100 * @see {@link http://api.jquery.com/} 101 */ 102 103 /** 104 * This object provides a subset of the methods of the Deferred object (then, done, fail, always, pipe, and state) to prevent users from changing the state of the Deferred. 105 * @typedef {object} jQuery.Promise 106 * @see {@link http://api.jquery.com/Types/#Promise} 107 */ 108 109 /** 110 * As of jQuery 1.5, the Deferred object provides a way to register multiple callbacks into self-managed callback queues, invoke callback queues as appropriate, 111 * and relay the success or failure state of any synchronous or asynchronous function. 112 * @typedef {object} jQuery.Deferred 113 * @see {@link http://api.jquery.com/Types/#Deferred} 114 */ 115 116 /** 117 * jQuery's event system normalizes the event object according to W3C standards. The event object is guaranteed to be passed to the event handler. Most properties from 118 * the original event are copied over and normalized to the new event object. 119 * @typedef {object} jQuery.Event 120 * @see {@link http://api.jquery.com/category/events/event-object/} 121 */ 122 123 /** 124 * Provides a way to execute callback functions based on one or more objects, usually Deferred objects that represent asynchronous events. 125 * @memberof jQuery 126 * @function when 127 * @param {...jQuery.Deferred} deferreds - Any number of deferred objects to wait for. 128 * @returns {jQuery.Promise} 129 * @see {@link http://api.jquery.com/jQuery.when/} 130 */ 131 132 /**
133 * The jQuery.fn namespace used to register plugins with jQuery. 134 * @memberof jQuery 135 * @namespace fn 136 * @see {@link http://learn.jquery.com/plugins/basic-plugin-creation/} 137 */ 138})( 139 jQuery, 140 /** 141 * The core FooTable namespace containing all the plugin code. 142 * @namespace 143 */ 144 FooTable = window.FooTable || {} 145); 146(function(F){ 147 var returnTrue = function(){ return true; }; 148 149 /** 150 * This namespace contains commonly used array utility methods. 151 * @namespace {object} FooTable.arr 152 */ 153 F.arr = {}; 154 155 /** 156 * Iterates over each item in the supplied array and performs the supplied function passing in the current item as the first argument. 157 * @memberof FooTable.arr 158 * @function each 159 * @param {Array} array - The array to iterate 160 * @param {function} func - The function to execute for each item. The first argument supplied to this function is the current item and the second is the current index. 161 */ 162 F.arr.each = function (array, func) { 163 if (!F.is.array(array) || !F.is.fn(func)) return; 164 for (var i = 0, len = array.length; i < len; i++) { 165 if (func(array[i], i) === false) break; 166 } 167 }; 168 169 /** 170 * Get all items in the supplied array that optionally matches the supplied where function. If no items are found an empty array is returned. 171 * @memberof FooTable.arr 172 * @function get 173 * @param {Array} array - The array to get items from. 174 * @param {function} where - This function must return a boolean value, true includes the item in the result array. 175 * @returns {Array} 176 */ 177 F.arr.get = function (array, where) { 178 var result = []; 179 if (!F.is.array(array)) return result; 180 if (!F.is.fn(where)) return array; 181 for (var i = 0, len = array.length; i < len; i++) { 182 if (where(array[i], i)) result.push(array[i]); 183 } 184 return result; 185 }; 186 187 /** 188 * Get a boolean value indicating if any item exists in the supplied array that optionally matches the supplied where function. 189 * @memberof FooTable.arr 190 * @function any 191 * @param {Array} array - The array to check. 192 * @param {function} [where] - [Optional] This function must return a boolean value, true indicates that the current item is a valid match. 193 * @returns {boolean} 194 */ 195 F.arr.any = function (array, where) { 196 if (!F.is.array(array)) return false; 197 where = F.is.fn(where) ? where : returnTrue; 198 for (var i = 0, len = array.length; i < len; i++) { 199 if (where(array[i], i)) return true; 200 } 201 return false; 202 }; 203 204 /** 205 * Checks if the supplied value exists in the array. 206 * @memberof FooTable.arr 207 * @function contains 208 * @param {Array} array - The array to check. 209 * @param {*} value - The value to check for. 210 * @returns {boolean} 211 */ 212 F.arr.contains = function(array, value){ 213 if (!F.is.array(array) || F.is.undef(value)) return false; 214 for (var i = 0, len = array.length; i < len; i++) { 215 if (array[i] == value) return true; 216 } 217 return false; 218 }; 219 220 /** 221 * Get the first item in the supplied array that optionally matches the supplied where function. If no item is found null is returned. 222 * @memberof FooTable.arr 223 * @function first 224 * @param {Array} array - The array to get the item from. 225 * @param {function} [where] - [Optional] This function must return a boolean value, true indicates that the current item can be returned. 226 * @returns {(*|null)} 227 */ 228 F.arr.first = function (array, where) { 229 if (!F.is.array(array)) return null; 230 where = F.is.fn(where) ? where : returnTrue; 231 for (var i = 0, len = array.length; i < len; i++) { 232 if (where(array[i], i)) return array[i]; 233 } 234 return null; 235 }; 236 237 /** 238 * Creates a new array from the results of the supplied getter function. If no items are found an empty array is returned, to exclude an item from the results return null. 239 * @memberof FooTable.arr 240 * @function map 241 * @param {Array} array - The array to iterate. 242 * @param {function} getter - This function must return either a new value or null. 243 * The first argument is the result being returned at this point in the iteration. The second argument is the current item being iterated. 244 * @returns {(*|null)} 245 */ 246 F.arr.map = function (array, getter) { 247 var result = [], returned = null; 248 if (!F.is.array(array) || !F.is.fn(getter)) return result; 249 for (var i = 0, len = array.length; i < len; i++) { 250 if ((returned = getter(array[i], i)) != null) result.push(returned); 251 } 252 return result; 253 }; 254 255 /** 256 * Removes items from the array matching the supplied where function. All removed items are returned in a new array. 257 * @memberof FooTable.arr 258 * @function remove 259 * @param {Array} array - The array to iterate and remove items from. 260 * @param {function} where - This function must return a boolean value, true includes the item in the result array. 261 * @returns {*} 262 */ 263 F.arr.remove = function (array, where) { 264 var remove = [], removed = []; 265 if (!F.is.array(array) || !F.is.fn(where)) return removed; 266 var i = 0, len = array.length; 267 for (; i < len; i++) { 268 if (where(array[i], i, removed)){ 269 remove.push(i); 270 removed.push(array[i]); 271 } 272 } 273 // sort the indexes to be removed from largest to smallest 274 remove.sort(function(a, b){ return b - a; }); 275 i = 0; len = remove.length; 276 for(; i < len; i++){ 277 var index = remove[i] - i; 278 array.splice(index, 1); 279 } 280 return removed; 281 }; 282 283 /** 284 * Deletes a single item from the array. The item if removed is returned. 285 * @memberof FooTable.arr 286 * @function delete
287 * @param {Array} array - The array to iterate and delete the item from. 288 * @param {*} item - The item to find and delete. 289 * @returns {(*|null)} 290 */ 291 F.arr.delete = function(array, item){ 292 var remove = -1, removed = null; 293 if (!F.is.array(array) || F.is.undef(item)) return removed; 294 var i = 0, len = array.length; 295 for (; i < len; i++) { 296 if (array[i] == item){ 297 remove = i; 298 removed = array[i]; 299 break; 300 } 301 } 302 if (remove != -1) array.splice(remove, 1); 303 return removed; 304 }; 305 306 /** 307 * Replaces a single item in the array with a new one. 308 * @memberof FooTable.arr 309 * @function replace 310 * @param {Array} array - The array to iterate and replace the item in. 311 * @param {*} oldItem - The item to be replaced. 312 * @param {*} newItem - The item to be inserted. 313 */ 314 F.arr.replace = function(array, oldItem, newItem){ 315 var index = array.indexOf(oldItem); 316 if (index !== -1) array[index] = newItem; 317 }; 318 319})(FooTable); 320(function (F) { 321 322 /** 323 * This namespace contains commonly used 'is' type methods that return boolean values. 324 * @namespace FooTable.is 325 */ 326 F.is = {}; 327 328 /** 329 * Checks if the type of the value is the same as that supplied.
330 * @memberof FooTable.is 331 * @function type 332 * @param {*} value - The value to check the type of. 333 * @param {string} type - The type to check for. 334 * @returns {boolean} 335 */ 336 F.is.type = function (value, type) { 337 return typeof value === type; 338 }; 339 340 /** 341 * Checks if the value is defined. 342 * @memberof FooTable.is 343 * @function defined 344 * @param {*} value - The value to check is defined. 345 * @returns {boolean} 346 */ 347 F.is.defined = function (value) { 348 return typeof value !== 'undefined'; 349 }; 350 351 /** 352 * Checks if the value is undefined. 353 * @memberof FooTable.is 354 * @function undef 355 * @param {*} value - The value to check is undefined. 356 * @returns {boolean} 357 */ 358 F.is.undef = function (value) { 359 return typeof value === 'undefined'; 360 }; 361 362 /** 363 * Checks if the value is an array. 364 * @memberof FooTable.is 365 * @function array 366 * @param {*} value - The value to check. 367 * @returns {boolean} 368 */ 369 F.is.array = function (value) { 370 return '[object Array]' === Object.prototype.toString.call(value); 371 }; 372 373 /** 374 * Checks if the value is a date. 375 * @memberof FooTable.is 376 * @function date 377 * @param {*} value - The value to check. 378 * @returns {boolean} 379 */ 380 F.is.date = function (value) { 381 return '[object Date]' === Object.prototype.toString.call(value) && !isNaN(value.getTime()); 382 }; 383 384 /** 385 * Checks if the value is a boolean. 386 * @memberof FooTable.is 387 * @function boolean 388 * @param {*} value - The value to check. 389 * @returns {boolean} 390 */ 391 F.is.boolean = function (value) { 392 return '[object Boolean]' === Object.prototype.toString.call(value); 393 }; 394 395 /** 396 * Checks if the value is a string. 397 * @memberof FooTable.is 398 * @function string 399 * @param {*} value - The value to check. 400 * @returns {boolean} 401 */ 402 F.is.string = function (value) { 403 return '[object String]' === Object.prototype.toString.call(value); 404 }; 405 406 /** 407 * Checks if the value is a number. 408 * @memberof FooTable.is 409 * @function number 410 * @param {*} value - The value to check. 411 * @returns {boolean} 412 */ 413 F.is.number = function (value) { 414 return '[object Number]' === Object.prototype.toString.call(value) && !isNaN(value); 415 }; 416 417 /** 418 * Checks if the value is a function. 419 * @memberof FooTable.is 420 * @function fn 421 * @param {*} value - The value to check. 422 * @returns {boolean} 423 */ 424 F.is.fn = function (value) { 425 return (F.is.defined(window) && value === window.alert) || '[object Function]' === Object.prototype.toString.call(value); 426 }; 427 428 /** 429 * Checks if the value is an error. 430 * @memberof FooTable.is 431 * @function error 432 * @param {*} value - The value to check. 433 * @returns {boolean} 434 */ 435 F.is.error = function (value) { 436 return '[object Error]' === Object.prototype.toString.call(value); 437 }; 438 439 /** 440 * Checks if the value is an object. 441 * @memberof FooTable.is 442 * @function object 443 * @param {*} value - The value to check. 444 * @returns {boolean} 445 */ 446 F.is.object = function (value) { 447 return '[object Object]' === Object.prototype.toString.call(value); 448 }; 449 450 /** 451 * Checks if the value is a hash. 452 * @memberof FooTable.is 453 * @function hash 454 * @param {*} value - The value to check. 455 * @returns {boolean} 456 */ 457 F.is.hash = function (value) { 458 return F.is.object(value) && value.constructor === Object && !value.nodeType && !value.setInterval; 459 }; 460 461 /** 462 * Checks if the supplied object is an HTMLElement 463 * @memberof FooTable.is 464 * @function element 465 * @param {object} obj - The object to check. 466 * @returns {boolean} 467 */ 468 F.is.element = function (obj) { 469 return typeof HTMLElement === 'object' 470 ? obj instanceof HTMLElement 471 : obj && typeof obj === 'object' && obj !== null && obj.nodeType === 1 && typeof obj.nodeName === 'string'; 472 }; 473 474 /** 475 * This is a simple check to determine if an object is a jQuery promise object. It simply checks the object has a "then" and "promise" function defined. 476 * The promise object is created as an object literal inside of jQuery.Deferred. 477 * It has no prototype, nor any other truly unique properties that could be used to distinguish it. 478 * This method should be a little more accurate than the internal jQuery one that simply checks for a "promise" method. 479 * @memberof FooTable.is 480 * @function promise 481 * @param {object} obj - The object to check. 482 * @returns {boolean} 483 */ 484 F.is.promise = function(obj){ 485 return F.is.object(obj) && F.is.fn(obj.then) && F.is.fn(obj.promise); 486 }; 487 488 /** 489 * Checks if the supplied object is an instance of a jQuery object. 490 * @memberof FooTable.is 491 * @function jq 492 * @param {object} obj - The object to check. 493 * @returns {boolean} 494 */ 495 F.is.jq = function(obj){ 496 return F.is.defined(window.jQuery) && obj instanceof jQuery && obj.length > 0; 497 }; 498 499 /** 500 * Checks if the supplied object is a moment.js date object. 501 * @memberof FooTable.is 502 * @function moment 503 * @param {object} obj - The object to check. 504 * @returns {boolean} 505 */ 506 F.is.moment = function(obj){ 507 return F.is.defined(window.moment) && F.is.object(obj) && F.is.boolean(obj._isAMomentObject) 508 }; 509 510 /** 511 * Checks if the supplied value is an object and if it is empty. 512 * @memberof FooTable.is 513 * @function emptyObject 514 * @param {*} value - The value to check. 515 * @returns {boolean} 516 */ 517 F.is.emptyObject = function(value){ 518 if (!F.is.hash(value)) return false;
519 for(var prop in value) { 520 if(value.hasOwnProperty(prop)) 521 return false; 522 } 523 return true; 524 }; 525 526 /** 527 * Checks if the supplied value is an array and if it is empty. 528 * @memberof FooTable.is 529 * @function emptyArray 530 * @param {*} value - The value to check. 531 * @returns {boolean} 532 */ 533 F.is.emptyArray = function(value){ 534 return F.is.array(value) ? value.length === 0 : true; 535 }; 536 537 /** 538 * Checks if the supplied value is a string and if it is empty. 539 * @memberof FooTable.is 540 * @function emptyString 541 * @param {*} value - The value to check. 542 * @returns {boolean} 543 */ 544 F.is.emptyString = function(value){ 545 return F.is.string(value) ? value.length === 0 : true; 546 }; 547 548})(FooTable); 549(function (F) { 550 /** 551 * This namespace contains commonly used string utility methods. 552 * @namespace FooTable.str 553 */ 554 F.str = {}; 555 556 /** 557 * Checks if the supplied string contains the given substring. 558 * @memberof FooTable.str 559 * @function contains 560 * @param {string} str - The string to check. 561 * @param {string} contains - The string to check for. 562 * @param {boolean} [ignoreCase=false] - Whether or not to ignore casing when performing the check. 563 * @returns {boolean} 564 */ 565 F.str.contains = function (str, contains, ignoreCase) { 566 if (F.is.emptyString(str) || F.is.emptyString(contains)) return false; 567 return contains.length <= str.length 568 && (ignoreCase ? str.toUpperCase().indexOf(contains.toUpperCase()) : str.indexOf(contains)) !== -1; 569 }; 570 571 /** 572 * Checks if the supplied string contains the exact given substring. 573 * @memberof FooTable.str 574 * @function contains 575 * @param {string} str - The string to check. 576 * @param {string} contains - The string to check for. 577 * @param {boolean} [ignoreCase=false] - Whether or not to ignore casing when performing the check. 578 * @returns {boolean} 579 */ 580 F.str.containsExact = function (str, contains, ignoreCase) { 581 if (F.is.emptyString(str) || F.is.emptyString(contains) || contains.length > str.length) return false; 582 return new RegExp('\\b'+ F.str.escapeRegExp(contains)+'\\b', ignoreCase ? 'i' : '').test(str); 583 }; 584 585 /** 586 * Checks if the supplied string contains the given word. 587 * @memberof FooTable.str 588 * @function containsWord 589 * @param {string} str - The string to check. 590 * @param {string} word - The word to check for. 591 * @param {boolean} [ignoreCase=false] - Whether or not to ignore casing when performing the check. 592 * @returns {boolean} 593 */ 594 F.str.containsWord = function(str, word, ignoreCase){ 595 if (F.is.emptyString(str) || F.is.emptyString(word) || str.length < word.length) 596 return false; 597 var parts = str.split(/\W/); 598 for (var i = 0, len = parts.length; i < len; i++){ 599 if (ignoreCase ? parts[i].toUpperCase() == word.toUpperCase() : parts[i] == word) return true; 600 } 601 return false; 602 }; 603 604 /** 605 * Returns the remainder of a string split on the first index of the given substring. 606 * @memberof FooTable.str 607 * @function from 608 * @param {string} str - The string to split. 609 * @param {string} from - The substring to split on. 610 * @returns {string} 611 */ 612 F.str.from = function (str, from) { 613 if (F.is.emptyString(str)) return str; 614 return F.str.contains(str, from) ? str.substring(str.indexOf(from) + 1) : str; 615 }; 616 617 /** 618 * Checks if a string starts with the supplied prefix. 619 * @memberof FooTable.str 620 * @function startsWith 621 * @param {string} str - The string to check. 622 * @param {string} prefix - The prefix to check for. 623 * @returns {boolean} 624 */ 625 F.str.startsWith = function (str, prefix) { 626 if (F.is.emptyString(str)) return str == prefix; 627 return str.slice(0, prefix.length) == prefix; 628 }; 629 630 /** 631 * Takes the supplied string and converts it to camel case. 632 * @memberof FooTable.str 633 * @function toCamelCase 634 * @param {string} str - The string to camel case. 635 * @returns {string} 636 */ 637 F.str.toCamelCase = function (str) { 638 if (F.is.emptyString(str)) return str; 639 if (str.toUpperCase() === str) return str.toLowerCase(); 640 return str.replace(/^([A-Z])|[-\s_](\w)/g, function (match, p1, p2) { 641 if (F.is.string(p2)) return p2.toUpperCase(); 642 return p1.toLowerCase(); 643 }); 644 }; 645 646 /** 647 * Generates a random string 9 characters long using the optional prefix if supplied.
648 * @memberof FooTable.str 649 * @function random 650 * @param {string} [prefix] - The prefix to append to the 9 random characters. 651 * @returns {string} 652 */ 653 F.str.random = function(prefix){ 654 prefix = F.is.emptyString(prefix) ? '' : prefix; 655 return prefix + Math.random().toString(36).substr(2, 9); 656 }; 657 658 /** 659 * Escapes a string for use in a regular expression. 660 * @memberof FooTable.str 661 * @function escapeRegExp 662 * @param {string} str - The string to escape. 663 * @returns {string} 664 */ 665 F.str.escapeRegExp = function(str){ 666 if (F.is.emptyString(str)) return str; 667 return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); 668 }; 669 670})(FooTable); 671(function (F) { 672 "use strict"; 673 674 if (!Object.create) { 675 Object.create = (function () { 676 var Object = function () {}; 677 return function (prototype) { 678 if (arguments.length > 1) 679 throw Error('Second argument not supported'); 680 681 if (!F.is.object(prototype)) 682 throw TypeError('Argument must be an object'); 683 684 Object.prototype = prototype; 685 var result = new Object(); 686 Object.prototype = null; 687 return result; 688 }; 689 })(); 690 } 691 692 /** 693 * This base implementation does nothing except provide access to the {@link FooTable.Class#extend} method. 694 * @constructs FooTable.Class 695 * @classdesc This class is based off of John Resig's [Simple JavaScript Inheritance]{@link http://ejohn.org/blog/simple-javascript-inheritance} but it has been updated to be ES 5.1 696 * compatible by implementing an [Object.create polyfill]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create#Polyfill} 697 * for older browsers. 698 * @see {@link http://ejohn.org/blog/simple-javascript-inheritance} 699 * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create#Polyfill} 700 * @returns {FooTable.Class} 701 */ 702 function Class() {} 703 704 var __extendable__ = /xyz/.test(function () {xyz;}) ? /\b_super\b/ : /.*/; 705 706 // this._super() within the context of the new function is a pointer to the original function 707 // except if the hook param is specified then the this._super variable is the result of the original function 708 Class.__extend__ = function(proto, name, func, original){ 709 // to all who venture here, here be dragons! 710 proto[name] = F.is.fn(original) && __extendable__.test(func) ? 711 (function (name, fn) { 712 return function () { 713 var tmp, ret; 714 tmp = this._super; 715 this._super = original; 716 ret = fn.apply(this, arguments); 717 this._super = tmp; 718 return ret; 719 }; 720 })(name, func) : func; 721 }; 722 723 /** 724 * Creates a new class that inherits from this class which in turn allows itself to be extended or if a name and function is supplied extends only that specific function on the class. 725 * @param {(object|string)} arg1 - An object containing any new methods/members to implement or the name of the method to extend. 726 * @param {function} arg2 - If the first argument is a method name then this is the new function to replace it with. 727 * @returns {FooTable.Class} A new class that inherits from the base class. 728 * @example <caption>The below shows an example of how to implement inheritance using this method.</caption> 729 * var Person = FooTable.Class.extend({ 730 * construct: function(isDancing){ 731 * this.dancing = isDancing; 732 * }, 733 * dance: function(){ 734 * return this.dancing; 735 * } 736 * }); 737 * 738 * var Ninja = Person.extend({ 739 * construct: function(){ 740 * this._super( false ); 741 * }, 742 * dance: function(){ 743 * // Call the inherited version of dance() 744 * return this._super(); 745 * }, 746 * swingSword: function(){ 747 * return true; 748 * } 749 * }); 750 *
751 * var p = new Person(true); 752 * p.dance(); // => true 753 * 754 * var n = new Ninja(); 755 * n.dance(); // => false 756 * n.swingSword(); // => true 757 * 758 * // Should all be true 759 * p instanceof Person && p instanceof FooTable.Class && 760 * n instanceof Ninja && n instanceof Person && n instanceof FooTable.Class 761 */ 762 Class.extend = function (arg1 , arg2) { 763 var args = Array.prototype.slice.call(arguments); 764 arg1 = args.shift(); 765 arg2 = args.shift(); 766 767 function __extend__(proto, name, func, original){ 768 // to all who venture here, here be dragons! 769 proto[name] = F.is.fn(original) && __extendable__.test(func) ? 770 (function (name, fn, ofn) { 771 return function () { 772 var tmp, ret; 773 tmp = this._super; 774 this._super = ofn; 775 ret = fn.apply(this, arguments); 776 this._super = tmp; 777 return ret; 778 }; 779 })(name, func, original) : func; 780 } 781 782 if (F.is.hash(arg1)){ 783 var proto = Object.create(this.prototype), 784 _super = this.prototype; 785 for (var name in arg1) { 786 if (name === '__ctor__') continue; 787 __extend__(proto, name, arg1[name], _super[name]); 788 } 789 var obj = F.is.fn(proto.__ctor__) ? proto.__ctor__ : function () { 790 if (!F.is.fn(this.construct)) 791 throw new SyntaxError('FooTable class objects must be constructed with the "new" keyword.'); 792 this.construct.apply(this, arguments); 793 }; 794 proto.construct = F.is.fn(proto.construct) ? proto.construct : function(){}; 795 obj.prototype = proto; 796 proto.constructor = obj; 797 obj.extend = Class.extend; 798 return obj; 799 } else if (F.is.string(arg1) && F.is.fn(arg2)) { 800 __extend__(this.prototype, arg1, arg2, this.prototype[arg1]); 801 } 802 }; 803 804 F.Class = Class; 805 806 F.ClassFactory = F.Class.extend(/** @lends FooTable.ClassFactory */{ 807 /** 808 * This is a simple factory for {@link FooTable.Class} objects allowing them to be registered using a friendly name 809 * and then new instances can be created using this friendly name. 810 * @constructs 811 * @extends FooTable.Class 812 * @returns {FooTable.ClassFactory} 813 * @this FooTable.ClassFactory 814 */ 815 construct: function(){ 816 /** 817 * An object containing all registered classes. 818 * @type {{}} 819 */ 820 this.registered = {}; 821 }, 822 /** 823 * Checks if the factory contains a class registered using the supplied name. 824 * @instance 825 * @param {string} name - The name of the class to check. 826 * @returns {boolean} 827 * @this FooTable.ClassFactory 828 */ 829 contains: function(name){ 830 return F.is.defined(this.registered[name]); 831 }, 832 /** 833 * Gets an array of all registered names. 834 * @instance 835 * @returns {Array.<string>} 836 * @this FooTable.ClassFactory 837 */ 838 names: function(){ 839 var names = [], name; 840 for (name in this.registered){ 841 if (!this.registered.hasOwnProperty(name)) continue; 842 names.push(name); 843 } 844 return names; 845 }, 846 /** 847 * Registers a class object using the supplied friendly name and priority. The priority is only taken into account when loading all registered classes 848 * using the {@link FooTable.ClassFactory#load} method. 849 * @instance 850 * @param {string} name - The friendly name of the class. 851 * @param {function} klass - The class to register. 852 * @param {number} priority - This determines the order that the class is created when using the {@link FooTable.ClassFactory#load} method, higher values are loaded first. 853 * @this FooTable.ClassFactory 854 */ 855 register: function(name, klass, priority){ 856 if (!F.is.string(name) || !F.is.fn(klass)) return; 857 var current = this.registered[name]; 858 this.registered[name] = { 859 name: name, 860 klass: klass, 861 priority: F.is.number(priority) ? priority : (F.is.defined(current) ? current.priority : 0) 862 }; 863 }, 864 /** 865 * Creates new instances of all registered classes using there priority and the supplied arguments to return them in an array. 866 * @instance 867 * @param {object} subs - An object containing classes to substitute on load. 868 * @param {*} arg1 - The first argument to supply when creating new instances of all registered classes. 869 * @param {*} [argN...] - Any number of additional arguments to supply when creating new instances of all registered classes. 870 * @returns {Array.<FooTable.Class>} 871 * @this FooTable.ClassFactory 872 */ 873 load: function(subs, arg1, argN){ 874 var self = this, args = Array.prototype.slice.call(arguments), reg = [], loaded = [], name, klass; 875 subs = args.shift() || {}; 876 for (name in self.registered){ 877 if (!self.registered.hasOwnProperty(name)) continue; 878 var component = self.registered[name]; 879 if (subs.hasOwnProperty(name)){ 880 klass = subs[name]; 881 if (F.is.string(klass)) klass = F.getFnPointer(subs[name]); 882 if (F.is.fn(klass)){ 883 component = {name: name, klass: klass, priority: self.registered[name].priority}; 884 } 885 } 886 reg.push(component); 887 } 888 for (name in subs){ 889 if (!subs.hasOwnProperty(name) || self.registered.hasOwnProperty(name)) continue; 890 klass = subs[name]; 891 if (F.is.string(klass)) klass = F.getFnPointer(subs[name]); 892 if (F.is.fn(klass)){ 893 reg.push({name: name, klass: klass, priority: 0}); 894 } 895 } 896 reg.sort(function(a, b){ return b.priority - a.priority; }); 897 F.arr.each(reg, function(r){ 898 if (F.is.fn(r.klass)){ 899 loaded.push(self._make(r.klass, args)); 900 } 901 }); 902 return loaded; 903 }, 904 /** 905 * Create a new instance of a single class using the supplied name and arguments. 906 * @instance 907 * @param {string} name - The name of the class to create. 908 * @param {*} arg1 - The first argument to supply to the new instance. 909 * @param {*} [argN...] - Any number of additional arguments to supply to the new instance. 910 * @returns {FooTable.Class} 911 * @this FooTable.ClassFactory 912 */ 913 make: function(name, arg1, argN){ 914 var self = this, args = Array.prototype.slice.call(arguments), reg; 915 name = args.shift(); 916 reg = self.registered[name]; 917 if (F.is.fn(reg.klass)){ 918 return self._make(reg.klass, args); 919 } 920 return null; 921 }, 922 /** 923 * This in effect lets us use the "apply" method on a function using the "new" keyword. 924 * @instance 925 * @private 926 * @param {function} klass 927 * @param args 928 * @returns {FooTable.Class} 929 * @this FooTable.ClassFactory 930 */ 931 _make: function(klass, args){ 932 function Class() { 933 return klass.apply(this, args); 934 }
935 Class.prototype = klass.prototype; 936 return new Class(); 937 } 938 }); 939 940})(FooTable); 941(function($, F){ 942 943 /** 944 * Converts the supplied cssText string into JSON object. 945 * @param {string} cssText - The cssText to convert to a JSON object. 946 * @returns {object} 947 */ 948 F.css2json = function(cssText){ 949 if (F.is.emptyString(cssText)) return {}; 950 var json = {}, props = cssText.split(';'), pair, key, value; 951 for (var i = 0, i_len = props.length; i < i_len; i++){ 952 if (F.is.emptyString(props[i])) continue; 953 pair = props[i].split(':'); 954 if (F.is.emptyString(pair[0]) || F.is.emptyString(pair[1])) continue; 955 key = F.str.toCamelCase($.trim(pair[0])); 956 value = $.trim(pair[1]); 957 json[key] = value; 958 } 959 return json; 960 }; 961 962 /** 963 * Attempts to retrieve a function pointer using the given name. 964 * @param {string} functionName - The name of the function to fetch a pointer to. 965 * @returns {(function|object|null)} 966 */ 967 F.getFnPointer = function(functionName){ 968 if (F.is.emptyString(functionName)) return null; 969 var pointer = window, 970 parts = functionName.split('.'); 971 F.arr.each(parts, function(part){ 972 if (pointer[part]) pointer = pointer[part]; 973 }); 974 return F.is.fn(pointer) ? pointer : null; 975 }; 976 977 /** 978 * Checks the value for function properties such as the {@link FooTable.Column#formatter} option which could also be specified using just the name 979 * and attempts to return the correct function pointer or null if none was found matching the value. 980 * @param {FooTable.Class} self - The class to use as the 'this' keyword within the context of the function. 981 * @param {(function|string)} value - The actual function or the name of the function for the property. 982 * @param {function} [def] - A default function to return if none is found. 983 * @returns {(function|null)} 984 */ 985 F.checkFnValue = function(self, value, def){ 986 def = F.is.fn(def) ? def : null; 987 function wrap(t, fn, d){ 988 if (!F.is.fn(fn)) return d; 989 return function(){ 990 return fn.apply(t, arguments); 991 }; 992 } 993 return F.is.fn(value) ? wrap(self, value, def) : (F.is.type(value, 'string') ? wrap(self, F.getFnPointer(value), def) : def); 994 }; 995 996})(jQuery, FooTable); 997(function($, F){ 998 999 F.Cell = F.Class.extend(/** @lends FooTable.Cell */{ 1000 /** 1001 * The cell class containing all the properties for cells. 1002 * @constructs 1003 * @extends FooTable.Class 1004 * @param {FooTable.Table} table - The root {@link FooTable.Table} this cell belongs to. 1005 * @param {FooTable.Row} row - The parent {@link FooTable.Row} this cell belongs to. 1006 * @param {FooTable.Column} column - The {@link FooTable.Column} this cell falls under. 1007 * @param {(*|HTMLElement|jQuery)} valueOrElement - Either the value or the element for the cell. 1008 * @returns {FooTable.Cell} 1009 * @this FooTable.Cell 1010 */ 1011 construct: function (table, row, column, valueOrElement) { 1012 /** 1013 * The root {@link FooTable.Table} for the cell. 1014 * @instance 1015 * @readonly 1016 * @type {FooTable.Table} 1017 */ 1018 this.ft = table; 1019 /** 1020 * The parent {@link FooTable.Row} for the cell. 1021 * @instance 1022 * @readonly 1023 * @type {FooTable.Row} 1024 */ 1025 this.row = row; 1026 /** 1027 * The {@link FooTable.Column} this cell falls under. 1028 * @instance 1029 * @readonly 1030 * @type {FooTable.Column} 1031 */ 1032 this.column = column; 1033 this.created = false; 1034 this.define(valueOrElement); 1035 }, 1036 /** 1037 * This is supplied either the value or the cell element/jQuery object if it exists. 1038 * If supplied the element we need set the $el property and parse the value from it. 1039 * @instance 1040 * @protected 1041 * @param {(*|jQuery)} valueOrElement - The value or element to define the cell. 1042 * @this FooTable.Cell 1043 */ 1044 define: function(valueOrElement){ 1045 /** 1046 * The jQuery table cell object this instance wraps. 1047 * @instance 1048 * @type {jQuery} 1049 */ 1050 this.$el = F.is.element(valueOrElement) || F.is.jq(valueOrElement) ? $(valueOrElement) : null; 1051 /** 1052 * The jQuery row object that represents this cell in the details table. 1053 * @type {jQuery} 1054 */ 1055 this.$detail = null; 1056 1057 var hasOptions = F.is.hash(valueOrElement) && F.is.hash(valueOrElement.options) && F.is.defined(valueOrElement.value); 1058 1059 /** 1060 * The value of the cell. 1061 * @instance 1062 * @type {*} 1063 */ 1064 this.value = this.column.parser.call(this.column, F.is.jq(this.$el) ? this.$el : (hasOptions ? valueOrElement.value : valueOrElement), this.ft.o); 1065 1066 /** 1067 * Contains any options for the cell. These are the options supplied through the plugin constructor as part of the row object itself. 1068 * @type {object} 1069 */ 1070 this.o = $.extend(true, { 1071 classes: null, 1072 style: null 1073 }, hasOptions ? valueOrElement.options : {}); 1074 /** 1075 * An array of CSS classes for the cell. 1076 * @instance 1077 * @protected 1078 * @type {Array.<string>} 1079 */ 1080 this.classes = F.is.jq(this.$el) && this.$el.attr('class') ? this.$el.attr('class').match(/\S+/g) : (F.is.array(this.o.classes) ? this.o.classes : (F.is.string(this.o.classes) ? this.o.classes.match(/\S+/g) : [])); 1081 /** 1082 * The inline styles for the cell. 1083 * @instance 1084 * @protected 1085 * @type {object} 1086 */ 1087 this.style = F.is.jq(this.$el) && this.$el.attr('style') ? F.css2json(this.$el.attr('style')) : (F.is.hash(this.o.style) ? this.o.style : (F.is.string(this.o.style) ? F.css2json(this.o.style) : {})); 1088 }, 1089 /** 1090 * After the cell has been defined this ensures that the $el and #detail properties are jQuery objects by either creating or updating them. 1091 * @instance 1092 * @protected 1093 * @this FooTable.Cell 1094 */ 1095 $create: function(){ 1096 if (this.created) return; 1097 (this.$el = F.is.jq(this.$el) ? this.$el : $('<td/>')) 1098 .data('value', this.value) 1099 .contents().detach().end() 1100 .append(this.format(this.value)); 1101 1102 this._setClasses(this.$el); 1103 this._setStyle(this.$el); 1104 1105 this.$detail = $('<tr/>').addClass(this.row.classes.join(' ')) 1106 .data('__FooTableCell__', this) 1107 .append($('<th/>')) 1108 .append($('<td/>')); 1109 1110 this.created = true; 1111 }, 1112 /** 1113 * Collapses this cell and displays it in the details row. 1114 * @instance 1115 * @protected 1116 */ 1117 collapse: function(){ 1118 if (!this.created) return; 1119 this.$detail.children('th').html(this.column.title); 1120 this.$el.clone() 1121 .attr('id', this.$el.attr('id') ? this.$el.attr('id') + '-detail' : undefined) 1122 .css('display', 'table-cell') 1123 .html('') 1124 .append(this.$el.contents().detach()) 1125 .replaceAll(this.$detail.children('td').first()); 1126 1127 if (!F.is.jq(this.$detail.parent())) 1128 this.$detail.appendTo(this.row.$details.find('.footable-details > tbody')); 1129 }, 1130 /** 1131 * Restores this cell from a detail row back into the normal row. 1132 * @instance 1133 * @protected 1134 */ 1135 restore: function(){ 1136 if (!this.created) return; 1137 if (F.is.jq(this.$detail.parent())){ 1138 var $cell = this.$detail.children('td').first(); 1139 this.$el 1140 .attr('class', $cell.attr('class')) 1141 .attr('style', $cell.attr('style')) 1142 .css('display', (this.column.hidden || !this.column.visible) ? 'none' : 'table-cell') 1143 .append($cell.contents().detach()); 1144 } 1145 this.$detail.detach(); 1146 }, 1147 /** 1148 * Helper method to call this cell's column parser function supplying the required parameters. 1149 * @instance 1150 * @protected 1151 * @returns {*} 1152 * @see FooTable.Column#parser 1153 * @this FooTable.Cell 1154 */ 1155 parse: function(){ 1156 return this.column.parser.call(this.column, this.$el, this.ft.o); 1157 }, 1158 /** 1159 * Helper method to call this cell's column formatter function using the supplied value and any additional required parameters. 1160 * @instance 1161 * @protected 1162 * @param {*} value - The value to format. 1163 * @returns {(string|HTMLElement|jQuery)} 1164 * @see FooTable.Column#formatter 1165 * @this FooTable.Cell 1166 */ 1167 format: function(value){ 1168 return this.column.formatter.call(this.column, value, this.ft.o, this.row.value); 1169 }, 1170 /** 1171 * Allows easy access to getting or setting the cell's value. If the value is set all associated properties are also updated along with the actual element.
1172 * Using this method also allows us to supply an object containing options and the value for the cell. 1173 * @instance 1174 * @param {*} [value] - The value to set for the cell. If not supplied the current value of the cell is returned. 1175 * @param {boolean} [redraw=true] - Whether or not to redraw the row once the value has been set. 1176 * @param {boolean} [redrawSelf=true] - Whether or not to redraw the cell itself once the value has been set, if `false` this will override the supplied `redraw` value and prevent the row from redrawing as well. 1177 * @returns {(*|undefined)} 1178 * @this FooTable.Cell 1179 */ 1180 val: function(value, redraw, redrawSelf){ 1181 if (F.is.undef(value)){ 1182 // get 1183 return this.value; 1184 } 1185 // set 1186 var self = this, hasOptions = F.is.hash(value) && F.is.hash(value.options) && F.is.defined(value.value); 1187 this.o = $.extend(true, { 1188 classes: self.classes, 1189 style: self.style 1190 }, hasOptions ? value.options : {}); 1191 1192 this.value = hasOptions ? value.value : value; 1193 this.classes = F.is.array(this.o.classes) ? this.o.classes : (F.is.string(this.o.classes) ? this.o.classes.match(/\S+/g) : []); 1194 this.style = F.is.hash(this.o.style) ? this.o.style : (F.is.string(this.o.style) ? F.css2json(this.o.style) : {}); 1195 1196 redrawSelf = F.is.boolean(redrawSelf) ? redrawSelf : true; 1197 if (this.created && redrawSelf){ 1198 this.$el.data('value', this.value).empty(); 1199 1200 var $detail = this.$detail.children('td').first().empty(), 1201 $target = F.is.jq(this.$detail.parent()) ? $detail : this.$el; 1202 1203 $target.append(this.format(this.value)); 1204 1205 this._setClasses($target); 1206 this._setStyle($target); 1207 1208 if (F.is.boolean(redraw) ? redraw : true) this.row.draw(); 1209 } 1210 }, 1211 _setClasses: function($el){ 1212 var hasColClasses = !F.is.emptyArray(this.column.classes), 1213 hasClasses = !F.is.emptyArray(this.classes), 1214 classes = null; 1215 $el.removeAttr('class'); 1216 if (!hasColClasses && !hasClasses) return; 1217 if (hasColClasses && hasClasses){ 1218 classes = this.classes.concat(this.column.classes).join(' '); 1219 } else if (hasColClasses) { 1220 classes = this.column.classes.join(' '); 1221 } else if (hasClasses){ 1222 classes = this.classes.join(' '); 1223 } 1224 if (!F.is.emptyString(classes)){
1225 $el.addClass(classes); 1226 } 1227 }, 1228 _setStyle: function($el){ 1229 var hasColStyle = !F.is.emptyObject(this.column.style), 1230 hasStyle = !F.is.emptyObject(this.style), 1231 style = null; 1232 $el.removeAttr('style'); 1233 if (!hasColStyle && !hasStyle) return; 1234 if (hasColStyle && hasStyle){ 1235 style = $.extend({}, this.column.style, this.style); 1236 } else if (hasColStyle) { 1237 style = this.column.style; 1238 } else if (hasStyle){ 1239 style = this.style; 1240 } 1241 if (F.is.hash(style)){ 1242 $el.css(style); 1243 } 1244 } 1245 }); 1246 1247})(jQuery, FooTable); 1248(function($, F){ 1249 1250 F.Column = F.Class.extend(/** @lends FooTable.Column */{ 1251 /** 1252 * The column class containing all the properties for columns. All members marked as "readonly" should not be used when defining {@link FooTable.Defaults#columns}. 1253 * @constructs 1254 * @extends FooTable.Class 1255 * @param {FooTable.Table} instance - The parent {@link FooTable.Table} this component belongs to. 1256 * @param {object} definition - An object containing all the properties to set for the column. 1257 * @param {string} [type] - The type of column, "text" by default. 1258 * @returns {FooTable.Column} 1259 * @this FooTable.Column 1260 */ 1261 construct: function(instance, definition, type){ 1262 /** 1263 * The root {@link FooTable.Table} for the column. 1264 * @instance 1265 * @readonly 1266 * @type {FooTable.Table} 1267 */ 1268 this.ft = instance; 1269 /** 1270 * The type of data displayed by the column. 1271 * @instance 1272 * @readonly 1273 * @type {string} 1274 */ 1275 this.type = F.is.emptyString(type) ? 'text' : type; 1276 /** 1277 * Whether or not the column was parsed from a standard table row containing data instead of from an actual header row. 1278 * @instance 1279 * @readonly 1280 * @type {boolean} 1281 */ 1282 this.virtual = F.is.boolean(definition.virtual) ? definition.virtual : false; 1283 /** 1284 * The jQuery cell object for the column header. 1285 * @instance 1286 * @readonly 1287 * @type {jQuery} 1288 */ 1289 this.$el = F.is.jq(definition.$el) ? definition.$el : null; 1290 /** 1291 * The index of the column in the table. This is set by the plugin during initialization. 1292 * @instance 1293 * @readonly 1294 * @type {number} 1295 * @default -1 1296 */ 1297 this.index = F.is.number(definition.index) ? definition.index : -1; 1298 /** 1299 * Whether or not this in an internal only column. 1300 * @instance 1301 * @readonly 1302 * @type {boolean} 1303 * @description Internal columns or there cells will not be returned when calling methods such as `FooTable.Row#val`. 1304 */ 1305 this.internal = false; 1306 this.define(definition); 1307 this.$create(); 1308 }, 1309 /** 1310 * This is supplied the column definition in the form of a simple object created by merging options supplied via the plugin constructor with those parsed from the DOM. 1311 * @instance 1312 * @protected 1313 * @param {object} definition - The object containing the column definition. 1314 * @this FooTable.Column 1315 */ 1316 define: function(definition){ 1317 /** 1318 * Whether or not this column is hidden from view and appears in the details row. 1319 * @type {boolean} 1320 * @default false 1321 */ 1322 this.hidden = F.is.boolean(definition.hidden) ? definition.hidden : false; 1323 /** 1324 * Whether or not this column is completely hidden from view and will not appear in the details row. 1325 * @type {boolean} 1326 * @default true 1327 */ 1328 this.visible = F.is.boolean(definition.visible) ? definition.visible : true; 1329 1330 /** 1331 * The name of the column. This name must correspond to the property name of the JSON row data. 1332 * @type {string} 1333 * @default null 1334 */ 1335 this.name = F.is.string(definition.name) ? definition.name : null; 1336 if (this.name == null) this.name = 'col'+(definition.index+1); 1337 /** 1338 * The title to display in the column header, this can be HTML. 1339 * @type {string} 1340 * @default null 1341 */ 1342 this.title = F.is.string(definition.title) ? definition.title : null; 1343 if (!this.virtual && this.title == null && F.is.jq(this.$el)) this.title = this.$el.html(); 1344 if (this.title == null) this.title = 'Column '+(definition.index+1); 1345 /** 1346 * The styles to apply to all cells in this column. 1347 * @type {object} 1348 */ 1349 this.style = F.is.hash(definition.style) ? definition.style : (F.is.string(definition.style) ? F.css2json(definition.style) : {}); 1350 /** 1351 * The classes to apply to all cells in this column. 1352 * @type {Array.<string>} 1353 */
1354 this.classes = F.is.array(definition.classes) ? definition.classes : (F.is.string(definition.classes) ? definition.classes.match(/\S+/g) : []); 1355 1356 // override any default functions ensuring when they are executed "this" within the context of the function points to the instance of this object. 1357 this.parser = F.checkFnValue(this, definition.parser, this.parser); 1358 this.formatter = F.checkFnValue(this, definition.formatter, this.formatter); 1359 }, 1360 /** 1361 * After the column has been defined this ensures that the $el property is a jQuery object by either creating or updating the current value. 1362 * @instance 1363 * @protected 1364 * @this FooTable.Column 1365 */ 1366 $create: function(){ 1367 (this.$el = !this.virtual && F.is.jq(this.$el) ? this.$el : $('<th/>')).html(this.title).addClass(this.classes.join(' ')).css(this.style); 1368 }, 1369 /** 1370 * This is supplied either the cell value or jQuery object to parse. Any value can be returned from this method and will be provided to the {@link FooTable.Column#format} function 1371 * to generate the cell contents. 1372 * @instance 1373 * @protected 1374 * @param {(*|jQuery)} valueOrElement - The value or jQuery cell object. 1375 * @returns {string} 1376 * @this FooTable.Column 1377 */ 1378 parser: function(valueOrElement){ 1379 if (F.is.element(valueOrElement) || F.is.jq(valueOrElement)){ // use jQuery to get the value 1380 var data = $(valueOrElement).data('value'); 1381 return F.is.defined(data) ? data : $(valueOrElement).html(); 1382 } 1383 if (F.is.defined(valueOrElement) && valueOrElement != null) return valueOrElement+''; // use the native toString of the value 1384 return null; // otherwise we have no value so return null 1385 }, 1386 /** 1387 * This is supplied the value retrieved from the {@link FooTable.Column#parse} function and must return a string, HTMLElement or jQuery object. 1388 * The return value from this function is what is displayed in the cell in the table. 1389 * @instance 1390 * @protected 1391 * @param {string} value - The value to format. 1392 * @param {object} options - The current plugin options. 1393 * @param {object} rowData - An object containing the current row data. 1394 * @returns {(string|HTMLElement|jQuery)} 1395 * @this FooTable.Column 1396 */ 1397 formatter: function(value, options, rowData){ 1398 return value == null ? '' : value; 1399 }, 1400 /** 1401 * Creates a cell for this column from the supplied {@link FooTable.Row} object. This allows different column types to return different types of cells. 1402 * @instance 1403 * @protected 1404 * @param {FooTable.Row} row - The row to create the cell from. 1405 * @returns {FooTable.Cell} 1406 * @this FooTable.Column 1407 */ 1408 createCell: function(row){ 1409 var element = F.is.jq(row.$el) ? row.$el.children('td,th').get(this.index) : null, 1410 data = F.is.hash(row.value) ? row.value[this.name] : null; 1411 return new F.Cell(this.ft, row, this, element || data); 1412 } 1413 }); 1414 1415 F.columns = new F.ClassFactory(); 1416 1417 F.columns.register('text', F.Column); 1418 1419})(jQuery, FooTable); 1420(function ($, F) { 1421 1422 F.Component = F.Class.extend(/** @lends FooTable.Component */{ 1423 /** 1424 * The base class for all FooTable components. 1425 * @constructs 1426 * @extends FooTable.Class 1427 * @param {FooTable.Table} instance - The parent {@link FooTable.Table} object for the component. 1428 * @param {boolean} enabled - Whether or not the component is enabled. 1429 * @throws {TypeError} The instance parameter must be an instance of {@link FooTable.Table}. 1430 * @returns {FooTable.Component} 1431 */ 1432 construct: function (instance, enabled) { 1433 if (!(instance instanceof F.Table)) 1434 throw new TypeError('The instance parameter must be an instance of FooTable.Table.'); 1435 1436 /** 1437 * The parent {@link FooTable.Table} for the component. 1438 * @type {FooTable.Table} 1439 */ 1440 this.ft = instance; 1441 /** 1442 * Whether or not this component is enabled. Disabled components only have there preinit method called allowing for this value to be overridden. 1443 * @type {boolean} 1444 */ 1445 this.enabled = F.is.boolean(enabled) ? enabled : false; 1446 }, 1447 /** 1448 * The preinit method is called during the parent {@link FooTable.Table} constructor call. 1449 * @param {object} data - The jQuery.data() object of the root table. 1450 * @instance 1451 * @protected 1452 * @function 1453 */ 1454 preinit: function(data){}, 1455 /** 1456 * The init method is called during the parent {@link FooTable.Table} constructor call. 1457 * @instance 1458 * @protected 1459 * @function 1460 */ 1461 init: function(){}, 1462 /**
1463 * This method is called from the {@link FooTable.Table#destroy} method. 1464 * @instance 1465 * @protected 1466 * @function 1467 */ 1468 destroy: function(){}, 1469 /** 1470 * This method is called from the {@link FooTable.Table#draw} method. 1471 * @instance 1472 * @protected 1473 * @function 1474 */ 1475 predraw: function(){}, 1476 /** 1477 * This method is called from the {@link FooTable.Table#draw} method. 1478 * @instance 1479 * @protected 1480 * @function 1481 */ 1482 draw: function(){}, 1483 /** 1484 * This method is called from the {@link FooTable.Table#draw} method. 1485 * @instance 1486 * @protected 1487 * @function 1488 */ 1489 postdraw: function(){} 1490 }); 1491 1492 F.components = new F.ClassFactory(); 1493 1494})(jQuery, FooTable); 1495(function ($, F) { 1496 /** 1497 * Contains all the available options for the FooTable plugin. 1498 * @name FooTable.Defaults 1499 * @function 1500 * @constructor 1501 * @returns {FooTable.Defaults} 1502 */ 1503 F.Defaults = function () { 1504 /** 1505 * Whether or not events raised using the {@link FooTable.Table#raise} method are propagated up the DOM. By default this is set to false and all events bubble up the DOM as per usual 1506 * however the reason for this option is if we have nested tables. If false the parent table would receive all the events raised by it's children and any handlers bound to both the 1507 * parent and child would be triggered which is not the desired behavior. 1508 * @type {boolean} 1509 * @default false 1510 */ 1511 this.stopPropagation = false; 1512 /** 1513 * An object in which the string keys represent one or more space-separated event types and optional namespaces, and the values represent a handler function to be called for the event(s). 1514 * @type {object.<string, function>} 1515 * @default NULL 1516 * @example <caption>
1516This example shows how to pass an object containing the events and handlers.</caption> 1517 * "on": { 1518 * "click": function(e){ 1519 * // bind a custom click event to do something whenever the table is clicked 1520 * }, 1521 * "init.ft.table": function(e, ft){ 1522 * // bind to the FooTable initialize event to do something 1523 * } 1524 * } 1525 */ 1526 this.on = null; 1527 }; 1528 1529 /** 1530 * Contains all the default options for the plugin. 1531 * @type {FooTable.Defaults} 1532 */ 1533 F.defaults = new F.Defaults(); 1534 1535})(jQuery, FooTable); 1536(function($, F){ 1537 1538 F.Row = F.Class.extend(/** @lends FooTable.Row */{ 1539 /** 1540 * The row class containing all the properties for a row and its' cells. 1541 * @constructs 1542 * @extends FooTable.Class 1543 * @param {FooTable.Table} table - The parent {@link FooTable.Table} this component belongs to. 1544 * @param {Array.<FooTable.Column>} columns - The array of {@link FooTable.Column} for this row. 1545 * @param {(*|HTMLElement|jQuery)} dataOrElement - Either the data for the row (create) or the element (parse) for the row. 1546 * @returns {FooTable.Row} 1547 */ 1548 construct: function (table, columns, dataOrElement) { 1549 /** 1550 * The {@link FooTable.Table} for the row. 1551 * @type {FooTable.Table} 1552 */ 1553 this.ft = table; 1554 /** 1555 * The array of {@link FooTable.Column} for this row. 1556 * @type {Array.<FooTable.Column>} 1557 */ 1558 this.columns = columns; 1559 1560 this.created = false; 1561 this.define(dataOrElement); 1562 }, 1563 /** 1564 * This is supplied either the object containing the values for the row or the row element/jQuery object if it exists. 1565 * If supplied the element we need to set the $el property and parse the cells from it using the column index. 1566 * If we have an object we parse the cells from it using the column name. 1567 * @param {(object|jQuery)} dataOrElement - The row object or element to define the row. 1568 */ 1569 define: function(dataOrElement){ 1570 /** 1571 * The jQuery table row object this instance wraps. 1572 * @instance 1573 * @protected 1574 * @type {jQuery} 1575 */ 1576 this.$el = F.is.element(dataOrElement) || F.is.jq(dataOrElement) ? $(dataOrElement) : null; 1577 /** 1578 * The jQuery toggle element for the row. 1579 * @instance 1580 * @protected 1581 * @type {jQuery} 1582 */ 1583 this.$toggle = $('<span/>', {'class': 'footable-toggle fooicon fooicon-plus'}); 1584 1585 var isObj = F.is.hash(dataOrElement), 1586 hasOptions = isObj && F.is.hash(dataOrElement.options) && F.is.hash(dataOrElement.value); 1587 1588 /** 1589 * The value of the row. 1590 * @instance 1591 * @protected 1592 * @type {Object} 1593 */ 1594 this.value = isObj ? (hasOptions ? dataOrElement.value : dataOrElement) : null; 1595 1596 /** 1597 * Contains any options for the row. 1598 * @type {object} 1599 */ 1600 this.o = $.extend(true, { 1601 expanded: false, 1602 classes: null, 1603 style: null 1604 }, hasOptions ? dataOrElement.options : {}); 1605 1606 /** 1607 * Whether or not this row is expanded and will display it's detail row when there are any hidden columns. 1608 * @instance 1609 * @protected 1610 * @type {boolean} 1611 */ 1612 this.expanded = F.is.jq(this.$el) ? (this.$el.data('expanded') || this.o.expanded) : this.o.expanded; 1613 /** 1614 * An array of CSS classes for the row. 1615 * @instance 1616 * @protected 1617 * @type {Array.<string>} 1618 */ 1619 this.classes = F.is.jq(this.$el) && this.$el.attr('class') ? this.$el.attr('class').match(/\S+/g) : (F.is.array(this.o.classes) ? this.o.classes : (F.is.string(this.o.classes) ? this.o.classes.match(/\S+/g) : [])); 1620 /** 1621 * The inline styles for the row. 1622 * @instance 1623 * @protected 1624 * @type {object} 1625 */ 1626 this.style = F.is.jq(this.$el) && this.$el.attr('style') ? F.css2json(this.$el.attr('style')) : (F.is.hash(this.o.style) ? this.o.style : (F.is.string(this.o.style) ? F.css2json(this.o.style) : {})); 1627 1628 /** 1629 * The cells array. This is populated before the call to the {@link FooTable.Row#$create} method. 1630 * @instance 1631 * @type {Array.<FooTable.Cell>} 1632 */ 1633 this.cells = this.createCells(); 1634 1635 // this ensures the value contains the parsed cell values and not the supplied values 1636 var self = this; 1637 self.value = {}; 1638 F.arr.each(self.cells, function(cell){ 1639 self.value[cell.column.name] = cell.val(); 1640 }); 1641 }, 1642 /** 1643 * After the row has been defined this ensures that the $el property is a jQuery object by either creating or updating the current value. 1644 * @instance 1645 * @protected 1646 * @this FooTable.Row 1647 */ 1648 $create: function(){ 1649 if (this.created) return; 1650 (this.$el = F.is.jq(this.$el) ? this.$el : $('<tr/>')) 1651 .data('__FooTableRow__', this); 1652 1653 this._setClasses(this.$el); 1654 this._setStyle(this.$el); 1655 1656 if (this.ft.rows.toggleColumn == 'last') this.$toggle.addClass('last-column'); 1657 1658 this.$details = $('<tr/>', { 'class': 'footable-detail-row' }) 1659 .append($('<td/>', { colspan: this.ft.columns.visibleColspan }) 1660 .append($('<table/>', { 'class': 'footable-details ' + this.ft.classes.join(' ') }) 1661 .append('<tbody/>'))); 1662 1663 var self = this; 1664 F.arr.each(self.cells, function(cell){ 1665 if (!cell.created) cell.$create(); 1666 self.$el.append(cell.$el); 1667 }); 1668 self.$el.off('click.ft.row').on('click.ft.row', { self: self }, self._onToggle); 1669 this.created = true; 1670 }, 1671 /** 1672 * This is called during the construct method and uses the current column definitions to create an array of {@link FooTable.Cell} objects for the row. 1673 * @instance 1674 * @protected 1675 * @returns {Array.<FooTable.Cell>} 1676 * @this FooTable.Row 1677 */ 1678 createCells: function(){ 1679 var self = this; 1680 return F.arr.map(self.columns, function(col){ 1681 return col.createCell(self); 1682 }); 1683 }, 1684 /** 1685 * Allows easy access to getting or setting the row's data. If the data is set all associated properties are also updated along with the actual element.
1686 * Using this method also allows us to supply an object containing options and the data for the row at the same time. 1687 * @instance 1688 * @param {object} [data] - The data to set for the row. If not supplied the current value of the row is returned. 1689 * @param {boolean} [redraw=true] - Whether or not to redraw the table once the value has been set. 1690 * @param {boolean} [redrawSelf=true] - Whether or not to redraw the row itself once the value has been set, if `false` this will override the supplied `redraw` value and prevent the table from redrawing as well. 1691 * @returns {(*|undefined)} 1692 */ 1693 val: function(data, redraw, redrawSelf){ 1694 var self = this; 1695 if (!F.is.hash(data)){ 1696 // get - check the value property and build it from the cells if required. 1697 if (!F.is.hash(this.value) || F.is.emptyObject(this.value)){ 1698 this.value = {}; 1699 F.arr.each(this.cells, function(cell){ 1700 if (!cell.column.internal){ 1701 self.value[cell.column.name] = cell.val(); 1702 } 1703 }); 1704 } 1705 return this.value; 1706 } 1707 // set 1708 this.collapse(false); 1709 var isObj = F.is.hash(data), 1710 hasOptions = isObj && F.is.hash(data.options) && F.is.hash(data.value); 1711 1712 this.o = $.extend(true, { 1713 expanded: self.expanded, 1714 classes: self.classes, 1715 style: self.style 1716 }, hasOptions ? data.options : {}); 1717 1718 this.expanded = this.o.expanded; 1719 this.classes = F.is.array(this.o.classes) ? this.o.classes : (F.is.string(this.o.classes) ? this.o.classes.match(/\S+/g) : []); 1720 this.style = F.is.hash(this.o.style) ? this.o.style : (F.is.string(this.o.style) ? F.css2json(this.o.style) : {}); 1721 if (isObj) { 1722 if ( hasOptions ) data = data.value; 1723 if (F.is.hash(this.value)){ 1724 for (var prop in data) { 1725 if (!data.hasOwnProperty(prop)) continue; 1726 this.value[prop] = data[prop]; 1727 } 1728 } else { 1729 this.value = data; 1730 } 1731 } else { 1732 this.value = null; 1733 } 1734 1735 redrawSelf = F.is.boolean(redrawSelf) ? redrawSelf : true; 1736 F.arr.each(this.cells, function(cell){ 1737 if (!cell.column.internal && F.is.defined(self.value[cell.column.name])){ 1738 cell.val(self.value[cell.column.name], false, redrawSelf); 1739 } 1740 }); 1741 1742 if (this.created && redrawSelf){ 1743 this._setClasses(this.$el); 1744 this._setStyle(this.$el); 1745 if (F.is.boolean(redraw) ? redraw : true) this.draw(); 1746 } 1747 }, 1748 _setClasses: function($el){ 1749 var hasClasses = !F.is.emptyArray(this.classes), 1750 classes = null; 1751 $el.removeAttr('class'); 1752 if (!hasClasses) return; 1753 else classes = this.classes.join(' '); 1754 if (!F.is.emptyString(classes)){
1755 $el.addClass(classes); 1756 } 1757 }, 1758 _setStyle: function($el){ 1759 var hasStyle = !F.is.emptyObject(this.style), 1760 style = null; 1761 $el.removeAttr('style'); 1762 if (!hasStyle) return; 1763 else style = this.style; 1764 if (F.is.hash(style)){ 1765 $el.css(style); 1766 } 1767 }, 1768 /** 1769 * Sets the current row to an expanded state displaying any hidden columns in a detail row just below it. 1770 * @instance 1771 * @fires FooTable.Row#"expand.ft.row" 1772 */ 1773 expand: function(){ 1774 if (!this.created) return; 1775 var self = this; 1776 /** 1777 * The expand.ft.row event is raised before the the row is expanded. 1778 * Calling preventDefault on this event will stop the row being expanded. 1779 * @event FooTable.Row#"expand.ft.row" 1780 * @param {jQuery.Event} e - The jQuery.Event object for the event. 1781 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 1782 * @param {FooTable.Row} row - The row about to be expanded. 1783 */ 1784 self.ft.raise('expand.ft.row',[self]).then(function(){ 1785 self.__hidden__ = F.arr.map(self.cells, function(cell){ 1786 return cell.column.hidden && cell.column.visible ? cell : null; 1787 }); 1788 1789 if (self.__hidden__.length > 0){ 1790 self.$details.insertAfter(self.$el) 1791 .children('td').first() 1792 .attr('colspan', self.ft.columns.visibleColspan); 1793 1794 F.arr.each(self.__hidden__, function(cell){ 1795 cell.collapse(); 1796 }); 1797 } 1798 self.$el.attr('data-expanded', true); 1799 self.$toggle.removeClass('fooicon-plus').addClass('fooicon-minus'); 1800 self.expanded = true; 1801 self.ft.raise('expanded.ft.row', [self]); 1802 }); 1803 }, 1804 /** 1805 * Sets the current row to a collapsed state removing the detail row if it exists. 1806 * @instance 1807 * @param {boolean} [setExpanded] - Whether or not to set the {@link FooTable.Row#expanded} property to false. 1808 * @fires FooTable.Row#"collapse.ft.row" 1809 */ 1810 collapse: function(setExpanded){ 1811 if (!this.created) return; 1812 var self = this; 1813 /** 1814 * The collapse.ft.row event is raised before the the row is collapsed. 1815 * Calling preventDefault on this event will stop the row being collapsed. 1816 * @event FooTable.Row#"collapse.ft.row" 1817 * @param {jQuery.Event} e - The jQuery.Event object for the event. 1818 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 1819 * @param {FooTable.Row} row - The row about to be expanded. 1820 */ 1821 self.ft.raise('collapse.ft.row',[self]).then(function(){ 1822 F.arr.each(self.__hidden__, function(cell){ 1823 cell.restore(); 1824 }); 1825 self.$details.detach(); 1826 self.$el.removeAttr('data-expanded'); 1827 self.$toggle.removeClass('fooicon-minus').addClass('fooicon-plus'); 1828 if (F.is.boolean(setExpanded) ? setExpanded : true) self.expanded = false; 1829 self.ft.raise('collapsed.ft.row', [self]); 1830 }); 1831 }, 1832 /** 1833 * Prior to drawing this moves the details contents back to there original cells and detaches the toggle element from the row. 1834 * @instance 1835 * @param {boolean} [detach] - Whether or not to detach the row. 1836 * @this FooTable.Row 1837 */ 1838 predraw: function(detach){ 1839 if (this.created){ 1840 if (this.expanded){ 1841 this.collapse(false); 1842 } 1843 this.$toggle.detach(); 1844 detach = F.is.boolean(detach) ? detach : true; 1845 if (detach) this.$el.detach(); 1846 } 1847 }, 1848 /** 1849 * Draws the current row and cells. 1850 * @instance 1851 * @this FooTable.Row 1852 */ 1853 draw: function($parent){ 1854 if (!this.created) this.$create(); 1855 if (F.is.jq($parent)) $parent.append(this.$el); 1856 var self = this; 1857 F.arr.each(self.cells, function(cell){ 1858 cell.$el.css('display', (cell.column.hidden || !cell.column.visible ? 'none' : 'table-cell')); 1859 if (self.ft.rows.showToggle && self.ft.columns.hasHidden){ 1860 if ((self.ft.rows.toggleColumn == 'first' && cell.column.index == self.ft.columns.firstVisibleIndex) 1861 || (self.ft.rows.toggleColumn == 'last' && cell.column.index == self.ft.columns.lastVisibleIndex)) { 1862 cell.$el.prepend(self.$toggle); 1863 } 1864 } 1865 cell.$el.add(cell.column.$el).removeClass('footable-first-visible footable-last-visible footable-first-visible-pad'); 1866 if (cell.column.index == self.ft.columns.firstVisibleIndex){ 1867 if (self.ft.rows.showToggle && self.ft.columns.hasHidden) { 1868 cell.$el.add(cell.column.$el).addClass('footable-first-visible footable-first-visible-pad'); 1869 } else { 1870 cell.$el.add(cell.column.$el).addClass('footable-first-visible'); 1871 } 1872 } 1873 if (cell.column.index == self.ft.columns.lastVisibleIndex){ 1874 cell.$el.add(cell.column.$el).addClass('footable-last-visible'); 1875 } 1876 }); 1877 if (this.expanded){ 1878 this.expand(); 1879 } 1880 }, 1881 /**
1882 * Toggles the row between it's expanded and collapsed state if there are hidden columns. 1883 * @instance 1884 * @this FooTable.Row 1885 */ 1886 toggle: function(){ 1887 if (this.created && this.ft.columns.hasHidden){ 1888 if (this.expanded) this.collapse(); 1889 else this.expand(); 1890 } 1891 }, 1892 /** 1893 * Handles the toggle click event for rows. 1894 * @instance 1895 * @param {jQuery.Event} e - The jQuery.Event object for the click event. 1896 * @private 1897 * @this jQuery 1898 */ 1899 _onToggle: function (e) { 1900 var self = e.data.self; 1901 // only execute the toggle if the event.target is one of the approved initiators 1902 if ($(e.target).is(self.ft.rows.toggleSelector)){ 1903 self.toggle(); 1904 } 1905 } 1906 }); 1907 1908})(jQuery, FooTable); 1909 1910(function ($, F) { 1911 1912 /** 1913 * An array of all currently loaded instances of the plugin. 1914 * @protected 1915 * @readonly 1916 * @type {Array.<FooTable.Table>} 1917 */ 1918 F.instances = []; 1919 1920 F.Table = F.Class.extend(/** @lends FooTable.Table */{ 1921 /** 1922 * This class is the core of the plugin and drives the logic of all components. 1923 * @constructs 1924 * @this FooTable.Table 1925 * @extends FooTable.Class 1926 * @param {(HTMLTableElement|jQuery)} element - The element or jQuery table object to bind the plugin to. 1927 * @param {object} options - The options to initialize the plugin with. 1928 * @param {function} [ready] - A callback function to execute once the plugin is initialized. 1929 * @returns {FooTable.Table} 1930 */ 1931 construct: function (element, options, ready) { 1932 //BEGIN MEMBERS 1933 /** 1934 * The timeout ID for the resize event. 1935 * @instance 1936 * @private 1937 * @type {?number} 1938 */ 1939 this._resizeTimeout = null; 1940 /** 1941 * The ID of the FooTable instance. 1942 * @instance 1943 * @type {number} 1944 */ 1945 this.id = F.instances.push(this); 1946 /** 1947 * Whether or not the plugin and all components and add-ons are fully initialized. 1948 * @instance 1949 * @type {boolean} 1950 */ 1951 this.initialized = false; 1952 /** 1953 * The jQuery table object the plugin is bound to. 1954 * @instance 1955 * @type {jQuery} 1956 */ 1957 this.$el = (F.is.jq(element) ? element : $(element)).first(); // ensure one table, one instance 1958 /** 1959 * A loader jQuery instance 1960 * @instance 1961 * @type {jQuery} 1962 */ 1963 this.$loader = $('<div/>', { 'class': 'footable-loader' }).append($('<span/>', {'class': 'fooicon fooicon-loader'})); 1964 /** 1965 * The options for the plugin. This is a merge of user defined options and the default options. 1966 * @instance 1967 * @type {object} 1968 */ 1969 this.o = $.extend(true, {}, F.defaults, options); 1970 /** 1971 * The jQuery data object for the table at initialization. 1972 * @instance 1973 * @type {object} 1974 */ 1975 this.data = this.$el.data() || {}; 1976 /** 1977 * An array of all CSS classes on the table that do not start with "footable". 1978 * @instance 1979 * @protected 1980 * @type {Array.<string>} 1981 */ 1982 this.classes = []; 1983 /** 1984 * All components for this instance of the plugin. These are executed in the order they appear in the array for the initialize phase and in reverse order for the destroy phase of the plugin. 1985 * @instance 1986 * @protected 1987 * @type {object} 1988 * @prop {Array.<FooTable.Component>} internal - The internal components for the plugin. These are executed either before all other components in the initialize phase or after them in the destroy phase of the plugin. 1989 * @prop {Array.<FooTable.Component>} core - The core components for the plugin. These are executed either after the internal components in the initialize phase or before them in the destroy phase of the plugin. 1990 * @prop {Array.<FooTable.Component>} custom - The custom components for the plugin. These are executed either after the core components in the initialize phase or before them in the destroy phase of the plugin. 1991 */ 1992 this.components = F.components.load((F.is.hash(this.data.components) ? this.data.components : this.o.components), this); 1993 /** 1994 * The breakpoints component for this instance of the plugin. 1995 * @instance 1996 * @type {FooTable.Breakpoints} 1997 */ 1998 this.breakpoints = this.use(FooTable.Breakpoints); 1999 /** 2000 * The columns component for this instance of the plugin. 2001 * @instance 2002 * @type {FooTable.Columns} 2003 */ 2004 this.columns = this.use(FooTable.Columns); 2005 /** 2006 * The rows component for this instance of the plugin. 2007 * @instance 2008 * @type {FooTable.Rows} 2009 */ 2010 this.rows = this.use(FooTable.Rows); 2011 2012 //END MEMBERS 2013 this._construct(ready); 2014 }, 2015 /** 2016 * Once all properties are set this performs the actual initialization of the plugin calling the {@link FooTable.Table#_preinit} and 2017 * {@link FooTable.Table#_init} methods as well as raising the {@link FooTable.Table#"ready.ft.table"} event. 2018 * @this FooTable.Table 2019 * @instance 2020 * @param {function} [ready] - A callback function to execute once the plugin is initialized. 2021 * @private 2022 * @returns {jQuery.Promise} 2023 * @fires FooTable.Table#"ready.ft.table" 2024 */ 2025 _construct: function(ready){ 2026 var self = this; 2027 return this._preinit().then(function(){ 2028 return self._init().then(function(){ 2029 /** 2030 * The ready.ft.table event is raised after the plugin has been initialized and the table drawn. 2031 * Calling preventDefault on this event will stop the ready callback being executed. 2032 * @event FooTable.Table#"ready.ft.table" 2033 * @param {jQuery.Event} e - The jQuery.Event object for the event. 2034 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 2035 */ 2036 return self.raise('ready.ft.table').then(function(){ 2037 if (F.is.fn(ready)) ready.call(self, self); 2038 }); 2039 }); 2040 }).always(function(arg){ 2041 self.$el.show(); 2042 if (F.is.error(arg)){ 2043 console.error('FooTable: unhandled error thrown during initialization.', arg); 2044 } 2045 }); 2046 }, 2047 /** 2048 * The preinit method is called prior to the plugins actual initialization and provides itself and it's components an opportunity to parse any additional option values. 2049 * @instance 2050 * @private 2051 * @returns {jQuery.Promise} 2052 * @fires FooTable.Table#"preinit.ft.table" 2053 */ 2054 _preinit: function(){ 2055 var self = this; 2056 /** 2057 * The preinit.ft.table event is raised before any components. 2058 * Calling preventDefault on this event will disable the entire plugin. 2059 * @event FooTable.Table#"preinit.ft.table" 2060 * @param {jQuery.Event} e - The jQuery.Event object for the event. 2061 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 2062 * @param {object} data - The jQuery data object from the root table element. 2063 */ 2064 return this.raise('preinit.ft.table', [self.data]).then(function(){ 2065 var classes = (self.$el.attr('class') || '').match(/\S+/g) || []; 2066 2067 self.o.ajax = F.checkFnValue(self, self.data.ajax, self.o.ajax); 2068 self.o.stopPropagation = F.is.boolean(self.data.stopPropagation) 2069 ? self.data.stopPropagation 2070 : self.o.stopPropagation; 2071 2072 for (var i = 0, len = classes.length; i < len; i++){
2073 if (!F.str.startsWith(classes[i], 'footable')) self.classes.push(classes[i]); 2074 } 2075 2076 self.$el.hide().after(self.$loader); 2077 return self.execute(false, false, 'preinit', self.data); 2078 }); 2079 }, 2080 /** 2081 * Initializes this instance of the plugin and calls the callback function if one is supplied once complete. 2082 * @this FooTable.Table 2083 * @instance 2084 * @private 2085 * @return {jQuery.Promise} 2086 * @fires FooTable.Table#"init.ft.table" 2087 */ 2088 _init: function(){ 2089 var self = this; 2090 /** 2091 * The init.ft.table event is raised before any components are initialized. 2092 * Calling preventDefault on this event will disable the entire plugin. 2093 * @event FooTable.Table#"init.ft.table" 2094 * @param {jQuery.Event} e - The jQuery.Event object for the event. 2095 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 2096 */ 2097 return self.raise('init.ft.table').then(function(){ 2098 var $thead = self.$el.children('thead'), 2099 $tbody = self.$el.children('tbody'), 2100 $tfoot = self.$el.children('tfoot'); 2101 self.$el.addClass('footable footable-' + self.id); 2102 if (F.is.hash(self.o.on)) self.$el.on(self.o.on); 2103 if ($tfoot.length == 0) self.$el.append($tfoot = $('<tfoot/>')); 2104 if ($tbody.length == 0) self.$el.append('<tbody/>'); 2105 if ($thead.length == 0) self.$el.prepend($thead = $('<thead/>')); 2106 return self.execute(false, true, 'init').then(function(){ 2107 self.$el.data('__FooTable__', self); 2108 if ($tfoot.children('tr').length == 0) $tfoot.remove(); 2109 if ($thead.children('tr').length == 0) $thead.remove(); 2110 2111 /** 2112 * The postinit.ft.table event is raised after any components are initialized but before the table is 2113 * drawn for the first time. 2114 * Calling preventDefault on this event will disable the initial drawing of the table. 2115 * @event FooTable.Table#"postinit.ft.table" 2116 * @param {jQuery.Event} e - The jQuery.Event object for the event. 2117 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 2118 */ 2119 return self.raise('postinit.ft.table').then(function(){ 2120 return self.draw(); 2121 }).always(function(){ 2122 $(window).off('resize.ft'+self.id, self._onWindowResize) 2123 .on('resize.ft'+self.id, { self: self }, self._onWindowResize); 2124 self.initialized = true; 2125 }); 2126 }); 2127 }); 2128 }, 2129 /** 2130 * Destroys this plugin removing it from the table. 2131 * @this FooTable.Table 2132 * @instance 2133 * @fires FooTable.Table#"destroy.ft.table" 2134 */ 2135 destroy: function () { 2136 var self = this; 2137 /** 2138 * The destroy.ft.table event is called before all core components. 2139 * Calling preventDefault on this event will prevent the entire plugin from being destroyed. 2140 * @event FooTable.Table#"destroy.ft.table" 2141 * @param {jQuery.Event} e - The jQuery.Event object for the event. 2142 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 2143 */ 2144 return self.raise('destroy.ft.table').then(function(){ 2145 return self.execute(true, true, 'destroy').then(function () { 2146 self.$el.removeData('__FooTable__').removeClass('footable-' + self.id); 2147 if (F.is.hash(self.o.on)) self.$el.off(self.o.on); 2148 $(window).off('resize.ft'+self.id, self._onWindowResize); 2149 self.initialized = false; 2150 F.instances[self.id] = null; 2151 }); 2152 }).fail(function(err){ 2153 if (F.is.error(err)){ 2154 console.error('FooTable: unhandled error thrown while destroying the plugin.', err); 2155 } 2156 }); 2157 }, 2158 /** 2159 * Raises an event on this instance supplying the args array as additional parameters to the handlers. 2160 * @this FooTable.Table 2161 * @instance 2162 * @param {string} eventName - The name of the event to raise, this can include namespaces. 2163 * @param {Array} [args] - An array containing additional parameters to be passed to any boun
2163d handlers. 2164 * @returns {jQuery.Event} 2165 */ 2166 raise: function(eventName, args){ 2167 var self = this, 2168 debug = F.__debug__ && (F.is.emptyArray(F.__debug_options__.events) || F.arr.any(F.__debug_options__.events, function(name){ return F.str.contains(eventName, name); })); 2169 args = args || []; 2170 args.unshift(this); 2171 return $.Deferred(function(d){ 2172 var evt = $.Event(eventName); 2173 if (self.o.stopPropagation == true){ 2174 self.$el.one(eventName, function (e) {e.stopPropagation();}); 2175 } 2176 if (debug) console.log('FooTable:'+eventName+': ', args); 2177 self.$el.trigger(evt, args); 2178 if (evt.isDefaultPrevented()){ 2179 if (debug) console.log('FooTable: default prevented for the "'+eventName+'" event.'); 2180 d.reject(evt); 2181 } else d.resolve(evt); 2182 }); 2183 }, 2184 /** 2185 * Attempts to retrieve the instance of the supplied component type for this instance. 2186 * @this FooTable.Table 2187 * @instance 2188 * @param {object} type - The content type to retrieve for this instance. 2189 * @returns {(*|null)} 2190 */ 2191 use: function(type){ 2192 for (var i = 0, len = this.components.length; i < len; i++){ 2193 if (this.components[i] instanceof type) return this.components[i]; 2194 } 2195 return null; 2196 }, 2197 /** 2198 * Performs the drawing of the table. 2199 * @this FooTable.Table 2200 * @instance 2201 * @protected 2202 * @returns {jQuery.Promise} 2203 * @fires FooTable.Table#"predraw.ft.table" 2204 * @fires FooTable.Table#"draw.ft.table" 2205 * @fires FooTable.Table#"postdraw.ft.table" 2206 */ 2207 draw: function () { 2208 var self = this; 2209 2210 // Clone the current table and insert it into the original's place 2211 var $elCopy = self.$el.clone().insertBefore(self.$el); 2212 2213 // Detach `self.$el` from the DOM, retaining its event handlers 2214 self.$el.detach(); 2215 2216 // when drawing the order that the components are executed is important so chain the methods but use promises to retain async safety. 2217 return self.execute(false, true, 'predraw').then(function(){ 2218 /** 2219 * The predraw.ft.table event is raised after all core components and add-ons have executed there predraw functions but before they execute there draw functions. 2220 * @event FooTable.Table#"predraw.ft.table" 2221 * @param {jQuery.Event} e - The jQuery.Event object for the event. 2222 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 2223 */ 2224 return self.raise('predraw.ft.table').then(function(){ 2225 return self.execute(false, true, 'draw').then(function(){ 2226 /** 2227 * The draw.ft.table event is raised after all core components and add-ons have executed there draw functions. 2228 * @event FooTable.Table#"draw.ft.table" 2229 * @param {jQuery.Event} e - The jQuery.Event object for the event. 2230 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 2231 */ 2232 return self.raise('draw.ft.table').then(function(){ 2233 return self.execute(false, true, 'postdraw').then(function(){ 2234 /** 2235 * The postdraw.ft.table event is raised after all core components and add-ons have executed there postdraw functions. 2236 * @event FooTable.Table#"postdraw.ft.table" 2237 * @param {jQuery.Event} e - The jQuery.Event object for the event. 2238 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 2239 */ 2240 return self.raise('postdraw.ft.table'); 2241 }); 2242 }); 2243 }); 2244 }); 2245 }).fail(function(err){ 2246 if (F.is.error(err)){ 2247 console.error('FooTable: unhandled error thrown during a draw operation.', err); 2248 } 2249 }).always(function(){ 2250 // Replace the copy that we added above with the modified `self.$el` 2251 $elCopy.replaceWith(self.$el); 2252 self.$loader.remove(); 2253 }); 2254 }, 2255 /** 2256 * Executes the specified method with the optional number of parameters on all components and waits for the promise from each to be resolved before executing the next. 2257 * @this FooTable.Table 2258 * @instance 2259 * @protected 2260 * @param {boolean} reverse - Whether or not to execute the component methods in the reverse order to what they were registered in. 2261 * @param {boolean} enabled - Whether or not to execute the method on enabled components only. 2262 * @param {string} methodName - The name of the method to execute. 2263 * @param {*} [param1] - The first parameter for the method. 2264 * @param {...*} [paramN] - Any number of additional parameters for the method. 2265 * @returns {jQuery.Promise} 2266 */ 2267 execute: function(reverse, enabled, methodName, param1, paramN){ 2268 var self = this, args = Array.prototype.slice.call(arguments); 2269 reverse = args.shift(); 2270 enabled = args.shift(); 2271 var components = enabled ? F.arr.get(self.components, function(c){ return c.enabled; }) : self.components.slice(0); 2272 args.unshift(reverse ? components.reverse() : components); 2273 return self._execute.apply(self, args); 2274 }, 2275 /** 2276 * Executes the specified method with the optional number of parameters on all supplied components waiting for the result of each before executing the next. 2277 * @this FooTable.Table 2278 * @instance 2279 * @private 2280 * @param {Array.<FooTable.Component>} components - The components to call the method on. 2281 * @param {string} methodName - The name of the method to execute 2282 * @param {*} [param1] - The first parameter for the method. 2283 * @param {...*} [paramN] - Any additional parameters for the method. 2284 * @returns {jQuery.Promise} 2285 */ 2286 _execute: function(components, methodName, param1, paramN){ 2287 if (!components || !components.length) return $.when(); 2288 var self = this, args = Array.prototype.slice.call(arguments), 2289 component; 2290 components = args.shift(); 2291 methodName = args.shift(); 2292 component = components.shift(); 2293 2294 if (!F.is.fn(component[methodName])) 2295 return self._execute.apply(self, [components, methodName].concat(args)); 2296 2297 return $.Deferred(function(d){
2298 try { 2299 var result = component[methodName].apply(component, args); 2300 if (F.is.promise(result)){ 2301 return result.then(d.resolve, d.reject); 2302 } else { 2303 d.resolve(result); 2304 } 2305 } catch (err) { 2306 d.reject(err); 2307 } 2308 }).then(function(){ 2309 return self._execute.apply(self, [components, methodName].concat(args)); 2310 }); 2311 }, 2312 /** 2313 * Listens to the window resize event and performs a check to see if the breakpoint has changed. 2314 * @this window 2315 * @instance 2316 * @private 2317 * @fires FooTable.Table#"resize.ft.table" 2318 */ 2319 _onWindowResize: function (e) { 2320 var self = e.data.self; 2321 if (self._resizeTimeout != null) { clearTimeout(self._resizeTimeout); } 2322 self._resizeTimeout = setTimeout(function () { 2323 self._resizeTimeout = null; 2324 /** 2325 * The resize event is raised a short time after window resize operations cease. 2326 * @event FooTable.Table#"resize.ft.table" 2327 * @param {jQuery.Event} e - The jQuery.Event object for the event. 2328 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 2329 */ 2330 self.raise('resize.ft.table').then(function(){ 2331 self.breakpoints.check(); 2332 }); 2333 }, 300); 2334 } 2335 }); 2336 2337})(jQuery, FooTable); 2338(function($, F){ 2339 2340 F.ArrayColumn = F.Column.extend(/** @lends FooTable.ArrayColumn */{ 2341 /** 2342 * @summary A column to handle Array values. 2343 * @constructs 2344 * @extends FooTable.Column 2345 * @param {FooTable.Table} instance - The parent {@link FooTable.Table} this column belongs to. 2346 * @param {object} definition - An object containing all the properties to set for the column. 2347 */ 2348 construct: function(instance, definition) { 2349 this._super(instance, definition, 'array'); 2350 }, 2351 /** 2352 * @summary Parses the supplied value or element to retrieve a column value. 2353 * @description This is supplied either the cell value or jQuery object to parse. This method will return either the Array containing the values or null. 2354 * @instance 2355 * @protected 2356 * @param {(*|jQuery)} valueOrElement - The value or jQuery cell object. 2357 * @returns {(array|null)} 2358 */ 2359 parser: function(valueOrElement){ 2360 if (F.is.element(valueOrElement) || F.is.jq(valueOrElement)){ // use jQuery to get the value 2361 var $el = $(valueOrElement), data = $el.data('value'); // .data() will automatically convert a JSON string to an array 2362 if (F.is.array(data)) return data; 2363 data = $el.html(); 2364 try { 2365 data = JSON.parse(data); 2366 } catch(err) { 2367 data = null; 2368 } 2369 return F.is.array(data) ? data : null; // if we have an array return it 2370 } 2371 if (F.is.array(valueOrElement)) return valueOrElement; // if we have an array return it 2372 return null; // otherwise we have no value so return null 2373 }, 2374 /** 2375 * @summary Formats the column value and creates the HTML seen within a cell. 2376 * @description This is supplied the value retrieved from the {@link FooTable.ArrayColumn#parser} function and must return a string, HTMLElement or jQuery object. 2377 * The return value from this function is what is displayed in the cell in the table. 2378 * @instance 2379 * @protected 2380 * @param {?Array} value - The value to format. 2381 * @param {object} options - The current plugin options. 2382 * @param {object} rowData - An object containing the current row data. 2383 * @returns {(string|HTMLElement|jQuery)} 2384 */ 2385 formatter: function(value, options, rowData){ 2386 return F.is.array(value) ? JSON.stringify(value) : ''; 2387 } 2388 }); 2389 2390 F.columns.register('array', F.ArrayColumn); 2391 2392})(jQuery, FooTable); 2393(function($, F){ 2394 2395 if (F.is.undef(window.moment)){ 2396 // The DateColumn requires moment.js to parse and format date values. Goto http://momentjs.com/ to get it. 2397 return; 2398 } 2399 2400 F.DateColumn = F.Column.extend(/** @lends FooTable.DateColumn */{ 2401 /** 2402 * The date column class is used to handle date values. This column is dependent on [moment.js]{@link http://momentjs.com/} to provide date parsing and formatting functionality. 2403 * @constructs 2404 * @extends FooTable.Column 2405 * @param {FooTable.Table} instance - The parent {@link FooTable.Table} this column belongs to. 2406 * @param {object}
2406 definition - An object containing all the properties to set for the column. 2407 * @returns {FooTable.DateColumn} 2408 */ 2409 construct: function(instance, definition){ 2410 this._super(instance, definition, 'date'); 2411 /** 2412 * The format string to use when parsing and formatting dates. 2413 * @instance 2414 * @type {string} 2415 */ 2416 this.formatString = F.is.string(definition.formatString) ? definition.formatString : 'MM-DD-YYYY'; 2417 }, 2418 /** 2419 * This is supplied either the cell value or jQuery object to parse. Any value can be returned from this method and will be provided to the {@link FooTable.DateColumn#format} function 2420 * to generate the cell contents. 2421 * @instance 2422 * @protected 2423 * @param {(*|jQuery)} valueOrElement - The value or jQuery cell object. 2424 * @returns {(moment|null)} 2425 * @this FooTable.DateColumn 2426 */ 2427 parser: function(valueOrElement){ 2428 if (F.is.element(valueOrElement) || F.is.jq(valueOrElement)){ 2429 var data = $(valueOrElement).data('value'); 2430 valueOrElement = F.is.defined(data) ? data : $(valueOrElement).text(); 2431 if (F.is.string(valueOrElement)) valueOrElement = isNaN(valueOrElement) ? valueOrElement : +valueOrElement; 2432 } 2433 if (F.is.date(valueOrElement)) return moment(valueOrElement); 2434 if (F.is.object(valueOrElement) && F.is.boolean(valueOrElement._isAMomentObject)) return valueOrElement; 2435 if (F.is.string(valueOrElement)){ 2436 // if it looks like a number convert it and do nothing else otherwise create a new moment using the string value and formatString 2437 if (isNaN(valueOrElement)){ 2438 return moment(valueOrElement, this.formatString); 2439 } else { 2440 valueOrElement = +valueOrElement; 2441 } 2442 } 2443 if (F.is.number(valueOrElement)){ 2444 return moment(valueOrElement); 2445 } 2446 return null; 2447 }, 2448 /** 2449 * This is supplied the value retrieved from the {@link FooTable.DateColumn#parser} function and must return a string, HTMLElement or jQuery object. 2450 * The return value from this function is what is displayed in the cell in the table. 2451 * @instance 2452 * @protected 2453 * @param {*} value - The value to format. 2454 * @param {object} options - The current plugin options. 2455 * @param {object} rowData - An object containing the current row data. 2456 * @returns {(string|HTMLElement|jQuery)} 2457 * @this FooTable.DateColumn 2458 */ 2459 formatter: function(value, options, rowData){ 2460 return F.is.object(value) && F.is.boolean(value._isAMomentObject) && value.isValid() ? value.format(this.formatString) : ''; 2461 }, 2462 /** 2463 * This is supplied either the cell value or jQuery object to parse. A string value must be returned from this method and will be used during filtering operations. 2464 * @param {(*|jQuery)} valueOrElement - The value or jQuery cell object. 2465 * @returns {string} 2466 * @this FooTable.DateColumn 2467 */ 2468 filterValue: function(valueOrElement){ 2469 // if we have an element or a jQuery object use jQuery to get the value 2470 if (F.is.element(valueOrElement) || F.is.jq(valueOrElement)) valueOrElement = $(valueOrElement).data('filterValue') || $(valueOrElement).text(); 2471 // if options are supplied with the value 2472 if (F.is.hash(valueOrElement) && F.is.hash(valueOrElement.options)){ 2473 if (F.is.string(valueOrElement.options.filterValue)) valueOrElement = valueOrElement.options.filterValue; 2474 if (F.is.defined(valueOrElement.value)) valueOrElement = valueOrElement.value; 2475 } 2476 // if the value is a moment object just return the formatted value 2477 if (F.is.object(valueOrElement) && F.is.boolean(valueOrElement._isAMomentObject)) return valueOrElement.format(this.formatString); 2478 // if its a string 2479 if (F.is.string(valueOrElement)){ 2480 // if its not a number return it 2481 if (isNaN(valueOrElement)){ 2482 return valueOrElement; 2483 } else { // otherwise convert it and carry on 2484 valueOrElement = +valueOrElement; 2485 } 2486 } 2487 // if the value is a number or date convert to a moment object and return the formatted result. 2488 if (F.is.number(valueOrElement) || F.is.date(valueOrElement)){ 2489 return moment(valueOrElement).format(this.formatString); 2490 } 2491 // try use the native toString of the value if its not undefined or null 2492 if (F.is.defined(valueOrElement) && valueOrElement != null) return valueOrElement+''; 2493 return ''; // otherwise we have no value so return an empty string 2494 } 2495 }); 2496 2497 F.columns.register('date', F.DateColumn); 2498 2499})(jQuery, FooTable); 2500 2501(function($, F){ 2502 2503 F.HTMLColumn = F.Column.extend(/** @lends FooTable.HTMLColumn */{ 2504 /** 2505 * The HTML column class is used to handle any raw HTML columns. 2506 * @constructs 2507 * @extends FooTable.Column 2508 * @param {FooTable.Table} instance - The parent {@link FooTable.Table} this column belongs to. 2509 * @param {object}
2509 definition - An object containing all the properties to set for the column. 2510 * @returns {FooTable.HTMLColumn} 2511 */ 2512 construct: function(instance, definition){ 2513 this._super(instance, definition, 'html'); 2514 }, 2515 /** 2516 * This is supplied either the cell value or jQuery object to parse. Any value can be returned from this method and will be provided to the {@link FooTable.HTMLColumn#format} function 2517 * to generate the cell contents. 2518 * @instance 2519 * @protected 2520 * @param {(*|jQuery)} valueOrElement - The value or jQuery cell object. 2521 * @returns {(jQuery|null)} 2522 * @this FooTable.HTMLColumn 2523 */ 2524 parser: function(valueOrElement){ 2525 if (F.is.string(valueOrElement)) valueOrElement = $($.trim(valueOrElement)); 2526 if (F.is.element(valueOrElement)) valueOrElement = $(valueOrElement); 2527 if (F.is.jq(valueOrElement)){ 2528 var tagName = valueOrElement.prop('tagName').toLowerCase(); 2529 if (tagName == 'td' || tagName == 'th'){ 2530 var data = valueOrElement.data('value'); 2531 return F.is.defined(data) ? data : valueOrElement.contents(); 2532 } 2533 return valueOrElement; 2534 } 2535 return null; 2536 } 2537 }); 2538 2539 F.columns.register('html', F.HTMLColumn); 2540 2541})(jQuery, FooTable); 2542(function($, F){ 2543 2544 F.NumberColumn = F.Column.extend(/** @lends FooTable.NumberColumn */{ 2545 /** 2546 * The number column class is used to handle simple number columns. 2547 * @constructs 2548 * @extends FooTable.Column 2549 * @param {FooTable.Table} instance - The parent {@link FooTable.Table} this column belongs to. 2550 * @param {object} definition - An object containing all the properties to set for the column. 2551 * @returns {FooTable.NumberColumn} 2552 */ 2553 construct: function(instance, definition){ 2554 this._super(instance, definition, 'number'); 2555 this.decimalSeparator = F.is.string(definition.decimalSeparator) ? definition.decimalSeparator : '.'; 2556 this.thousandSeparator = F.is.string(definition.thousandSeparator) ? definition.thousandSeparator : ','; 2557 this.decimalSeparatorRegex = new RegExp(F.str.escapeRegExp(this.decimalSeparator), 'g'); 2558 this.thousandSeparatorRegex = new RegExp(F.str.escapeRegExp(this.thousandSeparator), 'g'); 2559 this.cleanRegex = new RegExp('[^\-0-9' + F.str.escapeRegExp(this.decimalSeparator) + ']', 'g'); 2560 }, 2561 /** 2562 * This is supplied either the cell value or jQuery object to parse. Any value can be returned from this method and will be provided to the {@link FooTable.Column#formatter} function 2563 * to generate the cell contents. 2564 * @instance 2565 * @protected 2566 * @param {(*|jQuery)} valueOrElement - The value or jQuery cell object. 2567 * @returns {(number|null)} 2568 * @this FooTable.NumberColumn 2569 */ 2570 parser: function(valueOrElement){ 2571 if (F.is.element(valueOrElement) || F.is.jq(valueOrElement)){ 2572 var data = $(valueOrElement).data('value'); 2573 valueOrElement = F.is.defined(data) ? data : $(valueOrElement).text().replace(this.cleanRegex, ''); 2574 } 2575 if (F.is.string(valueOrElement)){ 2576 valueOrElement = valueOrElement.replace(this.thousandSeparatorRegex, '').replace(this.decimalSeparatorRegex, '.'); 2577 valueOrElement = parseFloat(valueOrElement); 2578 } 2579 if (F.is.number(valueOrElement)) return valueOrElement; 2580 return null; 2581 }, 2582 /** 2583 * This is supplied the value retrieved from the {@link FooTable.NumberColumn#parse} function and must return a string, HTMLElement or jQuery object. 2584 * The return value from this function is what is displayed in the cell in the table. 2585 * @instance 2586 * @protected 2587 * @param {number} value - The value to format. 2588 * @param {object} options - The current plugin options. 2589 * @param {object} rowData - An object containing the current row data. 2590 * @returns {(string|HTMLElement|jQuery)} 2591 * @this FooTable.NumberColumn 2592 */ 2593 formatter: function(value, options, rowData){ 2594 if (value == null) return ''; 2595 var s = (value + '').split('.'); 2596 if (s.length == 2 && s[0].length > 3) { 2597 s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, this.thousandSeparator); 2598 } 2599 return s.join(this.decimalSeparator); 2600 } 2601 }); 2602 2603 F.columns.register('number', F.NumberColumn); 2604 2605})(jQuery, FooTable); 2606(function($, F){ 2607 2608 F.ObjectColumn = F.Column.extend(/** @lends FooTable.ObjectColumn */{ 2609 /** 2610 * @summary A column to handle Object values. 2611 * @constructs 2612 * @extends FooTable.Column 2613 * @param {FooTable.Table} instance - The parent {@link FooTable.Table} this column belongs to. 2614 * @param {object}
2614 definition - An object containing all the properties to set for the column. 2615 */ 2616 construct: function(instance, definition) { 2617 this._super(instance, definition, 'object'); 2618 }, 2619 /** 2620 * @summary Parses the supplied value or element to retrieve a column value. 2621 * @description This is supplied either the cell value or jQuery object to parse. This method will return either the Object containing the values or null. 2622 * @instance 2623 * @protected 2624 * @param {(*|jQuery)} valueOrElement - The value or jQuery cell object. 2625 * @returns {(object|null)} 2626 */ 2627 parser: function(valueOrElement){ 2628 if (F.is.element(valueOrElement) || F.is.jq(valueOrElement)){ // use jQuery to get the value 2629 var $el = $(valueOrElement), data = $el.data('value'); // .data() will automatically convert a JSON string to an object 2630 if (F.is.object(data)) return data; 2631 data = $el.html(); 2632 try { 2633 data = JSON.parse(data); 2634 } catch(err) { 2635 data = null; 2636 } 2637 return F.is.object(data) ? data : null; // if we have an object return it 2638 } 2639 if (F.is.object(valueOrElement)) return valueOrElement; // if we have an object return it 2640 return null; // otherwise we have no value so return null 2641 }, 2642 /** 2643 * @summary Formats the column value and creates the HTML seen within a cell. 2644 * @description This is supplied the value retrieved from the {@link FooTable.ObjectColumn#parser} function and must return a string, HTMLElement or jQuery object. 2645 * The return value from this function is what is displayed in the cell in the table. 2646 * @instance 2647 * @protected 2648 * @param {*} value - The value to format. 2649 * @param {object} options - The current plugin options. 2650 * @param {object} rowData - An object containing the current row data. 2651 * @returns {(string|HTMLElement|jQuery)} 2652 */ 2653 formatter: function(value, options, rowData){ 2654 return F.is.object(value) ? JSON.stringify(value) : ''; 2655 } 2656 }); 2657 2658 F.columns.register('object', F.ObjectColumn); 2659 2660})(jQuery, FooTable); 2661(function($, F){ 2662 2663 F.Breakpoint = F.Class.extend(/** @lends FooTable.Breakpoint */{ 2664 /** 2665 * The breakpoint class containing the name and maximum width for the breakpoint. 2666 * @constructs 2667 * @extends FooTable.Class 2668 * @param {string} name - The name of the breakpoint. Must contain no spaces or special characters. 2669 * @param {number} width - The width of the breakpoint in pixels. 2670 * @returns {FooTable.Breakpoint} 2671 */ 2672 construct: function(name, width){ 2673 /** 2674 * The name of the breakpoint. 2675 * @type {string} 2676 */ 2677 this.name = name; 2678 /** 2679 * The maximum width of the breakpoint in pixels. 2680 * @type {number} 2681 */ 2682 this.width = width; 2683 } 2684 }); 2685 2686})(jQuery, FooTable); 2687(function($, F){ 2688 F.Breakpoints = F.Component.extend(/** @lends FooTable.Breakpoints */{ 2689 /** 2690 * Contains the logic to calculate and apply breakpoints for the plugin. 2691 * @constructs 2692 * @extends FooTable.Component 2693 * @param {FooTable.Table} table - The parent {@link FooTable.Table} this component belongs to. 2694 * @returns {FooTable.Breakpoints} 2695 */ 2696 construct: function(table){ 2697 // call the base class constructor 2698 this._super(table, true); 2699 2700 /* PROTECTED */ 2701 /** 2702 * This provides a shortcut to the {@link FooTable.Table#options} object. 2703 * @protected 2704 * @type {FooTable.Table#options} 2705 */ 2706 this.o = table.o; 2707 2708 /* PUBLIC */ 2709 /** 2710 * The current breakpoint. 2711 * @type {FooTable.Breakpoint} 2712 */ 2713 this.current = null; 2714 /** 2715 * An array of {@link FooTable.Breakpoint} objects created from parsing the options. 2716 * @type {Array.<FooTable.Breakpoint>} 2717 */ 2718 this.array = []; 2719 /** 2720 * Whether or not breakpoints cascade. When set to true all breakpoints larger than the current will be hidden along with it. 2721 * @type {boolean} 2722 */ 2723 this.cascade = this.o.cascade; 2724 /** 2725 * Whether or not to calculate breakpoints on the width of the parent element rather than the viewport. 2726 * @type {boolean} 2727 */ 2728 this.useParentWidth = this.o.useParentWidth; 2729 /** 2730 * This value is updated each time the current breakpoint changes and contains a space delimited string of the names of the current breakpoint and all those smaller than it. 2731 * @type {string} 2732 */ 2733 this.hidden = null; 2734 2735 /* PRIVATE */ 2736 /** 2737 * This value is set once when the {@link FooTable.Breakpoints#array} is generated and contains a space delimited string of all the breakpoint class names. 2738 * @type {string} 2739 * @private 2740 */ 2741 this._classNames = ''; 2742 2743 // check if a function was supplied to override the default getWidth 2744 this.getWidth = F.checkFnValue(this, this.o.getWidth, this.getWidth); 2745 }, 2746 2747 /* PROTECTED */ 2748 /** 2749 * Checks the supplied data and options for the breakpoints component. 2750 * @instance 2751 * @protected 2752 * @param {object} data - The jQuery data object from the parent table. 2753 * @fires FooTable.Breakpoints#"preinit.ft.breakpoints" 2754 */ 2755 preinit: function(data){ 2756 var self = this; 2757 /** 2758 * The preinit.ft.breakpoints event is raised before any UI is created and provides the tables jQuery data object for additional options parsing. 2759 * Calling preventDefault on this event will disable the entire plugin. 2760 * @event FooTable.Breakpoints#"preinit.ft.breakpoints" 2761 * @param {jQuery.Event} e - The jQuery.Event object for the event. 2762 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 2763 * @param {object} data - The jQuery data object of the table raising the event. 2764 */ 2765 return this.ft.raise('preinit.ft.breakpoints', [data]).then(function(){ 2766 self.cascade = F.is.boolean(data.cascade) ? data.cascade : self.cascade; 2767 self.o.breakpoints = F.is.hash(data.breakpoints) ? data.breakpoints : self.o.breakpoints; 2768 self.getWidth = F.checkFnValue(self, data.getWidth, self.getWidth); 2769 if (self.o.breakpoints == null) self.o.breakpoints = { "xs": 480, "sm": 768, "md": 992, "lg": 1200 }; 2770 // Create a nice friendly array to work with out of the breakpoints object. 2771 for (var name in self.o.breakpoints) { 2772 if (!self.o.breakpoints.hasOwnProperty(name)) continue; 2773 self.array.push(new F.Breakpoint(name, self.o.breakpoints[name])); 2774 self._classNames += 'breakpoint-' + name + ' '; 2775 } 2776 // Sort the breakpoints so the largest is checked first 2777 self.array.sort(function (a, b) { 2778 return b.width - a.width; 2779 }); 2780 }); 2781 }, 2782 /** 2783 * Initializes the class parsing the options into a sorted array of {@link FooTable.Breakpoint} objects. 2784 * @instance 2785 * @protected 2786 * @fires FooTable.Breakpoints#"init.ft.breakpoints" 2787 */ 2788 init: function(){ 2789 var self = this; 2790 /** 2791 * The init.ft.breakpoints event is raised before any UI is generated. 2792 * Calling preventDefault on this event will disable the entire plugin. 2793 * @event FooTable.Breakpoints#"init.ft.breakpoints" 2794 * @param {jQuery.Event} e - The jQuery.Event object for the event. 2795 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 2796 */ 2797 return this.ft.raise('init.ft.breakpoints').then(function(){ 2798 self.current = self.get(); 2799 }); 2800 }, 2801 /** 2802 * Whenever the table is drawn this ensures the correct breakpoint class is applied to the table. 2803 * @instance 2804 * @protected 2805 */ 2806 draw: function(){ 2807 this.ft.$el.removeClass(this._classNames).addClass('breakpoint-' + this.current.name); 2808 }, 2809 2810 /* PUBLIC */ 2811 /** 2812 * Calculates the current breakpoint from the {@link FooTable.Breakpoints#array} and sets the {@link FooTable.Breakpoints#current} property. 2813 * @instance 2814 * @returns {FooTable.Breakpoint} 2815 */ 2816 calculate: function(){ 2817 var self = this, current = null, hidden = [], breakpoint, prev = null, width = self.getWidth(); 2818 for (var i = 0, len = self.array.length; i < len; i++) { 2819 breakpoint = self.array[i]; 2820 // if the width is smaller than the smallest breakpoint set the smallest as the current. 2821 // if the width is larger than the largest breakpoint set the largest as the current. 2822 // otherwise if the width is somewhere in between check all breakpoints testing if the width 2823 // is greater than the current but smaller than the previous. 2824 if ((!current && i == len -1) 2825 || (width >= breakpoint.width && (prev instanceof F.Breakpoint ? width < prev.width : true))) { 2826 current = breakpoint; 2827 } 2828 if (!current) hidden.push(breakpoint.name); 2829 prev = breakpoint; 2830 } 2831 hidden.push(current.name); 2832 self.hidden = hidden.join(' '); 2833 return current; 2834 }, 2835 /** 2836 * Supplied a columns breakpoints this returns a boolean value indicating whether or not the column is visible. 2837 * @param {string} breakpoints - A space separated string of breakpoint names. 2838 * @returns {boolean} 2839 */ 2840 visible: function(breakpoints){ 2841 if (F.is.emptyString(breakpoints)) return true; 2842 if (breakpoints === 'all') return false;
2843 var parts = breakpoints.split(' '), i = 0, len = parts.length; 2844 for (; i < len; i++){ 2845 if (this.cascade ? F.str.containsWord(this.hidden, parts[i]) : parts[i] == this.current.name) return false; 2846 } 2847 return true; 2848 }, 2849 /** 2850 * Performs a check between the current breakpoint and the previous breakpoint and performs a redraw if they differ. 2851 * @instance 2852 * @fires FooTable.Breakpoints#"before.ft.breakpoints" 2853 * @fires FooTable.Breakpoints#"after.ft.breakpoints" 2854 */ 2855 check: function(){ 2856 var self = this, bp = self.get(); 2857 if (!(bp instanceof F.Breakpoint) 2858 || bp == self.current) 2859 return; 2860 2861 /** 2862 * The before.ft.breakpoints event is raised if the breakpoint has changed but before the UI is redrawn and is supplied both the current breakpoint 2863 * and the next "new" one that is about to be applied. 2864 * Calling preventDefault on this event will prevent the next breakpoint from being applied. 2865 * @event FooTable.Breakpoints#"before.ft.breakpoints" 2866 * @param {jQuery.Event} e - The jQuery.Event object for the event. 2867 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 2868 * @param {FooTable.Breakpoint} current - The current breakpoint. 2869 * @param {FooTable.Breakpoint} next - The breakpoint that is about to be applied. 2870 */ 2871 self.ft.raise('before.ft.breakpoints', [self.current, bp]).then(function(){ 2872 var previous = self.current; 2873 self.current = bp; 2874 return self.ft.draw().then(function(){ 2875 /** 2876 * The after.ft.breakpoints event is raised after the breakpoint has changed and the UI is redrawn and is supplied both the "new" current breakpoint 2877 * and the previous one that was replaced. 2878 * @event FooTable.Breakpoints#"after.ft.breakpoints" 2879 * @param {jQuery.Event} e - The jQuery.Event object for the event. 2880 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 2881 * @param {FooTable.Breakpoint} current - The current breakpoint. 2882 * @param {FooTable.Breakpoint} previous - The breakpoint that was just replaced. 2883 */ 2884 self.ft.raise('after.ft.breakpoints', [self.current, previous]); 2885 }); 2886 }); 2887 }, 2888 /** 2889 * Attempts to return a {@link FooTable.Breakpoint} instance when passed a {@link FooTable.Breakpoint}, 2890 * the {@link FooTable.Breakpoint#name} string or if nothing is supplied the current breakpoint. 2891 * @instance 2892 * @param {(FooTable.Breakpoint|string|number)} [breakpoint] - The breakpoint to retrieve. 2893 * @returns {FooTable.Breakpoint} 2894 */ 2895 get: function(breakpoint){ 2896 if (F.is.undef(breakpoint)) return this.calculate(); 2897 if (breakpoint instanceof F.Breakpoint) return breakpoint; 2898 if (F.is.string(breakpoint)) return F.arr.first(this.array, function (bp) { return bp.name == breakpoint; }); 2899 if (F.is.number(breakpoint)) return breakpoint >= 0 && breakpoint < this.array.length ? this.array[breakpoint] : null; 2900 return null; 2901 }, 2902 /** 2903 * Gets the width used to determine breakpoints whether it be from the viewport, parent or a custom function. 2904 * @instance 2905 * @returns {number} 2906 */ 2907 getWidth: function(){ 2908 if (F.is.fn(this.o.getWidth)) return this.o.getWidth(this.ft); 2909 if (this.useParentWidth == true) return this.getParentWidth(); 2910 return this.getViewportWidth(); 2911 }, 2912 /** 2913 * Gets the tables direct parents width. 2914 * @instance 2915 * @returns {number} 2916 */ 2917 getParentWidth: function(){ 2918 return this.ft.$el.parent().width(); 2919 }, 2920 /** 2921 * Gets the current viewport width. 2922 * @instance 2923 * @returns {number} 2924 */ 2925 getViewportWidth: function(){ 2926 return Math.max(document.documentElement.clientWidth, window.innerWidth, 0); 2927 } 2928 }); 2929 2930 F.components.register('breakpoints', F.Breakpoints, 1000); 2931 2932})(jQuery, FooTable); 2933(function(F){ 2934 /** 2935 * A space delimited string of breakpoint names that specify when the column will be hidden. You can also specify "all" to make a column permanently display in an expandable detail row. 2936 * @type {string} 2937 * @default null 2938 * @example <caption>The below shows how this value would be set</caption> 2939 * breakpoints: "md" 2940 */ 2941 F.Column.prototype.breakpoints = null; 2942 2943 F.Column.prototype.__breakpoints_define__ = function(definition){ 2944 this.breakpoints = F.is.emptyString(definition.breakpoints) ? null : definition.breakpoints; 2945 }; 2946 2947 F.Column.extend('define', function(definition){ 2948 this._super(definition); 2949 this.__breakpoints_define__(definition); 2950 }); 2951})(FooTable); 2952(function(F){ 2953 /**
2954 * An object containing the breakpoints for the plugin. 2955 * @type {object.<string, number>} 2956 * @default { "xs": 480, "sm": 768, "md": 992, "lg": 1200 } 2957 */ 2958 F.Defaults.prototype.breakpoints = null; 2959 2960 /** 2961 * Whether or not breakpoints cascade. When set to true all breakpoints larger than the current will also be hidden along with it. 2962 * @type {boolean} 2963 * @default false 2964 */ 2965 F.Defaults.prototype.cascade = false; 2966 2967 /** 2968 * Whether or not to calculate breakpoints on the width of the parent element rather than the viewport. 2969 * @type {boolean} 2970 * @default false 2971 */ 2972 F.Defaults.prototype.useParentWidth = false; 2973 2974 /** 2975 * A function used to override the default getWidth function with a custom one. 2976 * @type {function} 2977 * @default null 2978 * @example <caption>The below shows what the default getWidth function would look like.</caption> 2979 * getWidth: function(instance){ 2980 * if (instance.o.useParentWidth == true) return instance.$el.parent().width(); 2981 * return instance.breakpoints.getViewportWidth(); 2982 * } 2983 */ 2984 F.Defaults.prototype.getWidth = null; 2985})(FooTable); 2986(function($, F){ 2987 F.Columns = F.Component.extend(/** @lends FooTable.Columns */{ 2988 /** 2989 * The columns class contains all the logic for handling columns. 2990 * @constructs 2991 * @extends FooTable.Component 2992 * @param {FooTable.Table} table - The parent {@link FooTable.Table} this component belongs to. 2993 * @returns {FooTable.Columns} 2994 */ 2995 construct: function(table){ 2996 // call the base class constructor 2997 this._super(table, true); 2998 2999 /* PROTECTED */ 3000 /** 3001 * This provides a shortcut to the {@link FooTable.Table#options} object. 3002 * @protected 3003 * @type {FooTable.Table#options} 3004 */ 3005 this.o = table.o; 3006 3007 /* PUBLIC */ 3008 /** 3009 * An array of {@link FooTable.Column} objects created from parsing the options and/or DOM. 3010 * @type {Array.<FooTable.Column>} 3011 */ 3012 this.array = []; 3013 /** 3014 * The jQuery header row object. 3015 * @type {jQuery} 3016 */ 3017 this.$header = null; 3018 /** 3019 * Whether or not to display the header row. 3020 * @type {boolean} 3021 */ 3022 this.showHeader = table.o.showHeader; 3023 3024 this._fromHTML = F.is.emptyArray(table.o.columns) && !F.is.promise(table.o.columns); 3025 }, 3026 3027 /* PROTECTED */ 3028 /** 3029 * This parses the columns from either the tables rows or the supplied options. 3030 * @instance 3031 * @protected 3032 * @param {object} data - The tables jQuery data object. 3033 * @returns {jQuery.Promise} 3034 * @this FooTable.Columns 3035 */ 3036 parse: function(data){ 3037 var self = this; 3038 return $.Deferred(function(d){ 3039 function merge(cols1, cols2){ 3040 var merged = []; 3041 // check if either of the arrays is empty as it can save us having to merge them by index. 3042 if (cols1.length == 0 || cols2.length == 0){ 3043 merged = cols1.concat(cols2); 3044 } else { 3045 // at this point we have two arrays of column definitions, we now need to merge them based on there index properties 3046 // first figure out the highest column index provided so we can loop that many times to merge all columns and provide 3047 // defaults where nothing was specified (fill in the gaps in the array as it were). 3048 var highest = 0; 3049 F.arr.each(cols1.concat(cols2), function(c){ 3050 if (c.index > highest) highest = c.index; 3051 }); 3052 highest++; 3053 for (var i = 0, cols1_c, cols2_c; i < highest; i++){ 3054 cols1_c = {}; 3055 F.arr.each(cols1, function(c){ 3056 if (c.index == i){ 3057 cols1_c = c; 3058 return false; 3059 } 3060 }); 3061 cols2_c = {}; 3062 F.arr.each(cols2, function(c){ 3063 if (c.index == i){ 3064 cols2_c = c; 3065 return false; 3066 } 3067 }); 3068 merged.push($.extend(true, {}, cols1_c, cols2_c)); 3069 } 3070 } 3071 return merged; 3072 } 3073 3074 var json = [], html = []; 3075 // get the column options from the content 3076 var $header = self.ft.$el.find('tr.footable-header, thead > tr:last:has([data-breakpoints]), tbody > tr:first:has([data-breakpoints]), thead > tr:last, tbody > tr:first').first(), $cell, cdata; 3077 if ($header.length > 0){ 3078 var virtual = $header.parent().is('tbody') && $header.children().length == $header.children('td').length; 3079 if (!virtual) self.$header = $header.addClass('footable-header'); 3080 $header.children('td,th').each(function(i, cell){ 3081 $cell = $(cell); 3082 cdata = $cell.data(); 3083 cdata.index = i; 3084 cdata.$el = $cell; 3085 cdata.virtual = virtual; 3086 html.push(cdata); 3087 }); 3088 if (virtual) self.showHeader = false;
3089 } 3090 // get the supplied column options 3091 if (F.is.array(self.o.columns) && !F.is.emptyArray(self.o.columns)){ 3092 F.arr.each(self.o.columns, function(c, i){ 3093 c.index = i; 3094 json.push(c); 3095 }); 3096 self.parseFinalize(d, merge(json, html)); 3097 } else if (F.is.promise(self.o.columns)){ 3098 self.o.columns.then(function(cols){ 3099 F.arr.each(cols, function(c, i){ 3100 c.index = i; 3101 json.push(c); 3102 }); 3103 self.parseFinalize(d, merge(json, html)); 3104 }, function(xhr){ 3105 d.reject(Error('Columns ajax request error: ' + xhr.status + ' (' + xhr.statusText + ')')); 3106 }); 3107 } else { 3108 self.parseFinalize(d, merge(json, html)); 3109 } 3110 }); 3111 }, 3112 /** 3113 * Used to finalize the parsing of columns it is supplied the parse deferred object which must be resolved with an array of {@link FooTable.Column} objects 3114 * or rejected with an error. 3115 * @instance 3116 * @protected 3117 * @param {jQuery.Deferred} deferred - The deferred object used for parsing. 3118 * @param {Array.<object>} cols - An array of all merged column definitions. 3119 */ 3120 parseFinalize: function(deferred, cols){ 3121 // we now have a merged array of all column definitions supplied to the plugin, time to make the objects. 3122 var self = this, columns = [], column; 3123 F.arr.each(cols, function(def){ 3124 // if we have a column registered using the definition type then create an instance of that column otherwise just create a default text column. 3125 if (column = F.columns.contains(def.type) ? F.columns.make(def.type, self.ft, def) : new F.Column(self.ft, def)) 3126 columns.push(column); 3127 }); 3128 if (F.is.emptyArray(columns)){ 3129 deferred.reject(Error("No columns supplied.")); 3130 } else { 3131 // make sure to sort by the column index as the merge process may have mixed them up 3132 columns.sort(function(a, b){ return a.index - b.index; }); 3133 deferred.resolve(columns); 3134 } 3135 }, 3136 /** 3137 * The columns preinit method is used to parse and check the column options supplied from both static content and through the constructor. 3138 * @instance 3139 * @protected 3140 * @param {object} data - The jQuery data object from the root table element. 3141 * @this FooTable.Columns 3142 */ 3143 preinit: function(data){ 3144 var self = this; 3145 /** 3146 * The preinit.ft.columns event is raised before any UI is created and provides the tables jQuery data object for additional options parsing. 3147 * Calling preventDefault on this event will disable the entire plugin. 3148 * @event FooTable.Columns#"preinit.ft.columns" 3149 * @param {jQuery.Event} e - The jQuery.Event object for the event. 3150 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 3151 * @param {object} data - The jQuery data object of the table raising the event. 3152 */ 3153 return self.ft.raise('preinit.ft.columns', [data]).then(function(){ 3154 return self.parse(data).then(function(columns){ 3155 self.array = columns; 3156 self.showHeader = F.is.boolean(data.showHeader) ? data.showHeader : self.showHeader; 3157 }); 3158 }); 3159 }, 3160 /** 3161 * Initializes the columns creating the table header if required. 3162 * @instance 3163 * @protected 3164 * @fires FooTable.Columns#"init.ft.columns" 3165 * @this FooTable.Columns 3166 */ 3167 init: function(){ 3168 var self = this; 3169 /** 3170 * The init.ft.columns event is raised after the header row is created/parsed for column data. 3171 * @event FooTable.Columns#"init.ft.columns" 3172 * @param {jQuery.Event} e - The jQuery.Event object for the event. 3173 * @param {FooTable.Table} instance - The instance of the plugin raising the event. 3174 * @param {Array.<FooTable.Column>} columns - The array of {@link FooTable.Column} objects parsed from the options and/or DOM. 3175 */ 3176 return this.ft.raise('init.ft.columns', [ self.array ]).then(function(){ 3177 self.$create(); 3178 }); 3179 }, 3180 /** 3181 * Destroys the columns component removing any UI generated from the table. 3182 * @instance 3183 * @protected 3184 * @fires FooTable.Columns#"destroy.ft.columns" 3185 */ 3186 destroy: function(){ 3187 /** 3188 * The destroy.ft.columns event is raised before its UI is removed. 3189 * Calling preventDefault on this event will prevent the component from being destroyed. 3190 * @event FooTable.Columns#"destroy.ft.columns" 3191 * @param {jQuery.Event} e - The jQuery.Event object for the event. 3192 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 3193 */ 3194 var self = this; 3195 this.ft.raise('destroy.ft.columns').then(function(){ 3196 if (!self._fromHTML) self.$header.remove(); 3197 }); 3198 }, 3199 /** 3200 * The predraw method called from within the {@link FooTable.Table#draw} method. 3201 * @instance 3202 * @protected 3203 * @this FooTable.Columns 3204 */ 3205 predraw: function(){ 3206 var self = this, first = true; 3207 self.visibleColspan = 0; 3208 self.firstVisibleIndex = 0; 3209 self.lastVisibleIndex = 0; 3210 self.hasHidden = false;
3211 F.arr.each(self.array, function(col){ 3212 col.hidden = !self.ft.breakpoints.visible(col.breakpoints); 3213 if (!col.hidden && col.visible){ 3214 if (first){ 3215 self.firstVisibleIndex = col.index; 3216 first = false; 3217 } 3218 self.lastVisibleIndex = col.index; 3219 self.visibleColspan++; 3220 } 3221 if (col.hidden) self.hasHidden = true; 3222 }); 3223 self.ft.$el.toggleClass('breakpoint', self.hasHidden); 3224 }, 3225 /** 3226 * Performs the actual drawing of the columns, hiding or displaying them depending on there breakpoints. 3227 * @instance 3228 * @protected 3229 * @this FooTable.Columns 3230 */ 3231 draw: function(){ 3232 F.arr.each(this.array, function(col){ 3233 col.$el.css('display', (col.hidden || !col.visible ? 'none' : 'table-cell')); 3234 }); 3235 if (!this.showHeader && F.is.jq(this.$header.parent())){ 3236 this.$header.detach(); 3237 } 3238 }, 3239 /** 3240 * Creates the header row for the table from the parsed column definitions. 3241 * @instance 3242 * @protected 3243 * @this FooTable.Columns 3244 */ 3245 $create: function(){ 3246 var self = this; 3247 self.$header = F.is.jq(self.$header) ? self.$header : $('<tr/>', {'class': 'footable-header'}); 3248 self.$header.children('th,td').detach(); 3249 F.arr.each(self.array, function(col){ 3250 self.$header.append(col.$el); 3251 }); 3252 if (self.showHeader && !F.is.jq(self.$header.parent())){ 3253 self.ft.$el.children('thead').append(self.$header); 3254 } 3255 }, 3256 /** 3257 * Attempts to return a {@link FooTable.Column} instance when passed the {@link FooTable.Column} instance, the {@link FooTable.Column#name} string or the {@link FooTable.Column#index} number. 3258 * If supplied a function this will return an array by iterating all columns passing the index and column itself to the supplied callback as arguments. 3259 * Returning true in the callback will include the column in the result. 3260 * @instance 3261 * @param {(FooTable.Column|string|number|function)} column - The column to retrieve. 3262 * @returns {(Array.<FooTable.Column>|FooTable.Column|null)} The column if one is found otherwise it returns NULL. 3263 * @example <caption>This example shows retrieving a column by name assuming a column called "id" exists. The <code>columns</code> object is an instance of {@link FooTable.Columns}.</caption> 3264 * var column = columns.get('id'); 3265 * if (column instanceof FooTable.Column){ 3266 * // found the "id" column 3267 * } else { 3268 * // no column with a name of "id" exists 3269 * } 3270 * // to get an array of all hidden columns 3271 * var columns = columns.get(function(col){ 3272 * return col.hidden; 3273 * }); 3274 */ 3275 get: function(column){ 3276 if (column instanceof F.Column) return column; 3277 if (F.is.string(column)) return F.arr.first(this.array, function (col) { return col.name == column; }); 3278 if (F.is.number(column)) return F.arr.first(this.array, function (col) { return col.index == column; }); 3279 if (F.is.fn(column)) return F.arr.get(this.array, column); 3280 return null; 3281 }, 3282 /** 3283 * Takes an array of column names, index's or actual {@link FooTable.Column} and ensures that an array of only {@link FooTable.Column} is returned. 3284 * @instance 3285 * @param {(Array.<string>|Array.<number>|Array.<FooTable.Column>)} columns - The array of column names, index's or {@link FooTable.Column} to check. 3286 * @returns {Array.<FooTable.Column>} 3287 */ 3288 ensure: function(columns){ 3289 var self = this, result = []; 3290 if (!F.is.array(columns)) return result; 3291 F.arr.each(columns, function(name){ 3292 result.push(self.get(name)); 3293 }); 3294 return result; 3295 } 3296 }); 3297 3298 F.components.register('columns', F.Columns, 900); 3299 3300})(jQuery, FooTable); 3301(function(F){ 3302 /** 3303 * An array containing the column options or a jQuery promise that resolves returning the columns. The index of the definitions must match the index of each column as it should appear in the table. For more information on the options available see the {@link FooTable.Column} object. 3304 * @type {(Array.<object>|jQuery.Promise)} 3305 * @default [] 3306 * @example <caption>The below shows column definitions for a row defined as <code>{ id: Number, name: String, age: Number }</code>. The ID column has a fixed width, the table is initially sorted on the Name column and the Age column will be hidden on phones.</caption> 3307 * columns: [ 3308 * { name: 'id', title: 'ID', type: 'number' }, 3309 * { name: 'name', title: 'Name', sorted: true, direction: 'ASC' } 3310 * { name: 'age', title: 'Age', type: 'number', breakpoints: 'xs' } 3311 * ] 3312 */ 3313 F.Defaults.prototype.columns = []; 3314 3315 /** 3316 * Specifies whether or not the column headers should be displayed. 3317 * @type {boolean} 3318 * @default true 3319 */ 3320 F.Defaults.prototype.showHeader = true; 3321})(FooTable); 3322(function ($, F) { 3323 F.Rows = F.Component.extend(/** @lends FooTable.Rows */{ 3324 /** 3325 * The rows class contains all the logic for handling rows. 3326 * @constructs 3327 * @extends FooTable.Component 3328 * @param {FooTable.Table} table - The parent {@link FooTable.Table} this component belongs to. 3329 * @returns {FooTable.Rows} 3330 */ 3331 construct: function (table) { 3332 // call the base class constructor 3333 this._super(table, true); 3334 3335 /** 3336 * This provides a shortcut to the {@link FooTable.Table#options} object. 3337 * @instance 3338 * @protected 3339 * @type {FooTable.Table#options} 3340 */ 3341 this.o = table.o; 3342 /** 3343 * The current working array of {@link FooTable.Row} objects. 3344 * @instance 3345 * @protected 3346 * @type {Array.<FooTable.Row>} 3347 * @default [] 3348 */ 3349 this.array = []; 3350 /** 3351 * The base array of rows parsed from either the DOM or the constructor options. 3352 * The {@link FooTable.Rows#current} member is populated with a shallow clone of this array 3353 * during the predraw operation before any core or custom components are executed. 3354 * @instance 3355 * @protected 3356 * @type {Array.<FooTable.Row>} 3357 * @default [] 3358 */ 3359 this.all = []; 3360 /** 3361 * Whether or not to display a toggle in each row when it contains hidden columns. 3362 * @type {boolean} 3363 * @default true 3364 */ 3365 this.showToggle = table.o.showToggle; 3366 /** 3367 * The CSS selector used to filter row click events. If the event.target property matches the selector the row will be toggled. 3368 * @type {string} 3369 * @default "tr,td,.footable-toggle" 3370 */ 3371 this.toggleSelector = table.o.toggleSelector; 3372 /** 3373 * Specifies which column the row toggle is appended to. Supports only two values; "first" and "last" 3374 * @type {string} 3375 */ 3376 this.toggleColumn = table.o.toggleColumn; 3377 /** 3378 * The text to display when the table has no rows. 3379 * @type {string} 3380 */ 3381 this.emptyString = table.o.empty; 3382 /** 3383 * Whether or not the first rows details are expanded by default when displayed on a device that hides any columns. 3384 * @type {boolean} 3385 */ 3386 this.expandFirst = table.o.expandFirst; 3387 /** 3388 * Whether or not all row details are expanded by default when displayed on a device that hides any columns. 3389 * @type {boolean} 3390 */ 3391 this.expandAll = table.o.expandAll; 3392 /** 3393 * The jQuery object that contains the empty row control. 3394 * @type {jQuery} 3395 */ 3396 this.$empty = null; 3397 this._fromHTML = F.is.emptyArray(table.o.rows) && !F.is.promise(table.o.rows); 3398 }, 3399 /** 3400 * This parses the rows from either the tables rows or the supplied options. 3401 * @instance 3402 * @protected 3403 * @returns {jQuery.Promise} 3404 */ 3405 parse: function(){ 3406 var self = this; 3407 return $.Deferred(function(d){ 3408 var $rows = self.ft.$el.children('tbody').children('tr'); 3409 if (F.is.array(self.o.rows) && self.o.rows.length > 0){ 3410 self.parseFinalize(d, self.o.rows); 3411 } else if (F.is.promise(self.o.rows)){ 3412 self.o.rows.then(function(rows){ 3413 self.parseFinalize(d, rows); 3414 }, function(xhr){ 3415 d.reject(Error('Rows ajax request error: ' + xhr.status + ' (' + xhr.statusText + ')')); 3416 }); 3417 } else if (F.is.jq($rows)){ 3418 self.parseFinalize(d, $rows); 3419 $rows.detach(); 3420 } else { 3421 self.parseFinalize(d, []); 3422 } 3423 }); 3424 }, 3425 /** 3426 * Used to finalize the parsing of rows it is supplied the parse deferred object which must be resolved with an array of {@link FooTable.Row} objects 3427 * or rejected with an error. 3428 * @instance 3429 * @protected 3430 * @param {jQuery.Deferred} deferred - The deferred object used for parsing. 3431 * @param {(Array.<object>|jQuery)} rows - An array of row values and options or the jQuery object c
3431ontaining all rows. 3432 */ 3433 parseFinalize: function(deferred, rows){ 3434 var self = this, result = $.map(rows, function(r){ 3435 return new F.Row(self.ft, self.ft.columns.array, r); 3436 }); 3437 deferred.resolve(result); 3438 }, 3439 /** 3440 * The columns preinit method is used to parse and check the column options supplied from both static content and through the constructor. 3441 * @instance 3442 * @protected 3443 * @param {object} data - The jQuery data object from the root table element. 3444 * @fires FooTable.Rows#"preinit.ft.rows" 3445 */ 3446 preinit: function(data){ 3447 var self = this; 3448 /** 3449 * The preinit.ft.rows event is raised before any UI is created and provides the tables jQuery data object for additional options parsing. 3450 * Calling preventDefault on this event will disable the entire plugin. 3451 * @event FooTable.Rows#"preinit.ft.rows" 3452 * @param {jQuery.Event} e - The jQuery.Event object for the event. 3453 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 3454 * @param {object} data - The jQuery data object of the table raising the event. 3455 */ 3456 return self.ft.raise('preinit.ft.rows', [data]).then(function(){ 3457 return self.parse().then(function(rows){ 3458 self.all = rows; 3459 self.array = self.all.slice(0); 3460 self.showToggle = F.is.boolean(data.showToggle) ? data.showToggle : self.showToggle; 3461 self.toggleSelector = F.is.string(data.toggleSelector) ? data.toggleSelector : self.toggleSelector; 3462 self.toggleColumn = F.is.string(data.toggleColumn) ? data.toggleColumn : self.toggleColumn; 3463 if (self.toggleColumn != "first" && self.toggleColumn != "last") self.toggleColumn = "first"; 3464 self.emptyString = F.is.string(data.empty) ? data.empty : self.emptyString; 3465 self.expandFirst = F.is.boolean(data.expandFirst) ? data.expandFirst : self.expandFirst; 3466 self.expandAll = F.is.boolean(data.expandAll) ? data.expandAll : self.expandAll; 3467 }); 3468 }); 3469 }, 3470 /** 3471 * Initializes the rows class using the supplied table and options. 3472 * @instance 3473 * @protected 3474 * @fires FooTable.Rows#"init.ft.rows" 3475 */ 3476 init: function () { 3477 var self = this; 3478 /** 3479 * The init.ft.rows event is raised after the the rows are parsed from either the DOM or the options. 3480 * Calling preventDefault on this event will disable the entire plugin. 3481 * @event FooTable.Rows#"init.ft.rows" 3482 * @param {jQuery.Event} e - The jQuery.Event object for the event. 3483 * @param {FooTable.Table} instance - The instance of the plugin raising the event. 3484 * @param {Array.<FooTable.Row>} rows - The array of {@link FooTable.Row} objects parsed from the DOM or the options. 3485 */ 3486 return self.ft.raise('init.ft.rows', [self.all]).then(function(){ 3487 self.$create(); 3488 }); 3489 }, 3490 /** 3491 * Destroys the rows component removing any UI generated from the table. 3492 * @instance 3493 * @protected 3494 * @fires FooTable.Rows#"destroy.ft.rows" 3495 */ 3496 destroy: function(){ 3497 /** 3498 * The destroy.ft.rows event is raised before its UI is removed. 3499 * Calling preventDefault on this event will prevent the component from being destroyed. 3500 * @event FooTable.Rows#"destroy.ft.rows" 3501 * @param {jQuery.Event} e - The jQuery.Event object for the event. 3502 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 3503 */ 3504 var self = this; 3505 this.ft.raise('destroy.ft.rows').then(function(){ 3506 F.arr.each(self.array, function(row){ 3507 row.predraw(!self._fromHTML); 3508 }); 3509 self.all = self.array = []; 3510 }); 3511 }, 3512 /** 3513 * Performs the predraw operations that are required including creating the shallow clone of the {@link FooTable.Rows#array} to work with. 3514 * @instance 3515 * @protected 3516 */ 3517 predraw: function(){ 3518 F.arr.each(this.array, function(row){ 3519 row.predraw(); 3520 }); 3521 this.array = this.all.slice(0); 3522 }, 3523 $create: function(){ 3524 this.$empty = $('<tr/>', { 'class': 'footable-empty' }).append($('<td/>').text(this.emptyString)); 3525 }, 3526 /** 3527 * Performs the actual drawing of the table rows. 3528 * @instance 3529 * @protected 3530 */ 3531 draw: function(){ 3532 var self = this, $tbody = self.ft.$el.children('tbody'), first = true; 3533 // if we have rows 3534 if (self.array.length > 0){ 3535 self.$empty.detach(); 3536 // loop through them appending to the tbody and then drawing 3537 F.arr.each(self.array, function(row){ 3538 if ((self.expandFirst && first) || self.expandAll){ 3539 row.expanded = true; 3540 first = false;
3541 } 3542 row.draw($tbody); 3543 }); 3544 } else { 3545 // otherwise display the $empty row 3546 self.$empty.children('td').attr('colspan', self.ft.columns.visibleColspan); 3547 $tbody.append(self.$empty); 3548 } 3549 }, 3550 /** 3551 * Loads a JSON array of row objects into the table 3552 * @instance 3553 * @param {Array.<object>} data - An array of row objects to load. 3554 * @param {boolean} [append=false] - Whether or not to append the new rows to the current rows array or to replace them entirely. 3555 */ 3556 load: function(data, append){ 3557 var self = this, rows = $.map(data, function(r){ 3558 return new F.Row(self.ft, self.ft.columns.array, r); 3559 }); 3560 F.arr.each(this.array, function(row){ 3561 row.predraw(); 3562 }); 3563 this.all = (F.is.boolean(append) ? append : false) ? this.all.concat(rows) : rows; 3564 this.array = this.all.slice(0); 3565 this.ft.draw(); 3566 }, 3567 /** 3568 * Expands all visible rows. 3569 * @instance 3570 */ 3571 expand: function(){ 3572 F.arr.each(this.array, function(row){ 3573 row.expand(); 3574 }); 3575 }, 3576 /** 3577 * Collapses all visible rows. 3578 * @instance 3579 */ 3580 collapse: function(){ 3581 F.arr.each(this.array, function(row){ 3582 row.collapse(); 3583 }); 3584 } 3585 }); 3586 3587 F.components.register('rows', F.Rows, 800); 3588 3589})(jQuery, FooTable); 3590(function(F){ 3591 /** 3592 * An array of JSON objects containing the row data or a jQuery promise that resolves returning the row data. 3593 * @type {(Array.<object>|jQuery.Promise)} 3594 * @default [] 3595 */ 3596 F.Defaults.prototype.rows = []; 3597 3598 /** 3599 * A string to display when there are no rows in the table. 3600 * @type {string} 3601 * @default "No results" 3602 */ 3603 F.Defaults.prototype.empty = 'No results'; 3604 3605 /** 3606 * Whether or not the toggle is appended to each row. 3607 * @type {boolean} 3608 * @default true 3609 */ 3610 F.Defaults.prototype.showToggle = true; 3611 3612 /** 3613 * The CSS selector used to filter row click events. If the event.target property matches the selector the row will be toggled. 3614 * @type {string} 3615 * @default "tr,td,.footable-toggle" 3616 */ 3617 F.Defaults.prototype.toggleSelector = 'tr,td,.footable-toggle'; 3618 3619 /** 3620 * Specifies which column to display the row toggle in. The only supported values are "first" or "last". 3621 * @type {string} 3622 * @default "first" 3623 */ 3624 F.Defaults.prototype.toggleColumn = 'first'; 3625 3626 /** 3627 * Whether or not the first rows details are expanded by default when displayed on a device that hides any columns. 3628 * @type {boolean} 3629 */ 3630 F.Defaults.prototype.expandFirst = false; 3631 3632 /** 3633 * Whether or not all row details are expanded by default when displayed on a device that hides any columns. 3634 * @type {boolean} 3635 */ 3636 F.Defaults.prototype.expandAll = false; 3637})(FooTable); 3638(function(F){ 3639 /** 3640 * Loads a JSON array of row objects into the table 3641 * @param {Array.<object>} data - An array of row objects to load. 3642 * @param {boolean} [append=false] - Whether or not to append the new rows to the current rows array or to replace them entirely. 3643 */ 3644 F.Table.prototype.loadRows = function(data, append){ 3645 this.rows.load(data, append); 3646 }; 3647})(FooTable); 3648(function(F){ 3649 F.Filter = F.Class.extend(/** @lends FooTable.Filter */{ 3650 /** 3651 * The filter object contains the query to filter by and the columns to apply it to. 3652 * @constructs 3653 * @extends FooTable.Class 3654 * @param {string} name - The name for the filter. 3655 * @param {(string|FooTable.Query)} query - The query for the filter. 3656 * @param {Array.<FooTable.Column>} columns - The columns to apply the query to. 3657 * @param {string} [space="AND"] - How the query treats space chars. 3658 * @param {boolean} [connectors=true] - Whether or not to replace phrase connectors (+.-_) with spaces. 3659 * @param {boolean} [ignoreCase=true] - Whether or not ignore case when matching. 3660 * @param {boolean} [hidden=true] - Whether or not this is a hidden filter. 3661 * @returns {FooTable.Filter} 3662 */ 3663 construct: function(name, query, columns, space, connectors, ignoreCase, hidden){ 3664 /** 3665 * The name of the filter. 3666 * @instance 3667 * @type {string} 3668 */ 3669 this.name = name; 3670 /** 3671 * A string specifying how the filter treats space characters. Can be either "OR" or "AND". 3672 * @instance 3673 * @type {string} 3674 */ 3675 this.space = F.is.string(space) && (space == 'OR' || space == 'AND') ? space : 'AND'; 3676 /** 3677 * Whether or not to replace phrase connectors (+.-_) with spaces before executing the query. 3678 * @instance 3679 * @type {boolean} 3680 */ 3681 this.connectors = F.is.boolean(connectors) ? connectors : true; 3682 /** 3683 * Whether or not ignore case when matching. 3684 * @instance 3685 * @type {boolean} 3686 */ 3687 this.ignoreCase = F.is.boolean(ignoreCase) ? ignoreCase : true; 3688 /** 3689 * Whether or not this is a hidden filter. 3690 * @instance 3691 * @type {boolean} 3692 */ 3693 this.hidden = F.is.boolean(hidden) ? hidden : false;
3694 /** 3695 * The query for the filter. 3696 * @instance 3697 * @type {(string|FooTable.Query)} 3698 */ 3699 this.query = query instanceof F.Query ? query : new F.Query(query, this.space, this.connectors, this.ignoreCase); 3700 /** 3701 * The columns to apply the query to. 3702 * @instance 3703 * @type {Array.<FooTable.Column>} 3704 */ 3705 this.columns = columns; 3706 }, 3707 /** 3708 * Checks if the current filter matches the supplied string. 3709 * If the current query property is a string it will be auto converted to a {@link FooTable.Query} object to perform the match. 3710 * @instance 3711 * @param {string} str - The string to check. 3712 * @returns {boolean} 3713 */ 3714 match: function(str){ 3715 if (!F.is.string(str)) return false; 3716 if (F.is.string(this.query)){ 3717 this.query = new F.Query(this.query, this.space, this.connectors, this.ignoreCase); 3718 } 3719 return this.query instanceof F.Query ? this.query.match(str) : false; 3720 }, 3721 /** 3722 * Checks if the current filter matches the supplied {@link FooTable.Row}. 3723 * @instance 3724 * @param {FooTable.Row} row - The row to check. 3725 * @returns {boolean} 3726 */ 3727 matchRow: function(row){ 3728 var self = this, text = F.arr.map(row.cells, function(cell){ 3729 return F.arr.contains(self.columns, cell.column) ? cell.filterValue : null; 3730 }).join(' '); 3731 return self.match(text); 3732 } 3733 }); 3734 3735})(FooTable); 3736(function ($, F) { 3737 F.Filtering = F.Component.extend(/** @lends FooTable.Filtering */{ 3738 /** 3739 * The filtering component adds a search input and column selector dropdown to the table allowing users to filter the using space delimited queries. 3740 * @constructs 3741 * @extends FooTable.Component 3742 * @param {FooTable.Table} table - The parent {@link FooTable.Table} object for the component. 3743 * @returns {FooTable.Filtering} 3744 */ 3745 construct: function (table) { 3746 // call the constructor of the base class 3747 this._super(table, table.o.filtering.enabled); 3748 3749 /* PUBLIC */ 3750 /** 3751 * The filters to apply to the current {@link FooTable.Rows#array}. 3752 * @instance 3753 * @type {Array.<FooTable.Filter>} 3754 */ 3755 this.filters = table.o.filtering.filters; 3756 /** 3757 * The delay in milliseconds before the query is auto applied after a change. 3758 * @instance 3759 * @type {number} 3760 */ 3761 this.delay = table.o.filtering.delay; 3762 /** 3763 * The minimum number of characters allowed in the search input before it is auto applied. 3764 * @instance 3765 * @type {number} 3766 */ 3767 this.min = table.o.filtering.min; 3768 /** 3769 * Specifies how whitespace in a filter query is handled. 3770 * @instance 3771 * @type {string} 3772 */ 3773 this.space = table.o.filtering.space; 3774 /** 3775 * Whether or not to replace phrase connectors (+.-_) with spaces before executing the query. 3776 * @instance 3777 * @type {boolean} 3778 */ 3779 this.connectors = table.o.filtering.connectors; 3780 /** 3781 * Whether or not ignore case when matching. 3782 * @instance 3783 * @type {boolean} 3784 */ 3785 this.ignoreCase = table.o.filtering.ignoreCase; 3786 /** 3787 * Whether or not search queries are treated as phrases when matching. 3788 * @instance 3789 * @type {boolean} 3790 */ 3791 this.exactMatch = table.o.filtering.exactMatch; 3792 /** 3793 * The placeholder text to display within the search $input. 3794 * @instance 3795 * @type {string} 3796 */ 3797 this.placeholder = table.o.filtering.placeholder; 3798 /** 3799 * The title to display at the top of the search input column select. 3800 * @type {string} 3801 */ 3802 this.dropdownTitle = table.o.filtering.dropdownTitle; 3803 /** 3804 * The position of the $search input within the filtering rows cell. 3805 * @type {string} 3806 */ 3807 this.position = table.o.filtering.position; 3808 /** 3809 * Whether or not to focus the search input after the search/clear button is clicked or after auto applying the search input query. 3810 * @type {boolean} 3811 */ 3812 this.focus = table.o.filtering.focus; 3813 /** 3814 * A selector specifying where to place the filtering components form, if null the form is displayed within a row in the head of the table. 3815 * @type {string} 3816 */ 3817 this.container = table.o.filtering.container; 3818 /** 3819 * The jQuery object of the element containing the entire filtering form. 3820 * @instance 3821 * @type {jQuery} 3822 */ 3823 this.$container = null; 3824 /** 3825 * The jQuery row object that contains all the filtering specific elements. 3826 * @instance 3827 * @type {jQuery} 3828 */ 3829 this.$row = null; 3830 /** 3831 * The jQuery cell object that contains the search input and column selector. 3832 * @instance 3833 * @type {jQuery} 3834 */ 3835 this.$cell = null; 3836 /** 3837 * The jQuery form object of the form that contains the search input and column selector. 3838 * @instance 3839 * @type {jQuery} 3840 */ 3841 this.$form = null; 3842 /** 3843 * The jQuery object of the column selector dropdown. 3844 * @instance 3845 * @type {jQuery} 3846 */ 3847 this.$dropdown = null; 3848 /** 3849 * The jQuery object of the search input. 3850 * @instance 3851 * @type {jQuery} 3852 */ 3853 this.$input = null; 3854 /** 3855 * The jQuery object of the search button. 3856 * @instance 3857 * @type {jQuery} 3858 */ 3859 this.$button = null; 3860 3861 /* PRIVATE */ 3862 /** 3863 * The timeout ID for the filter changed event. 3864 * @instance 3865 * @private 3866 * @type {?number} 3867 */ 3868 this._filterTimeout = null; 3869 /** 3870 * The regular expression used to check for encapsulating quotations. 3871 * @instance 3872 * @private 3873 * @type {RegExp} 3874 */ 3875 this._exactRegExp = /^"(.*?)"$/; 3876 }, 3877 3878 /* PROTECTED */ 3879 /** 3880 * Checks the supplied data and options for the filtering component. 3881 * @instance 3882 * @protected 3883 * @param {object} data - The jQuery data object from the parent table. 3884 * @fires FooTable.Filtering#"preinit.ft.filtering" 3885 */ 3886 preinit: function(data){ 3887 var self = this; 3888 /** 3889 * The preinit.ft.filtering event is raised before the UI is created and provides the tables jQuery data object for additional options parsing. 3890 * Calling preventDefault on this event will disable the component. 3891 * @event FooTable.Filtering#"preinit.ft.filtering" 3892 * @param {jQuery.Event} e - The jQuery.Event object for the event. 3893 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 3894 * @param {object} data - The jQuery data object of the table raising the event. 3895 */ 3896 return self.ft.raise('preinit.ft.filtering').then(function(){ 3897 // first check if filtering is enabled via the class being applied 3898 if (self.ft.$el.hasClass('footable-filtering')) 3899 self.enabled = true; 3900 // then check if the data-filtering-enabled attribute has been set 3901 self.enabled = F.is.boolean(data.filtering) 3902 ? data.filtering 3903 : self.enabled; 3904 3905 // if filtering is not enabled exit early as we don't need to do anything else 3906 if (!self.enabled) return; 3907 3908 self.space = F.is.string(data.filterSpace) 3909 ? data.filterSpace 3910 : self.space; 3911 3912 self.min = F.is.number(data.filterMin) 3913 ? data.filterMin 3914 : self.min; 3915 3916 self.connectors = F.is.boolean(data.filterConnectors) 3917 ? data.filterConnectors 3918 : self.connectors; 3919 3920 self.ignoreCase = F.is.boolean(data.filterIgnoreCase) 3921 ? data.filterIgnoreCase 3922 : self.ignoreCase; 3923 3924 self.exactMatch = F.is.boolean(data.filterExactMatch) 3925 ? data.filterExactMatch 3926 : self.exactMatch; 3927 3928 self.focus = F.is.boolean(data.filterFocus) 3929 ? data.filterFocus 3930 : self.focus; 3931 3932 self.delay = F.is.number(data.filterDelay) 3933 ? data.filterDelay 3934 : self.delay; 3935 3936 self.placeholder = F.is.string(data.filterPlaceholder) 3937 ? data.filterPlaceholder 3938 : self.placeholder; 3939 3940 self.dropdownTitle = F.is.string(data.filterDropdownTitle) 3941 ? data.filterDropdownTitle 3942 : self.dropdownTitle; 3943 3944 self.container = F.is.string(data.filterContainer) 3945 ? data.filterContainer 3946 : self.container; 3947 3948 self.filters = F.is.array(data.filterFilters) 3949 ? self.ensure(data.filterFilters) 3950 : self.ensure(self.filters); 3951 3952 if (self.ft.$el.hasClass('footable-filtering-left')) 3953 self.position = 'left'; 3954 if (self.ft.$el.hasClass('footable-filtering-center')) 3955 self.position = 'center'; 3956 if (self.ft.$el.hasClass('footable-filtering-right')) 3957 self.position = 'right'; 3958 3959 self.position = F.is.string(data.filterPosition) 3960 ? data.filterPosition 3961 : self.position; 3962 },function(){ 3963 self.enabled = false;
3964 }); 3965 }, 3966 /** 3967 * Initializes the filtering component for the plugin. 3968 * @instance 3969 * @protected 3970 * @fires FooTable.Filtering#"init.ft.filtering" 3971 */ 3972 init: function () { 3973 var self = this; 3974 /** 3975 * The init.ft.filtering event is raised before its UI is generated. 3976 * Calling preventDefault on this event will disable the component. 3977 * @event FooTable.Filtering#"init.ft.filtering" 3978 * @param {jQuery.Event} e - The jQuery.Event object for the event. 3979 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 3980 */ 3981 return self.ft.raise('init.ft.filtering').then(function(){ 3982 self.$create(); 3983 }, function(){ 3984 self.enabled = false; 3985 }); 3986 }, 3987 /** 3988 * Destroys the filtering component removing any UI from the table. 3989 * @instance 3990 * @protected 3991 * @fires FooTable.Filtering#"destroy.ft.filtering" 3992 */ 3993 destroy: function () { 3994 var self = this; 3995 /** 3996 * The destroy.ft.filtering event is raised before its UI is removed. 3997 * Calling preventDefault on this event will prevent the component from being destroyed. 3998 * @event FooTable.Filtering#"destroy.ft.filtering" 3999 * @param {jQuery.Event} e - The jQuery.Event object for the event. 4000 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 4001 */ 4002 return self.ft.raise('destroy.ft.filtering').then(function(){ 4003 self.ft.$el.removeClass('footable-filtering') 4004 .find('thead > tr.footable-filtering').remove(); 4005 }); 4006 }, 4007 /** 4008 * Creates the filtering UI from the current options setting the various jQuery properties of this component. 4009 * @instance 4010 * @protected 4011 * @this FooTable.Filtering 4012 */ 4013 $create: function () { 4014 var self = this; 4015 // generate the cell that actually contains all the UI. 4016 var $form_grp = $('<div/>', {'class': 'form-group footable-filtering-search'}) 4017 .append($('<label/>', {'class': 'sr-only', text: 'Search'})), 4018 $input_grp = $('<div/>', {'class': 'input-group'}).appendTo($form_grp), 4019 $input_grp_btn = $('<div/>', {'class': 'input-group-btn'}), 4020 $dropdown_toggle = $('<button/>', {type: 'button', 'class': 'btn btn-default dropdown-toggle'}) 4021 .on('click', { self: self }, self._onDropdownToggleClicked) 4022 .append($('<span/>', {'class': 'caret'})), 4023 position; 4024 4025 switch (self.position){ 4026 case 'left': position = 'footable-filtering-left'; break; 4027 case 'center': position = 'footable-filtering-center'; break; 4028 default: position = 'footable-filtering-right'; break; 4029 } 4030 self.ft.$el.addClass('footable-filtering').addClass(position); 4031 4032 self.$container = self.container === null ? $() : $(self.container).first(); 4033 if (!self.$container.length){ 4034 // add it to a row and then populate it with the search input and column selector dropdown. 4035 self.$row = $('<tr/>', {'class': 'footable-filtering'}).prependTo(self.ft.$el.children('thead')); 4036 self.$cell = $('<th/>').attr('colspan', self.ft.columns.visibleColspan).appendTo(self.$row); 4037 self.$container = self.$cell; 4038 } else { 4039 self.$container.addClass('footable-filtering-external').addClass(position); 4040 } 4041 self.$form = $('<form/>', {'class': 'form-inline'}).append($form_grp).appendTo(self.$container); 4042 4043 self.$input = $('<input/>', {type: 'text', 'class': 'form-control', placeholder: self.placeholder}); 4044 4045 self.$button = $('<button/>', {type: 'button', 'class': 'btn btn-primary'}) 4046 .on('click', { self: self }, self._onSearchButtonClicked) 4047 .append($('<span/>', {'class': 'fooicon fooicon-search'})); 4048 4049 self.$dropdown = $('<ul/>', {'class': 'dropdown-menu dropdown-menu-right'}); 4050 if (!F.is.emptyString(self.dropdownTitle)){ 4051 self.$dropdown.append($('<li/>', {'class': 'dropdown-header','text': self.dropdownTitle})); 4052 } 4053 self.$dropdown.append( 4054 F.arr.map(self.ft.columns.array, function (col) { 4055 return col.filterable ? $('<li/>').append( 4056 $('<a/>', {'class': 'checkbox'}).append( 4057 $('<label/>', {html: col.title}).prepend( 4058 $('<input/>', {type: 'checkbox', checked: true}).data('__FooTableColumn__', col) 4059 ) 4060 ) 4061 ) : null; 4062 }) 4063 ); 4064 4065 if (self.delay > 0){ 4066 self.$input.on('keypress keyup paste', { self: self }, self._onSearchInputChanged); 4067 self.$dropdown.on('click', 'input[type="checkbox"]', {self: self}, self._onSearchColumnClicked); 4068 } 4069 4070 $input_grp_btn.append(self.$button, $dropdown_toggle, self.$dropdown); 4071 $input_grp.append(self.$input, $input_grp_btn); 4072 }, 4073 /** 4074 * Performs the filtering of rows before they are appended to the page. 4075 * @instance 4076 * @protected 4077 */ 4078 predraw: function(){ 4079 if (F.is.emptyArray(this.filters)) 4080 return; 4081 4082 var self = this; 4083 self.ft.rows.array = $.grep(self.ft.rows.array, function(r){ 4084 return r.filtered(self.filters); 4085 }); 4086 }, 4087 /** 4088 * As the rows are drawn by the {@link FooTable.Rows#draw}
4088 method this simply updates the colspan for the UI. 4089 * @instance 4090 * @protected 4091 */ 4092 draw: function(){ 4093 if (F.is.jq(this.$cell)){ 4094 this.$cell.attr('colspan', this.ft.columns.visibleColspan); 4095 } 4096 var search = this.find('search'); 4097 if (search instanceof F.Filter){ 4098 var query = search.query.val(); 4099 if (this.exactMatch && this._exactRegExp.test(query)){ 4100 query = query.replace(this._exactRegExp, '$1'); 4101 } 4102 this.$input.val(query); 4103 } else { 4104 this.$input.val(null); 4105 } 4106 this.setButton(!F.arr.any(this.filters, function(f){ return !f.hidden; })); 4107 }, 4108 4109 /* PUBLIC */ 4110 /** 4111 * Adds or updates the filter using the supplied name, query and columns. 4112 * @instance 4113 * @param {(string|FooTable.Filter|object)} nameOrFilter - The name for the filter or the actual filter object itself. 4114 * @param {(string|FooTable.Query)} [query] - The query for the filter. This is only optional when the first parameter is a filter object. 4115 * @param {(Array.<number>|Array.<string>|Array.<FooTable.Column>)} [columns] - The columns to apply the filter to. 4116 * If not supplied the filter will be applied to all selected columns in the search input dropdown. 4117 * @param {boolean} [ignoreCase=true] - Whether or not ignore case when matching. 4118 * @param {boolean} [connectors=true] - Whether or not to replace phrase connectors (+.-_) with spaces. 4119 * @param {string} [space="AND"] - How the query treats space chars. 4120 * @param {boolean} [hidden=true] - Whether or not this is a hidden filter. 4121 */ 4122 addFilter: function(nameOrFilter, query, columns, ignoreCase, connectors, space, hidden){ 4123 var f = this.createFilter(nameOrFilter, query, columns, ignoreCase, connectors, space, hidden); 4124 if (f instanceof F.Filter){ 4125 this.removeFilter(f.name); 4126 this.filters.push(f); 4127 } 4128 }, 4129 /** 4130 * Removes the filter using the supplied name if it exists. 4131 * @instance 4132 * @param {string} name - The name of the filter to remove. 4133 */ 4134 removeFilter: function(name){ 4135 F.arr.remove(this.filters, function(f){ return f.name == name; }); 4136 }, 4137 /** 4138 * Performs the required steps to handle filtering including the raising of the {@link FooTable.Filtering#"before.ft.filtering"} and {@link FooTable.Filtering#"after.ft.filtering"} events. 4139 * @instance 4140 * @param {boolean} [focus=false] - Whether or not to set the focus to the input once filtering is complete. 4141 * @returns {jQuery.Promise} 4142 * @fires FooTable.Filtering#"before.ft.filtering" 4143 * @fires FooTable.Filtering#"after.ft.filtering" 4144 */ 4145 filter: function(focus){ 4146 var self = this; 4147 self.filters = self.ensure(self.filters); 4148 /** 4149 * The before.ft.filtering event is raised before a filter is applied and allows listeners to modify the filter or cancel it completely by calling preventDefault on the jQuery.Event object. 4150 * @event FooTable.Filtering#"before.ft.filtering" 4151 * @param {jQuery.Event} e - The jQuery.Event object for the event. 4152 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 4153 * @param {Array.<FooTable.Filter>} filters - The filters that are about to be applied. 4154 */ 4155 return self.ft.raise('before.ft.filtering', [self.filters]).then(function(){ 4156 self.filters = self.ensure(self.filters); 4157 if (focus){ 4158 var start = self.$input.prop('selectionStart'), 4159 end = self.$input.prop('selectionEnd'); 4160 } 4161 return self.ft.draw().then(function(){ 4162 if (focus){ 4163 self.$input.focus().prop({ 4164 selectionStart: start, 4165 selectionEnd: end 4166 }); 4167 } 4168 /** 4169 * The after.ft.filtering event is raised after a filter has been applied. 4170 * @event FooTable.Filtering#"after.ft.filtering" 4171 * @param {jQuery.Event} e - The jQuery.Event object for the event. 4172 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 4173 * @param {FooTable.Filter} filter - The filters that were applied. 4174 */ 4175 self.ft.raise('after.ft.filtering', [self.filters]); 4176 }); 4177 }); 4178 }, 4179 /** 4180 * Removes the current search filter. 4181 * @instance 4182 * @returns {jQuery.Promise} 4183 * @fires FooTable.Filtering#"before.ft.filtering" 4184 * @fires FooTable.Filtering#"after.ft.filtering" 4185 */ 4186 clear: function(){ 4187 this.filters = F.arr.get(this.filters, function(f){ return f.hidden; }); 4188 return this.filter(this.focus); 4189 }, 4190 /** 4191 * Toggles the button icon between the search and clear icons based on the supplied value. 4192 * @instance 4193 * @param {boolean} search - Whether or not to display the search icon. 4194 */ 4195 setButton: function(search){ 4196 if (!search){ 4197 this.$button.children('.fooicon').removeClass('fooicon-search').addClass('fooicon-remove'); 4198 } else { 4199 this.$button.children('.fooicon').removeClass('fooicon-remove').addClass('fooicon-search'); 4200 } 4201 }, 4202 /** 4203 * Finds a filter by name. 4204 * @param {string} name - The name of the filter to find. 4205 * @returns {(FooTable.Filter|null)} 4206 */ 4207 find: function(name){ 4208 return F.arr.first(this.filters, function(f){ return f.name == name; }); 4209 }, 4210 /** 4211 * Gets an array of {@link FooTable.Column}
4211 to apply the search filter to. This also doubles as the default columns for filters which do not specify any columns. 4212 * @instance 4213 * @returns {Array.<FooTable.Column>} 4214 */ 4215 columns: function(){ 4216 if (F.is.jq(this.$dropdown)){ 4217 // if we have a dropdown containing the column names get the selected columns from there 4218 return this.$dropdown.find('input:checked').map(function(){ 4219 return $(this).data('__FooTableColumn__'); 4220 }).get(); 4221 } else { 4222 // otherwise find all columns that are set to be filterable. 4223 return this.ft.columns.get(function(c){ return c.filterable; }); 4224 } 4225 }, 4226 /** 4227 * Takes an array of plain objects containing the filter values or actual {@link FooTable.Filter} objects and ensures that an array of only {@link FooTable.Filter} is returned. 4228 * If supplied a plain object that object must contain a name, query and columns properties which are used to create a new {@link FooTable.Filter}. 4229 * @instance 4230 * @param {({name: string, query: (string|FooTable.Query), columns: (Array.<string>|Array.<number>|Array.<FooTable.Column>)}|Array.<FooTable.Filter>)} filters - The array of filters to check. 4231 * @returns {Array.<FooTable.Filter>} 4232 */ 4233 ensure: function(filters){ 4234 var self = this, parsed = [], filterable = self.columns(); 4235 if (!F.is.emptyArray(filters)){ 4236 F.arr.each(filters, function(f){ 4237 f = self._ensure(f, filterable); 4238 if (f instanceof F.Filter) parsed.push(f); 4239 }); 4240 } 4241 return parsed; 4242 }, 4243 4244 /** 4245 * Creates a new filter using the supplied object or individual parameters to populate it. 4246 * @instance 4247 * @param {(string|FooTable.Filter|object)} nameOrObject - The name for the filter or the actual filter object itself. 4248 * @param {(string|FooTable.Query)} [query] - The query for the filter. This is only optional when the first parameter is a filter object. 4249 * @param {(Array.<number>|Array.<string>|Array.<FooTable.Column>)} [columns] - The columns to apply the filter to. 4250 * If not supplied the filter will be applied to all selected columns in the search input dropdown. 4251 * @param {boolean} [ignoreCase=true] - Whether or not ignore case when matching. 4252 * @param {boolean} [connectors=true] - Whether or not to replace phrase connectors (+.-_) with spaces. 4253 * @param {string} [space="AND"] - How the query treats space chars. 4254 * @param {boolean} [hidden=true] - Whether or not this is a hidden filter. 4255 * @returns {*} 4256 */ 4257 createFilter: function(nameOrObject, query, columns, ignoreCase, connectors, space, hidden){ 4258 if (F.is.string(nameOrObject)){ 4259 nameOrObject = {name: nameOrObject, query: query, columns: columns, ignoreCase: ignoreCase, connectors: connectors, space: space, hidden: hidden}; 4260 } 4261 return this._ensure(nameOrObject, this.columns()); 4262 }, 4263 4264 /* PRIVATE */ 4265 _ensure: function(filter, selectedColumns){ 4266 if ((F.is.hash(filter) || filter instanceof F.Filter) && !F.is.emptyString(filter.name) && (!F.is.emptyString(filter.query) || filter.query instanceof F.Query)){ 4267 filter.columns = F.is.emptyArray(filter.columns) ? selectedColumns : this.ft.columns.ensure(filter.columns); 4268 filter.ignoreCase = F.is.boolean(filter.ignoreCase) ? filter.ignoreCase : this.ignoreCase; 4269 filter.connectors = F.is.boolean(filter.connectors) ? filter.connectors : this.connectors; 4270 filter.hidden = F.is.boolean(filter.hidden) ? filter.hidden : false; 4271 filter.space = F.is.string(filter.space) && (filter.space === 'AND' || filter.space === 'OR') ? filter.space : this.space; 4272 filter.query = F.is.string(filter.query) ? new F.Query(filter.query, filter.space, filter.connectors, filter.ignoreCase) : filter.query; 4273 return (filter instanceof F.Filter) 4274 ? filter 4275 : new F.Filter(filter.name, filter.query, filter.columns, filter.space, filter.connectors, filter.ignoreCase, filter.hidden); 4276 } 4277 return null; 4278 }, 4279 /** 4280 * Handles the change event for the {@link FooTable.Filtering#$input}. 4281 * @instance 4282 * @private 4283 * @param {jQuery.Event} e - The event object for the event. 4284 */ 4285 _onSearchInputChanged: function (e) { 4286 var self = e.data.self; 4287 var alpha = e.type == 'keypress' && !F.is.emptyString(String.fromCharCode(e.charCode)), 4288 ctrl = e.type == 'keyup' && (e.which == 8 || e.which == 46), 4289 paste = e.type == 'paste'; // backspace & delete 4290 4291 // if alphanumeric characters or specific control characters 4292 if(alpha || ctrl || paste) { 4293 if (e.which == 13) e.preventDefault(); 4294 if (self._filterTimeout != null) clearTimeout(self._filterTimeout); 4295 self._filterTimeout = setTimeout(function(){ 4296 self._filterTimeout = null; 4297 var query = self.$input.val(); 4298 if (query.length >= self.min){ 4299 if (self.exactMatch && !self._exactRegExp.test(query)){ 4300 query = '"' + query + '"'; 4301 } 4302 self.addFilter('search', query); 4303 self.filter(self.focus); 4304 } else if (F.is.emptyString(query)){ 4305 self.clear(); 4306 } 4307 }, self.delay); 4308 } 4309 }, 4310 /** 4311 * Handles the click event for the {@link FooTable.Filtering#$button}. 4312 * @instance 4313 * @private 4314 * @param {jQuery.Event} e - The event object for the event. 4315 */ 4316 _onSearchButtonClicked: function (e) { 4317 e.preventDefault(); 4318 var self = e.data.self; 4319 if (self._filterTimeout != null) clearTimeout(self._filterTimeout); 4320 var $icon = self.$button.children('.fooicon'); 4321 if ($icon.hasClass('fooicon-remove')) self.clear(); 4322 else { 4323 var query = self.$input.val(); 4324 if (query.length >= self.min){ 4325 if (self.exactMatch && !self._exactRegExp.test(query)){ 4326 query = '"' + query + '"'; 4327 } 4328 self.addFilter('search', query); 4329 self.filter(self.focus); 4330 } 4331 } 4332 }, 4333 /** 4334 * Handles the click event for the column checkboxes in the {@link FooTable.Filtering#$dropdown}. 4335 * @instance 4336 * @private 4337 * @param {jQuery.Event} e - The event object for the event. 4338 */ 4339 _onSearchColumnClicked: function (e) { 4340 var self = e.data.self; 4341 if (self._filterTimeout != null) clearTimeout(self._filterTimeout); 4342 self._filterTimeout = setTimeout(function(){ 4343 self._filterTimeout = null; 4344 var $icon = self.$button.children('.fooicon'); 4345 if ($icon.hasClass('fooicon-remove')){ 4346 $icon.removeClass('fooicon-remove').addClass('fooicon-search'); 4347 self.addFilter('search', self.$input.val()); 4348 self.filter(); 4349 } 4350 }, self.delay); 4351 }, 4352 /** 4353 * Handles the click event for the {@link FooTable.Filtering#$dropdown} toggle. 4354 * @instance 4355 * @private 4356 * @param {jQuery.Event} e - The event object for the event. 4357 */ 4358 _onDropdownToggleClicked: function (e) { 4359 e.preventDefault(); 4360 e.stopPropagation(); 4361 var self = e.data.self; 4362 self.$dropdown.parent().toggleClass('open'); 4363 if (self.$dropdown.parent().hasClass('open')) $(document).on('click.footable', { self: self }, self._onDocumentClicked); 4364 else $(document).off('click.footable', self._onDocumentClicked); 4365 }, 4366 /** 4367 * Checks all click events when the dropdown is visible and closes the menu if the target is not the dropdown. 4368 * @instance 4369 * @private 4370 * @param {jQuery.Event} e - The event object for the event. 4371 */ 4372 _onDocumentClicked: function(e){ 4373 if ($(e.target).closest('.dropdown-menu').length == 0){ 4374 e.preventDefault(); 4375 var self = e.data.self; 4376 self.$dropdown.parent().removeClass('open'); 4377 $(document).off('click.footable', self._onDocumentClicked); 4378 } 4379 } 4380 }); 4381 4382 F.components.register('filtering', F.Filtering, 500); 4383 4384})(jQuery, FooTable); 4385 4386(function(F){ 4387 F.Query = F.Class.extend(/** @lends FooTable.Query */{ 4388 /** 4389 * The query object is used to parse and test the filtering component's queries 4390 * @constructs 4391 * @extends FooTable.Class 4392 * @param {string} query - The string value of the query. 4393 * @param {string} [space="AND"] - How the query treats whitespace. 4394 * @param {boolean} [connectors=true] - Whether or not to replace phrase connectors (+.-_) with spaces. 4395 * @param {boolean} [ignoreCase=true] - Whether or not ignore case when matching. 4396 * @returns {FooTable.Query} 4397 */ 4398 construct: function(query, space, connectors, ignoreCase){ 4399 /* PRIVATE */ 4400 /** 4401 * Holds the previous value of the query and is used internally in the {@link FooTable.Query#val} method. 4402 * @type {string} 4403 * @private 4404 */ 4405 this._original = null; 4406 /** 4407 * Holds the value for the query. Access to this variable is provided through the {@link FooTable.Query#val} method. 4408 * @type {string} 4409 * @private 4410 */ 4411 this._value = null; 4412 /* PUBLIC */ 4413 /** 4414 * A string specifying how the query treats whitespace. Can be either "OR" or "AND". 4415 * @type {string} 4416 */ 4417 this.space = F.is.string(space) && (space == 'OR' || space == 'AND') ? space : 'AND'; 4418 /** 4419 * Whether or not to replace phrase connectors (+.-_) with spaces before executing the query. 4420 * @instance 4421 * @type {boolean} 4422 */ 4423 this.connectors = F.is.boolean(connectors) ? connectors : true; 4424 /** 4425 * Whether or not ignore case when matching. 4426 * @instance 4427 * @type {boolean} 4428 */ 4429 this.ignoreCase = F.is.boolean(ignoreCase) ? ignoreCase : true; 4430 /** 4431 * The left side of the query if one exists. OR takes precedence over AND. 4432 * @type {FooTable.Query} 4433 * @example <caption>
4433The below shows what is meant by the "left" side of a query</caption> 4434 * query = "Dave AND Mary" - "Dave" is the left side of the query. 4435 * query = "Dave AND Mary OR John" - "Dave and Mary" is the left side of the query. 4436 */ 4437 this.left = null; 4438 /** 4439 * The right side of the query if one exists. OR takes precedence over AND. 4440 * @type {FooTable.Query} 4441 * @example <caption>The below shows what is meant by the "right" side of a query</caption> 4442 * query = "Dave AND Mary" - "Mary" is the right side of the query. 4443 * query = "Dave AND Mary OR John" - "John" is the right side of the query. 4444 */ 4445 this.right = null; 4446 /** 4447 * The parsed parts of the query. This contains the information used to actually perform a match against a string. 4448 * @type {Array} 4449 */ 4450 this.parts = []; 4451 /** 4452 * The type of operand to apply to the results of the individual parts of the query. 4453 * @type {string} 4454 */ 4455 this.operator = null; 4456 this.val(query); 4457 }, 4458 /** 4459 * Gets or sets the value for the query. During set the value is parsed setting all properties as required. 4460 * @param {string} [value] - If supplied the value to set for this query. 4461 * @returns {(string|undefined)} 4462 */ 4463 val: function(value){ 4464 // get 4465 if (F.is.emptyString(value)) return this._value; 4466 4467 // set 4468 if (F.is.emptyString(this._original)) this._original = value; 4469 else if (this._original == value) return; 4470 4471 this._value = value; 4472 this._parse(); 4473 }, 4474 /** 4475 * Tests the supplied string against the query. 4476 * @param {string} str - The string to test. 4477 * @returns {boolean} 4478 */ 4479 match: function(str){ 4480 if (F.is.emptyString(this.operator) || this.operator === 'OR') 4481 return this._left(str, false) || this._match(str, false) || this._right(str, false); 4482 if (this.operator === 'AND') 4483 return this._left(str, true) && this._match(str, true) && this._right(str, true); 4484 }, 4485 /** 4486 * Matches this queries parts array against the supplied string. 4487 * @param {string} str - The string to test. 4488 * @param {boolean} def - The default value to return based on the operand. 4489 * @returns {boolean} 4490 * @private 4491 */ 4492 _match: function(str, def){ 4493 var self = this, result = false, empty = F.is.emptyString(str); 4494 if (F.is.emptyArray(self.parts) && self.left instanceof F.Query) return def; 4495 if (F.is.emptyArray(self.parts)) return result; 4496 if (self.space === 'OR'){ 4497 // with OR we give the str every part to test and if any match it is a success, we do exit early if a negated match occurs 4498 F.arr.each(self.parts, function(p){ 4499 if (p.empty && empty){ 4500 result = true; 4501 if (p.negate){ 4502 result = false;
4503 return result; 4504 } 4505 } else { 4506 var match = (p.exact ? F.str.containsExact : F.str.contains)(str, p.query, self.ignoreCase); 4507 if (match && !p.negate) result = true; 4508 if (match && p.negate) { 4509 result = false; 4510 return result; 4511 } 4512 } 4513 }); 4514 } else { 4515 // otherwise with AND we check until the first failure and then exit 4516 result = true; 4517 F.arr.each(self.parts, function(p){ 4518 if (p.empty){ 4519 if ((!empty && !p.negate) || (empty && p.negate)) result = false; 4520 return result; 4521 } else { 4522 var match = (p.exact ? F.str.containsExact : F.str.contains)(str, p.query, self.ignoreCase); 4523 if ((!match && !p.negate) || (match && p.negate)) result = false; 4524 return result; 4525 } 4526 }); 4527 } 4528 return result; 4529 }, 4530 /** 4531 * Matches the left side of the query if one exists with the supplied string. 4532 * @param {string} str - The string to test. 4533 * @param {boolean} def - The default value to return based on the operand. 4534 * @returns {boolean} 4535 * @private 4536 */ 4537 _left: function(str, def){ 4538 return (this.left instanceof F.Query) ? this.left.match(str) : def; 4539 }, 4540 /** 4541 * Matches the right side of the query if one exists with the supplied string. 4542 * @param {string} str - The string to test. 4543 * @param {boolean} def - The default value to return based on the operand. 4544 * @returns {boolean} 4545 * @private 4546 */ 4547 _right: function(str, def){ 4548 return (this.right instanceof F.Query) ? this.right.match(str) : def; 4549 }, 4550 /** 4551 * Parses the private {@link FooTable.Query#_value} property and populates the object. 4552 * @private 4553 */ 4554 _parse: function(){ 4555 if (F.is.emptyString(this._value)) return; 4556 // OR takes precedence so test for it first 4557 if (/\sOR\s/.test(this._value)){ 4558 // we have an OR so split the value on the first occurrence of OR to get the left and right sides of the statement 4559 this.operator = 'OR'; 4560 var or = this._value.split(/(?:\sOR\s)(.*)?/); 4561 this.left = new F.Query(or[0], this.space, this.connectors, this.ignoreCase); 4562 this.right = new F.Query(or[1], this.space, this.connectors, this.ignoreCase); 4563 } else if (/\sAND\s/.test(this._value)) { 4564 // there are no more OR's so start with AND 4565 this.operator = 'AND'; 4566 var and = this._value.split(/(?:\sAND\s)(.*)?/); 4567 this.left = new F.Query(and[0], this.space, this.connectors, this.ignoreCase); 4568 this.right = new F.Query(and[1], this.space, this.connectors, this.ignoreCase); 4569 } else { 4570 // we have no more statements to parse so set the parts array by parsing each part of the remaining query 4571 var self = this; 4572 this.parts = F.arr.map(this._value.match(/(?:[^\s"]+|"[^"]*")+/g), function(str){ 4573 return self._part(str); 4574 }); 4575 } 4576 }, 4577 /** 4578 * Parses a single part of a query into an object to use during matching. 4579 * @param {string} str - The string representation of the part. 4580 * @returns {{query: string, negate: boolean, phrase: boolean, exact: boolean}} 4581 * @private 4582 */ 4583 _part: function(str){ 4584 var p = { 4585 query: str, 4586 negate: false, 4587 phrase: false, 4588 exact: false, 4589 empty: false 4590 }; 4591 // support for NEGATE operand - (minus sign). Remove this first so we can get onto phrase checking 4592 if (F.str.startsWith(p.query, '-')){ 4593 p.query = F.str.from(p.query, '-'); 4594 p.negate = true; 4595 } 4596 // support for PHRASES (exact matches) 4597 if (/^"(.*?)"$/.test(p.query)){ // if surrounded in quotes strip them and nothing else 4598 p.query = p.query.replace(/^"(.*?)"$/, '$1'); 4599 p.phrase = true; 4600 p.exact = true; 4601 } else if (this.connectors && /(?:\w)+?([-_\+\.])(?:\w)+?/.test(p.query)) { // otherwise replace supported phrase connectors (-_+.) with spaces 4602 p.query = p.query.replace(/(?:\w)+?([-_\+\.])(?:\w)+?/g, function(match, p1){ 4603 return match.replace(p1, ' '); 4604 }); 4605 p.phrase = true; 4606 } 4607 p.empty = p.phrase && F.is.emptyString(p.query); 4608 return p; 4609 } 4610 }); 4611 4612})(FooTable); 4613(function(F){ 4614 4615 /** 4616 * The value used by the filtering component during filter operations. Must be a string and can be set using the data-filter-value attribute on the cell itself. 4617 * If this is not supplied it is set to the result of the toString method called on the value for the cell. Added by the {@link FooTable.Filtering} component. 4618 * @type {string} 4619 * @default null 4620 */ 4621 F.Cell.prototype.filterValue = null; 4622 4623 // this is used to define the filtering specific properties on cell creation 4624 F.Cell.prototype.__filtering_define__ = function(valueOrElement){ 4625 this.filterValue = this.column.filterValue.call(this.column, valueOrElement); 4626 }; 4627 4628 // this is used to update the filterValue property whenever the cell value is changed 4629 F.Cell.prototype.__filtering_val__ = function(value){ 4630 if (F.is.defined(value)){ 4631 // set only 4632 this.filterValue = this.column.filterValue.call(this.column, value); 4633 } 4634 }; 4635 4636 // overrides the public define method and replaces it with our own 4637 F.Cell.extend('define', function(valueOrElement){ 4638 this._super(valueOrElement); 4639 this.__filtering_define__(valueOrElement); 4640 }); 4641 // overrides the public val method and replaces it with our own 4642 F.Cell.extend('val', function(value, redraw, redrawSelf){ 4643 var val = this._super(value, redraw, redrawSelf); 4644 this.__filtering_val__(value); 4645 return val; 4646 }); 4647})(FooTable); 4648(function($, F){ 4649 /** 4650 * Whether or not the column can be used during filtering. Added by the {@link FooTable.Filtering} component. 4651 * @type {boolean} 4652 * @default true 4653 */ 4654 F.Column.prototype.filterable = true; 4655 4656 /** 4657 * This is supplied either the cell value or jQuery object to parse. A str
4657ing value must be returned from this method and will be used during filtering operations. 4658 * @param {(*|jQuery)} valueOrElement - The value or jQuery cell object. 4659 * @returns {string} 4660 * @this FooTable.Column 4661 */ 4662 F.Column.prototype.filterValue = function(valueOrElement){ 4663 // if we have an element or a jQuery object use jQuery to get the value 4664 if (F.is.element(valueOrElement) || F.is.jq(valueOrElement)){ 4665 var data = $(valueOrElement).data('filterValue'); 4666 return F.is.defined(data) ? ''+data : $(valueOrElement).text(); 4667 } 4668 // if options are supplied with the value 4669 if (F.is.hash(valueOrElement) && F.is.hash(valueOrElement.options)){ 4670 if (F.is.string(valueOrElement.options.filterValue)) return valueOrElement.options.filterValue; 4671 if (F.is.defined(valueOrElement.value)) valueOrElement = valueOrElement.value; 4672 } 4673 if (F.is.defined(valueOrElement) && valueOrElement != null) return valueOrElement+''; // use the native toString of the value 4674 return ''; // otherwise we have no value so return an empty string 4675 }; 4676 4677 // this is used to define the filtering specific properties on column creation 4678 F.Column.prototype.__filtering_define__ = function(definition){ 4679 this.filterable = F.is.boolean(definition.filterable) ? definition.filterable : this.filterable; 4680 this.filterValue = F.checkFnValue(this, definition.filterValue, this.filterValue); 4681 }; 4682 4683 // overrides the public define method and replaces it with our own 4684 F.Column.extend('define', function(definition){ 4685 this._super(definition); // call the base so we don't have to redefine any previously set properties 4686 this.__filtering_define__(definition); // then call our own 4687 }); 4688})(jQuery, FooTable); 4689(function(F){ 4690 /** 4691 * An object containing the filtering options for the plugin. Added by the {@link FooTable.Filtering} component. 4692 * @type {object} 4693 * @prop {boolean} enabled=false - Whether or not to allow filtering on the table. 4694 * @prop {({name: string, query: (string|FooTable.Query), columns: (Array.<string>|Array.<number>|Array.<FooTable.Column>)}|Array.<FooTable.Filter>)} filters - The filters to apply to the current {@link FooTable.Rows#array}. 4695 * @prop {number} delay=1200 - The delay in milliseconds before the query is auto applied after a change (any value equal to or less than zero will disable this). 4696 * @prop {number} min=1 - The minimum number of characters allowed in the search input before it is auto applied. 4697 * @prop {string} space="AND" - Specifies how whitespace in a filter query is handled. 4698 * @prop {string} placeholder="Search" - The string used as the placeholder for the search input. 4699 * @prop {string} dropdownTitle=null - The title to display at the top of the search input column select. 4700 * @prop {string} position="right" - The string used to specify the alignment of the search input. 4701 * @prop {string} connectors=true - Whether or not to replace phrase connectors (+.-_) with space before executing the query. 4702 * @prop {boolean} ignoreCase=true - Whether or not ignore case when matching. 4703 * @prop {boolean} exactMatch=false - Whether or not search queries are treated as phrases when matching. 4704 * @prop {boolean} focus=true - Whether or not to focus the search input after the search/clear button is clicked or after auto applying the search input query. 4705 * @prop {string} container=null - A selector specifying where to place the filtering components form, if null the form is displayed within a row in the head of the table. 4706 */ 4707 F.Defaults.prototype.filtering = { 4708 enabled: false, 4709 filters: [], 4710 delay: 1200, 4711 min: 1, 4712 space: 'AND', 4713 placeholder: 'Search', 4714 dropdownTitle: null, 4715 position: 'right', 4716 connectors: true, 4717 ignoreCase: true, 4718 exactMatch: false, 4719 focus: true, 4720 container: null 4721 }; 4722})(FooTable); 4723(function(F){ 4724 /** 4725 * Checks if the row is filtered using the supplied filters. 4726 * @this FooTable.Row 4727 * @param {Array.<FooTable.Filter>} filters - The filters to apply. 4728 * @returns {boolean} 4729 */ 4730 F.Row.prototype.filtered = function(filters){ 4731 var result = true, self = this; 4732 F.arr.each(filters, function(f){ 4733 if ((result = f.matchRow(self)) == false) return false;
4734 }); 4735 return result; 4736 }; 4737})(FooTable); 4738(function($, F){ 4739 4740 F.Sorter = F.Class.extend(/** @lends FooTable.Sorter */{ 4741 /** 4742 * The sorter object contains the column and direction to sort by. 4743 * @constructs 4744 * @extends FooTable.Class 4745 * @param {FooTable.Column} column - The column to sort. 4746 * @param {string} direction - The direction to sort by. 4747 * @returns {FooTable.Sorter} 4748 */ 4749 construct: function(column, direction){ 4750 /** 4751 * The column to sort. 4752 * @type {FooTable.Column} 4753 */ 4754 this.column = column; 4755 /** 4756 * The direction to sort by. 4757 * @type {string} 4758 */ 4759 this.direction = direction; 4760 } 4761 }); 4762 4763})(jQuery, FooTable); 4764(function ($, F) { 4765 F.Sorting = F.Component.extend(/** @lends FooTable.Sorting */{ 4766 /** 4767 * The sorting component adds a small sort button to specified column headers allowing users to sort those columns in the table. 4768 * @constructs 4769 * @extends FooTable.Component 4770 * @param {FooTable.Table} table - The parent {@link FooTable.Table} object for the component. 4771 * @returns {FooTable.Sorting} 4772 */ 4773 construct: function (table) { 4774 // call the constructor of the base class 4775 this._super(table, table.o.sorting.enabled); 4776 4777 /* PROTECTED */ 4778 /** 4779 * This provides a shortcut to the {@link FooTable.Table#options}.[sorting]{@link FooTable.Defaults#sorting} object. 4780 * @instance 4781 * @protected 4782 * @type {object} 4783 */ 4784 this.o = table.o.sorting; 4785 /** 4786 * The current sorted column. 4787 * @instance 4788 * @type {FooTable.Column} 4789 */ 4790 this.column = null; 4791 /** 4792 * Whether or not to allow sorting to occur, should be set using the {@link FooTable.Sorting#toggleAllowed} method. 4793 * @instance 4794 * @type {boolean} 4795 */ 4796 this.allowed = true; 4797 /** 4798 * The initial sort state of the table, this value is used for determining if the sorting has occurred or to reset the state to default. 4799 * @instance 4800 * @type {{isset: boolean, rows: Array.<FooTable.Row>, column: string, direction: ?string}} 4801 */ 4802 this.initial = null; 4803 }, 4804 4805 /* PROTECTED */ 4806 /** 4807 * Checks the supplied data and options for the sorting component. 4808 * @instance 4809 * @protected 4810 * @param {object} data - The jQuery data object from the parent table. 4811 * @fires FooTable.Sorting#"preinit.ft.sorting" 4812 * @this FooTable.Sorting 4813 */ 4814 preinit: function(data){ 4815 var self = this; 4816 /** 4817 * The preinit.ft.sorting event is raised before the UI is created and provides the tables jQuery data object for additional options parsing. 4818 * Calling preventDefault on this event will disable the component. 4819 * @event FooTable.Sorting#"preinit.ft.sorting" 4820 * @param {jQuery.Event} e - The jQuery.Event object for the event. 4821 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 4822 * @param {object} data - The jQuery data object of the table raising the event. 4823 */ 4824 this.ft.raise('preinit.ft.sorting', [data]).then(function(){ 4825 if (self.ft.$el.hasClass('footable-sorting')) 4826 self.enabled = true; 4827 self.enabled = F.is.boolean(data.sorting) 4828 ? data.sorting 4829 : self.enabled; 4830 if (!self.enabled) return; 4831 self.column = F.arr.first(self.ft.columns.array, function(col){ return col.sorted; }); 4832 }, function(){ 4833 self.enabled = false; 4834 }); 4835 }, 4836 /** 4837 * Initializes the sorting component for the plugin using the supplied table and options. 4838 * @instance 4839 * @protected 4840 * @fires FooTable.Sorting#"init.ft.sorting" 4841 * @this FooTable.Sorting 4842 */ 4843 init: function () { 4844 /** 4845 * The init.ft.sorting event is raised before its UI is generated. 4846 * Calling preventDefault on this event will disable the component. 4847 * @event FooTable.Sorting#"init.ft.sorting" 4848 * @param {jQuery.Event} e - The jQuery.Event object for the event. 4849 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 4850 */ 4851 var self = this; 4852 this.ft.raise('init.ft.sorting').then(function(){ 4853 if (!self.initial){ 4854 var isset = !!self.column; 4855 self.initial = { 4856 isset: isset, 4857 // grab a shallow copy of the rows array prior to sorting - allows us to reset without an initial sort 4858 rows: self.ft.rows.all.slice(0), 4859 // if there is a sorted column store its name and direction 4860 column: isset ? self.column.name : null, 4861 direction: isset ? self.column.direction : null 4862 } 4863 } 4864 F.arr.each(self.ft.columns.array, function(col){ 4865 if (col.sortable){ 4866 col.$el.addClass('footable-sortable').append($('<span/>', {'class': 'fooicon fooicon-sort'})); 4867 } 4868 }); 4869 self.ft.$el.on('click.footable', '.footable-sortable', { self: self }, self._onSortClicked); 4870 }, function(){ 4871 self.enabled = false;
4872 }); 4873 }, 4874 /** 4875 * Destroys the sorting component removing any UI generated from the table. 4876 * @instance 4877 * @protected 4878 * @fires FooTable.Sorting#"destroy.ft.sorting" 4879 */ 4880 destroy: function () { 4881 /** 4882 * The destroy.ft.sorting event is raised before its UI is removed. 4883 * Calling preventDefault on this event will prevent the component from being destroyed. 4884 * @event FooTable.Sorting#"destroy.ft.sorting" 4885 * @param {jQuery.Event} e - The jQuery.Event object for the event. 4886 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 4887 */ 4888 var self = this; 4889 this.ft.raise('destroy.ft.paging').then(function(){ 4890 self.ft.$el.off('click.footable', '.footable-sortable', self._onSortClicked); 4891 self.ft.$el.children('thead').children('tr.footable-header') 4892 .children('.footable-sortable').removeClass('footable-sortable footable-asc footable-desc') 4893 .find('span.fooicon').remove(); 4894 }); 4895 }, 4896 /** 4897 * Performs the actual sorting against the {@link FooTable.Rows#current} array. 4898 * @instance 4899 * @protected 4900 */ 4901 predraw: function () { 4902 if (!this.column) return; 4903 var self = this, col = self.column; 4904 self.ft.rows.array.sort(function (a, b) { 4905 return col.direction == 'DESC' 4906 ? col.sorter(b.cells[col.index].sortValue, a.cells[col.index].sortValue) 4907 : col.sorter(a.cells[col.index].sortValue, b.cells[col.index].sortValue); 4908 }); 4909 }, 4910 /** 4911 * Updates the sorting UI setting the state of the sort buttons. 4912 * @instance 4913 * @protected 4914 */ 4915 draw: function () { 4916 if (!this.column) return; 4917 var self = this, 4918 $sortable = self.ft.$el.find('thead > tr > .footable-sortable'), 4919 $active = self.column.$el; 4920 4921 $sortable.removeClass('footable-asc footable-desc').children('.fooicon').removeClass('fooicon-sort fooicon-sort-asc fooicon-sort-desc'); 4922 $sortable.not($active).children('.fooicon').addClass('fooicon-sort'); 4923 $active.addClass(self.column.direction == 'DESC' ? 'footable-desc' : 'footable-asc') 4924 .children('.fooicon').addClass(self.column.direction == 'DESC' ? 'fooicon-sort-desc' : 'fooicon-sort-asc'); 4925 }, 4926 4927 /* PUBLIC */ 4928 /** 4929 * Sets the sorting options and calls the {@link FooTable.Table#draw} method to perform the actual sorting. 4930 * @instance 4931 * @param {(string|number|FooTable.Column)} column - The column name, index or the actual {@link FooTable.Column} object to sort by. 4932 * @param {string} [direction="ASC"] - The direction to sort by, either ASC or DESC. 4933 * @returns {jQuery.Promise} 4934 * @fires FooTable.Sorting#"before.ft.sorting" 4935 * @fires FooTable.Sorting#"after.ft.sorting" 4936 */ 4937 sort: function(column, direction){ 4938 return this._sort(column, direction); 4939 }, 4940 /** 4941 * Toggles whether or not sorting is currently allowed. 4942 * @param {boolean} [state] - You can optionally specify the state you want it to be, if not supplied the current value is flipped. 4943 */ 4944 toggleAllowed: function(state){ 4945 state = F.is.boolean(state) ? state : !this.allowed; 4946 this.allowed = state; 4947 this.ft.$el.toggleClass('footable-sorting-disabled', !this.allowed); 4948 }, 4949 /** 4950 * Checks whether any sorting has occurred for the table. 4951 * @returns {boolean} 4952 */ 4953 hasChanged: function(){ 4954 return !(!this.initial || !this.column || 4955 (this.column.name === this.initial.column && 4956 (this.column.direction === this.initial.direction || (this.initial.direction === null && this.column.direction === 'ASC'))) 4957 ); 4958 }, 4959 /** 4960 * Resets the table sorting to the initial state recorded in the components init method. 4961 */ 4962 reset: function(){ 4963 if (!!this.initial){ 4964 if (this.initial.isset){ 4965 // if the initial value specified a column, sort by it 4966 this.sort(this.initial.column, this.initial.direction); 4967 } else { 4968 // if there was no initial column then we need to reset the rows to there original order 4969 if (!!this.column){ 4970 // if there is a currently sorted column remove the asc/desc classes and set it to null. 4971 this.column.$el.removeClass('footable-asc footable-desc'); 4972 this.column = null; 4973 } 4974 // replace the current all rows array with the one stored in the initial value 4975 this.ft.rows.all = this.initial.rows; 4976 // force the table to redraw itself using the updated rows array 4977 this.ft.draw(); 4978 } 4979 } 4980 }, 4981 4982 /* PRIVATE */ 4983 /** 4984 * Performs the required steps to handle sorting including the raising of the {@link FooTable.Sorting#"before.ft.sorting"} and {@link FooTable.Sorting#"after.ft.sorting"} events. 4985 * @instance 4986 * @private 4987 * @param {(string|number|FooTable.Column)} column - The column name, index or the actual {@link FooTable.Column} object to sort by. 4988 * @param {string} [direction="ASC"] - The direction to sort by, either ASC or DESC. 4989 * @returns {jQuery.Promise} 4990 * @fires FooTable.Sorting#"before.ft.sorting" 4991 * @fires FooTable.Sorting#"after.ft.sorting" 4992 */ 4993 _sort: function(column, direction){ 4994 if (!this.allowed) return $.Deferred().reject('sorting disabled'); 4995 var self = this; 4996 var sorter = new F.Sorter(self.ft.columns.get(column), F.Sorting.dir(direction)); 4997 /** 4998 * The before.ft.sorting event is raised before a sort is applied and allows listeners to modify the sorter or cancel it completely by calling preventDefault on the jQuery.Event object. 4999 * @event FooTable.Sorting#"before.ft.sorting" 5000 * @param {jQuery.Event} e - The jQuery.Event object for the event. 5001 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 5002 * @param {FooTable.Sorter} sorter - The sorter that is about to be applied. 5003 */ 5004 return self.ft.raise('before.ft.sorting', [sorter]).then(function(){ 5005 F.arr.each(self.ft.columns.array, function(col){ 5006 if (col != self.column) col.direction = null; 5007 }); 5008 self.column = self.ft.columns.get(sorter.column); 5009 if (self.column) self.column.direction = F.Sorting.dir(sorter.direction); 5010 return self.ft.draw().then(function(){ 5011 /** 5012 * The after.ft.sorting event is raised after a sorter has been applied. 5013 * @event FooTable.Sorting#"after.ft.sorting" 5014 * @param {jQuery.Event} e - The jQuery.Event object for the event. 5015 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 5016 * @param {FooTable.Sorter} sorter - The sorter that has been applied. 5017 */ 5018 self.ft.raise('after.ft.sorting', [sorter]); 5019 }); 5020 }); 5021 }, 5022 /** 5023 * Handles the sort button clicked event. 5024 * @instance 5025 * @private 5026 * @param {jQuery.Event} e - The event object for the event. 5027 */ 5028 _onSortClicked: function (e) { 5029 var self = e.data.self, $header = $(this).closest('th,td'), 5030 direction = $header.is('.footable-asc, .footable-desc') 5031 ? ($header.hasClass('footable-desc') ? 'ASC' : 'DESC') 5032 : 'ASC'; 5033 self._sort($header.index(), direction); 5034 } 5035 }); 5036 5037 /** 5038 * Checks the supplied string is a valid direction and if not returns ASC as default. 5039 * @static 5040 * @protected 5041 * @param {string} str - The string to check. 5042 */ 5043 F.Sorting.dir = function(str){ 5044 return F.is.string(str) && (str == 'ASC' || str == 'DESC') ? str : 'ASC'; 5045 }; 5046 5047 F.components.register('sorting', F.Sorting, 600); 5048 5049})(jQuery, FooTable); 5050(function(F){ 5051 5052 /** 5053 * The value used by the sorting component during sort operations. Can be set using the data-sort-value attribute on the cell itself. 5054 * If this is not supplied it is set to the result of the toString method called on the value for the cell. Added by the {@link FooTable.Sorting} component. 5055 * @type {string} 5056 * @default null 5057 */ 5058 F.Cell.prototype.sortValue = null; 5059 5060 // this is used to define the sorting specific properties on cell creation 5061 F.Cell.prototype.__sorting_define__ = function(valueOrElement){ 5062 this.sortValue = this.column.sortValue.call(this.column, valueOrElement); 5063 }; 5064 5065 // this is used to update the sortValue property whenever the cell value is changed 5066 F.Cell.prototype.__sorting_val__ = function(value){ 5067 if (F.is.defined(value)){ 5068 // set only 5069 this.sortValue = this.column.sortValue.call(this.column, value); 5070 } 5071 }; 5072 5073 // overrides the public define method and replaces it with our own 5074 F.Cell.extend('define', function(valueOrElement){ 5075 this._super(valueOrElement); 5076 this.__sorting_define__(valueOrElement); 5077 }); 5078 // overrides the public val method and replaces it with our own 5079 F.Cell.extend('val', function(value, redraw, redrawSelf){ 5080 var val = this._super(value, redraw, redrawSelf); 5081 this.__sorting_val__(value); 5082 return val; 5083 }); 5084})(FooTable); 5085(function($, F){ 5086 /** 5087 * The direction to sort if the {@link FooTable.Column#sorted} property is set to true. Can be "ASC", "DESC" or NULL. Added by the {@link FooTable.Sorting} component. 5088 * @type {string} 5089 * @default null 5090 */ 5091 F.Column.prototype.direction = null; 5092 /** 5093 * Whether or not the column can be sorted. Added by the {@link FooTable.Sorting} component. 5094 * @type {boolean} 5095 * @default true 5096 */ 5097 F.Column.prototype.sortable = true; 5098 /** 5099 * Whether or not the column is sorted. Added by the {@link FooTable.Sorting} component. 5100 * @type {boolean} 5101 * @default false 5102 */ 5103 F.Column.prototype.sorted = false;
5104 5105 /** 5106 * This is supplied two values from the column for a comparison to be made and the result returned. Added by the {@link FooTable.Sorting} component. 5107 * @param {*} a - The first value to be compared. 5108 * @param {*} b - The second value to compare to the first. 5109 * @returns {number} 5110 * @example <caption>This example shows using pseudo code what a sort function would look like.</caption> 5111 * "sorter": function(a, b){ 5112 * if (a is less than b by some ordering criterion) { 5113 * return -1; 5114 * } 5115 * if (a is greater than b by the ordering criterion) { 5116 * return 1; 5117 * } 5118 * // a must be equal to b 5119 * return 0; 5120 * } 5121 */ 5122 F.Column.prototype.sorter = function(a, b){ 5123 if (typeof a === 'string') a = a.toLowerCase(); 5124 if (typeof b === 'string') b = b.toLowerCase(); 5125 if (a === b) return 0; 5126 if (a < b) return -1; 5127 return 1; 5128 }; 5129 5130 /** 5131 * This is supplied either the cell value or jQuery object to parse. A value must be returned from this method and will be used during sorting operations. 5132 * @param {(*|jQuery)} valueOrElement - The value or jQuery cell object. 5133 * @returns {*} 5134 * @this FooTable.Column 5135 */ 5136 F.Column.prototype.sortValue = function(valueOrElement){ 5137 // if we have an element or a jQuery object use jQuery to get the value 5138 if (F.is.element(valueOrElement) || F.is.jq(valueOrElement)){ 5139 var data = $(valueOrElement).data('sortValue'); 5140 return F.is.defined(data) ? data : this.parser(valueOrElement); 5141 } 5142 // if options are supplied with the value 5143 if (F.is.hash(valueOrElement) && F.is.hash(valueOrElement.options)){ 5144 if (F.is.string(valueOrElement.options.sortValue)) return valueOrElement.options.sortValue; 5145 if (F.is.defined(valueOrElement.value)) valueOrElement = valueOrElement.value; 5146 } 5147 if (F.is.defined(valueOrElement) && valueOrElement != null) return valueOrElement; 5148 return null; 5149 }; 5150 5151 // this is used to define the sorting specific properties on column creation 5152 F.Column.prototype.__sorting_define__ = function(definition){ 5153 this.sorter = F.checkFnValue(this, definition.sorter, this.sorter); 5154 this.direction = F.is.type(definition.direction, 'string') ? F.Sorting.dir(definition.direction) : null; 5155 this.sortable = F.is.boolean(definition.sortable) ? definition.sortable : true; 5156 this.sorted = F.is.boolean(definition.sorted) ? definition.sorted : false; 5157 this.sortValue = F.checkFnValue(this, definition.sortValue, this.sortValue); 5158 }; 5159 5160 // overrides the public define method and replaces it with our own 5161 F.Column.extend('define', function(definition){ 5162 this._super(definition); 5163 this.__sorting_define__(definition); 5164 }); 5165 5166})(jQuery, FooTable); 5167(function(F){ 5168 /** 5169 * An object containing the sorting options for the plugin. Added by the {@link FooTable.Sorting} component. 5170 * @type {object} 5171 * @prop {boolean} enabled=false - Whether or not to allow sorting on the table. 5172 */ 5173 F.Defaults.prototype.sorting = { 5174 enabled: false 5175 }; 5176})(FooTable); 5177(function($, F){ 5178 5179 F.HTMLColumn.extend('__sorting_define__', function(definition){ 5180 this._super(definition); 5181 this.sortUse = F.is.string(definition.sortUse) && $.inArray(definition.sortUse, ['html','text']) !== -1 ? definition.sortUse : 'html'; 5182 }); 5183 5184 /** 5185 * This is supplied either the cell value or jQuery object to parse. A value must be returned from this method and will be used during sorting operations. 5186 * @param {(*|jQuery)} valueOrElement - The value or jQuery cell object. 5187 * @returns {*} 5188 * @this FooTable.HTMLColumn 5189 */ 5190 F.HTMLColumn.prototype.sortValue = function(valueOrElement){ 5191 // if we have an element or a jQuery object use jQuery to get the data value or pass it off to the parser 5192 if (F.is.element(valueOrElement) || F.is.jq(valueOrElement)){ 5193 var data = $(valueOrElement).data('sortValue'); 5194 return F.is.defined(data) ? data : this.parser(valueOrElement); 5195 } 5196 // if options are supplied with the value 5197 if (F.is.hash(valueOrElement) && F.is.hash(valueOrElement.options)){ 5198 if (F.is.string(valueOrElement.options.sortValue)) return valueOrElement.options.sortValue; 5199 if (F.is.defined(valueOrElement.value)) valueOrElement = valueOrElement.value; 5200 } 5201 if (F.is.defined(valueOrElement) && valueOrElement != null) return valueOrElement; 5202 return null; 5203 }; 5204 5205})(jQuery, FooTable); 5206(function($, F){ 5207 5208 /** 5209 * This is supplied either the cell value or jQuery object to parse. A value must be returned from this method and will be used during sorting operations. 5210 * @param {(*|jQuery)} valueOrElement - The value or jQuery cell object. 5211 * @returns {*} 5212 */ 5213 F.NumberColumn.prototype.sortValue = function(valueOrElement){ 5214 // if we have an element or a jQuery object use jQuery to get the data value or pass it off to the parser
5215 if (F.is.element(valueOrElement) || F.is.jq(valueOrElement)){ 5216 var data = $(valueOrElement).data('sortValue'); 5217 return F.is.number(data) ? data : this.parser(valueOrElement); 5218 } 5219 // if options are supplied with the value 5220 if (F.is.hash(valueOrElement) && F.is.hash(valueOrElement.options)){ 5221 if (F.is.string(valueOrElement.options.sortValue)) return this.parser(valueOrElement); 5222 if (F.is.number(valueOrElement.options.sortValue)) return valueOrElement.options.sortValue; 5223 if (F.is.number(valueOrElement.value)) return valueOrElement.value; 5224 } 5225 if (F.is.string(valueOrElement)) return this.parser(valueOrElement); 5226 if (F.is.number(valueOrElement)) return valueOrElement; 5227 return null; 5228 }; 5229 5230})(jQuery, FooTable); 5231(function(F){ 5232 /** 5233 * Sort the table using the specified column and direction. Added by the {@link FooTable.Sorting} component. 5234 * @instance 5235 * @param {(string|number|FooTable.Column)} column - The column name, index or the actual {@link FooTable.Column} object to sort by. 5236 * @param {string} [direction="ASC"] - The direction to sort by, either ASC or DESC. 5237 * @returns {jQuery.Promise} 5238 * @fires FooTable.Sorting#"change.ft.sorting" 5239 * @fires FooTable.Sorting#"changed.ft.sorting" 5240 * @see FooTable.Sorting#sort 5241 */ 5242 F.Table.prototype.sort = function(column, direction){ 5243 return this.use(F.Sorting).sort(column, direction); 5244 }; 5245})(FooTable); 5246(function($, F){ 5247 5248 F.Pager = F.Class.extend(/** @lends FooTable.Pager */{ 5249 /** 5250 * The pager object contains the page number and direction to page to. 5251 * @constructs 5252 * @extends FooTable.Class 5253 * @param {number} total - The total number of pages available. 5254 * @param {number} current - The current page number. 5255 * @param {number} size - The number of rows per page. 5256 * @param {number} page - The page number to goto. 5257 * @param {boolean} forward - A boolean indicating the direction of paging, TRUE = forward, FALSE = back. 5258 * @returns {FooTable.Pager} 5259 */ 5260 construct: function(total, current, size, page, forward){ 5261 /** 5262 * The total number of pages available. 5263 * @type {number} 5264 */ 5265 this.total = total; 5266 /** 5267 * The current page number. 5268 * @type {number} 5269 */ 5270 this.current = current; 5271 /** 5272 * The number of rows per page. 5273 * @type {number} 5274 */ 5275 this.size = size; 5276 /** 5277 * The page number to goto. 5278 * @type {number} 5279 */ 5280 this.page = page; 5281 /** 5282 * A boolean indicating the direction of paging, TRUE = forward, FALSE = back. 5283 * @type {boolean} 5284 */ 5285 this.forward = forward; 5286 } 5287 }); 5288 5289})(jQuery, FooTable); 5290(function($, F){ 5291 F.Paging = F.Component.extend(/** @lends FooTable.Paging */{ 5292 /** 5293 * The paging component adds a pagination control to the table allowing users to navigate table rows via pages. 5294 * @constructs 5295 * @extends FooTable.Component 5296 * @param {FooTable.Table} table - The parent {@link FooTable.Table} object for the component. 5297 * @returns {FooTable.Filtering} 5298 */ 5299 construct: function(table){ 5300 // call the base constructor 5301 this._super(table, table.o.paging.enabled); 5302 5303 /* PROTECTED */ 5304 /** 5305 * An object containing the strings used by the paging buttons. 5306 * @type {{ first: string, prev: string, next: string, last: string }} 5307 */ 5308 this.strings = table.o.paging.strings; 5309 5310 /* PUBLIC */ 5311 /** 5312 * The current page number to display. 5313 * @instance 5314 * @type {number} 5315 */ 5316 this.current = table.o.paging.current; 5317 /** 5318 * The number of rows to display per page. 5319 * @instance 5320 * @type {number} 5321 */ 5322 this.size = table.o.paging.size; 5323 /** 5324 * The maximum number of page links to display at once. 5325 * @instance 5326 * @type {number} 5327 */ 5328 this.limit = table.o.paging.limit; 5329 /** 5330 * The position of the pagination control within the paging rows cell. 5331 * @instance 5332 * @type {string} 5333 */ 5334 this.position = table.o.paging.position; 5335 /** 5336 * The format string used to generate the text displayed under the pagination control. 5337 * @instance 5338 * @type {string} 5339 */ 5340 this.countFormat = table.o.paging.countFormat; 5341 /** 5342 * A selector specifying where to place the paging components UI, if null the UI is displayed within a row in the foot of the table. 5343 * @instance 5344 * @type {string} 5345 */ 5346 this.container = table.o.paging.container; 5347 /** 5348 * The total number of pages. 5349 * @instance 5350 * @type {number} 5351 */ 5352 this.total = -1; 5353 /** 5354 * The number of rows in the {@link FooTable.Rows#array} before paging is applied. 5355 * @instance 5356 * @type {number} 5357 */ 5358 this.totalRows = 0; 5359 /** 5360 * A number indicating the previous page displayed. 5361 * @instance 5362 * @type {number} 5363 */ 5364 this.previous = -1; 5365 /** 5366 * The count string generated using the {@link FooTable.Filtering#countFormat} option. This value is only set after the first call to the {@link FooTable.Filtering#predraw} method. 5367 * @instance 5368 * @type {string} 5369 */ 5370 this.formattedCount = null; 5371 /** 5372 * The jQuery object of the element containing the entire paging UI. 5373 * @instance 5374 * @type {jQuery} 5375 */ 5376 this.$container = null; 5377 /** 5378 * The jQuery object of the element wrapping all the paging UI elements. 5379 * @instance 5380 * @type {jQuery} 5381 */ 5382 this.$wrapper = null; 5383 /** + 5384 * The jQuery row object that contains all the paging specific elements. 5385 * @instance 5386 * @type {jQuery} 5387 */ 5388 this.$row = null; 5389 /** 5390 * The jQuery cell object that contains the pagination control and total count. 5391 * @instance 5392 * @type {jQuery} 5393 */ 5394 this.$cell = null; 5395 /** 5396 * The jQuery object that contains the links for the pagination control. 5397 * @instance 5398 * @type {jQuery} 5399 */ 5400 this.$pagination = null; 5401 /** 5402 * The jQuery object that contains the row count. 5403 * @instance 5404 * @type {jQuery} 5405 */ 5406 this.$count = null; 5407 /** 5408 * Whether or not the pagination row is detached from the table. 5409 * @instance 5410 * @type {boolean} 5411 */ 5412 this.detached = true; 5413 5414 /* PRIVATE */ 5415 /** 5416 * Used to hold the number of page links created. 5417 * @instance 5418 * @type {number} 5419 * @private 5420 */ 5421 this._createdLinks = 0; 5422 }, 5423 5424 /* PROTECTED */ 5425 /** 5426 * Checks the supplied data and options for the paging component. 5427 * @instance 5428 * @protected 5429 * @param {object} data - The jQuery data object from the parent table. 5430 * @fires FooTable.Paging#"preinit.ft.paging" 5431 */ 5432 preinit: function(data){ 5433 var self = this; 5434 /** 5435 * The preinit.ft.paging event is raised before the UI is created and provides the tables jQuery data object for additional options parsing. 5436 * Calling preventDefault on this event will disable the component. 5437 * @event FooTable.Paging#"preinit.ft.paging" 5438 * @param {jQuery.Event} e - The jQuery.Event object for the event. 5439 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 5440 * @param {object} data - The jQuery data object of the table raising the event. 5441 */ 5442 this.ft.raise('preinit.ft.paging', [data]).then(function(){ 5443 if (self.ft.$el.hasClass('footable-paging')) 5444 self.enabled = true; 5445 self.enabled = F.is.boolean(data.paging) 5446 ? data.paging 5447 : self.enabled; 5448 5449 if (!self.enabled) return; 5450 5451 self.size = F.is.number(data.pagingSize) 5452 ? data.pagingSize 5453 : self.size; 5454 5455 self.current = F.is.number(data.pagingCurrent) 5456 ? data.pagingCurrent 5457 : self.current; 5458 5459 self.limit = F.is.number(data.pagingLimit) 5460 ? data.pagingLimit 5461 : self.limit; 5462 5463 if (self.ft.$el.hasClass('footable-paging-left')) 5464 self.position = 'left'; 5465 if (self.ft.$el.hasClass('footable-paging-center')) 5466 self.position = 'center'; 5467 if (self.ft.$el.hasClass('footable-paging-right')) 5468 self.position = 'right'; 5469 5470 self.position = F.is.string(data.pagingPosition) 5471 ? data.pagingPosition 5472 : self.position; 5473 5474 self.countFormat = F.is.string(data.pagingCountFormat) 5475 ? data.pagingCountFormat 5476 : self.countFormat; 5477 5478 self.container = F.is.string(data.pagingContainer) 5479 ? data.pagingContainer 5480 : self.container; 5481
5482 self.total = Math.ceil(self.ft.rows.all.length / self.size); 5483 }, function(){ 5484 self.enabled = false; 5485 }); 5486 }, 5487 /** 5488 * Initializes the paging component for the plugin using the supplied table and options. 5489 * @instance 5490 * @protected 5491 * @fires FooTable.Paging#"init.ft.paging" 5492 */ 5493 init: function(){ 5494 /** 5495 * The init.ft.paging event is raised before its UI is generated. 5496 * Calling preventDefault on this event will disable the component. 5497 * @event FooTable.Paging#"init.ft.paging" 5498 * @param {jQuery.Event} e - The jQuery.Event object for the event. 5499 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 5500 */ 5501 var self = this; 5502 this.ft.raise('init.ft.paging').then(function(){ 5503 self.$create(); 5504 }, function(){ 5505 self.enabled = false; 5506 }); 5507 }, 5508 /** 5509 * Destroys the paging component removing any UI generated from the table. 5510 * @instance 5511 * @protected 5512 * @fires FooTable.Paging#"destroy.ft.paging" 5513 */ 5514 destroy: function () { 5515 /** 5516 * The destroy.ft.paging event is raised before its UI is removed. 5517 * Calling preventDefault on this event will prevent the component from being destroyed. 5518 * @event FooTable.Paging#"destroy.ft.paging" 5519 * @param {jQuery.Event} e - The jQuery.Event object for the event. 5520 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 5521 */ 5522 var self = this; 5523 this.ft.raise('destroy.ft.paging').then(function(){ 5524 self.ft.$el.removeClass('footable-paging') 5525 .find('tfoot > tr.footable-paging').remove(); 5526 self.detached = true; 5527 self._createdLinks = 0; 5528 }); 5529 }, 5530 /** 5531 * Performs the actual paging against the {@link FooTable.Rows#current} array removing all rows that are not on the current visible page. 5532 * @instance 5533 * @protected 5534 */ 5535 predraw: function(){ 5536 this.total = Math.ceil(this.ft.rows.array.length / this.size); 5537 this.current = this.current > this.total ? this.total : (this.current < 1 ? 1 : this.current); 5538 this.totalRows = this.ft.rows.array.length; 5539 if (this.totalRows > this.size){ 5540 this.ft.rows.array = this.ft.rows.array.splice((this.current - 1) * this.size, this.size); 5541 } 5542 this.formattedCount = this.format(this.countFormat); 5543 }, 5544 /** 5545 * Updates the paging UI setting the state of the pagination control. 5546 * @instance 5547 * @protected 5548 */ 5549 draw: function(){ 5550 if (this.total <= 1){ 5551 if (!this.detached){ 5552 if (this.$row){ 5553 this.$row.detach(); 5554 } else { 5555 this.$wrapper.detach(); 5556 } 5557 this.detached = true; 5558 } 5559 } else { 5560 if (this.detached){ 5561 if (this.$row){ 5562 var $tfoot = this.ft.$el.children('tfoot'); 5563 if ($tfoot.length == 0){ 5564 $tfoot = $('<tfoot/>'); 5565 this.ft.$el.append($tfoot); 5566 } 5567 this.$row.appendTo($tfoot); 5568 } else { 5569 this.$wrapper.appendTo(this.$container); 5570 } 5571 this.detached = false; 5572 } 5573 if (F.is.jq(this.$cell)){ 5574 this.$cell.attr('colspan', this.ft.columns.visibleColspan); 5575 } 5576 this._createLinks(); 5577 this._setVisible(this.current, this.current > this.previous); 5578 this._setNavigation(true); 5579 this.$count.text(this.formattedCount); 5580 } 5581 }, 5582 /** 5583 * Creates the paging UI from the current options setting the various jQuery properties of this component. 5584 * @instance 5585 * @protected 5586 */ 5587 $create: function(){ 5588 this._createdLinks = 0; 5589 var position = 'footable-paging-center'; 5590 switch (this.position){ 5591 case 'left': position = 'footable-paging-left'; break; 5592 case 'right': position = 'footable-paging-right'; break; 5593 } 5594 this.ft.$el.addClass('footable-paging').addClass(position); 5595 5596 this.$container = this.container === null ? null : $(this.container).first(); 5597 if (!F.is.jq(this.$container)){ 5598 var $tfoot = this.ft.$el.children('tfoot'); 5599 if ($tfoot.length == 0){ 5600 $tfoot = $('<tfoot/>'); 5601 this.ft.$el.append($tfoot); 5602 } 5603 // add it to a row and then populate it with the search input and column selector dropdown. 5604 this.$row = $('<tr/>', {'class': 'footable-paging'}).prependTo($tfoot); 5605 this.$container = this.$cell = $('<td/>').attr('colspan', this.ft.columns.visibleColspan).appendTo(this.$row); 5606 } else { 5607 this.$container.addClass('footable-paging-external').addClass(position); 5608 }
5609 this.$wrapper = $('<div/>', {'class': 'footable-pagination-wrapper'}).appendTo(this.$container); 5610 this.$pagination = $('<ul/>', { 'class': 'pagination' }).on('click.footable', 'a.footable-page-link', { self: this }, this._onPageClicked); 5611 this.$count = $('<span/>', { 'class': 'label label-default' }); 5612 this.$wrapper.append(this.$pagination, $('<div/>', {'class': 'divider'}), this.$count); 5613 this.detached = false; 5614 }, 5615 5616 /* PUBLIC */ 5617 /** 5618 * @summary Uses the supplied format string and replaces the placeholder strings with the current values. 5619 * @description This method is used to generate the short description label for the pagination control. i.e. Showing X of Y records. The placeholders for this string are the following: 5620 * * {CP} - The current page number. 5621 * * {TP} - The total number of pages. 5622 * * {PF} - The first row of the current page. 5623 * * {PL} - The last row of the current page. 5624 * * {TR} - The total rows available. 5625 * These placeholders can be supplied in a string like; "Showing {PF} to {PL} of {TR} rows." 5626 * @param {string} formatString - The string to be formatted with the paging specific variables. 5627 * @returns {string} 5628 */ 5629 format: function(formatString){ 5630 var firstRow = (this.size * (this.current - 1)) + 1, 5631 lastRow = this.size * this.current; 5632 if (this.ft.rows.array.length == 0){ 5633 firstRow = 0; 5634 lastRow = 0; 5635 } else { 5636 lastRow = lastRow > this.totalRows ? this.totalRows : lastRow; 5637 } 5638 return formatString.replace(/\{CP}/g, this.current) 5639 .replace(/\{TP}/g, this.total) 5640 .replace(/\{PF}/g, firstRow) 5641 .replace(/\{PL}/g, lastRow) 5642 .replace(/\{TR}/g, this.totalRows); 5643 }, 5644 /** 5645 * Pages to the first page. 5646 * @instance 5647 * @returns {jQuery.Promise} 5648 * @fires FooTable.Paging#"before.ft.paging" 5649 * @fires FooTable.Paging#"after.ft.paging" 5650 */ 5651 first: function(){ 5652 return this._set(1); 5653 }, 5654 /** 5655 * Pages to the previous page. 5656 * @instance 5657 * @returns {jQuery.Promise} 5658 * @fires FooTable.Paging#"before.ft.paging" 5659 * @fires FooTable.Paging#"after.ft.paging" 5660 */ 5661 prev: function(){ 5662 return this._set(this.current - 1 > 0 ? this.current - 1 : 1); 5663 }, 5664 /** 5665 * Pages to the next page. 5666 * @instance 5667 * @returns {jQuery.Promise} 5668 * @fires FooTable.Paging#"before.ft.paging" 5669 * @fires FooTable.Paging#"after.ft.paging" 5670 */ 5671 next: function(){ 5672 return this._set(this.current + 1 < this.total ? this.current + 1 : this.total); 5673 }, 5674 /** 5675 * Pages to the last page. 5676 * @instance 5677 * @returns {jQuery.Promise} 5678 * @fires FooTable.Paging#"before.ft.paging" 5679 * @fires FooTable.Paging#"after.ft.paging" 5680 */ 5681 last: function(){ 5682 return this._set(this.total); 5683 }, 5684 /** 5685 * Pages to the specified page. 5686 * @instance 5687 * @param {number} page - The page number to go to. 5688 * @returns {jQuery.Promise} 5689 * @fires FooTable.Paging#"before.ft.paging" 5690 * @fires FooTable.Paging#"after.ft.paging" 5691 */ 5692 goto: function(page){ 5693 return this._set(page > this.total ? this.total : (page < 1 ? 1 : page)); 5694 }, 5695 /** 5696 * Shows the previous X number of pages in the pagination control where X is the value set by the {@link FooTable.Defaults#paging} - limit option value. 5697 * @instance 5698 */ 5699 prevPages: function(){ 5700 var page = this.$pagination.children('li.footable-page.visible:first').data('page') - 1; 5701 this._setVisible(page, true); 5702 this._setNavigation(false); 5703 }, 5704 /** 5705 * Shows the next X number of pages in the pagination control where X is the value set by the {@link FooTable.Defaults#paging} - limit option value. 5706 * @instance 5707 */ 5708 nextPages: function(){ 5709 var page = this.$pagination.children('li.footable-page.visible:last').data('page') + 1; 5710 this._setVisible(page, false); 5711 this._setNavigation(false); 5712 }, 5713 /** 5714 * Gets or sets the current page size. 5715 * @instance 5716 * @param {(number|string)} [value] - The new page size to use, this value is supplied to `parseInt` so strings can be used. If not supplied or an invalid valid the current page size is returned. 5717 * @returns {(number|undefined)} 5718 */ 5719 pageSize: function(value){ 5720 value = parseInt(value); 5721 if (isNaN(value)){ 5722 return this.size; 5723 } 5724 this.size = value;
5725 this.total = Math.ceil(this.ft.rows.all.length / this.size); 5726 if (F.is.jq(this.$wrapper)){ 5727 if (this.$container.is("td")){ 5728 this.$row.remove(); 5729 } else { 5730 this.$wrapper.remove(); 5731 } 5732 } 5733 this.$create(); 5734 this.ft.draw(); 5735 }, 5736 5737 /* PRIVATE */ 5738 /** 5739 * Performs the required steps to handle paging including the raising of the {@link FooTable.Paging#"before.ft.paging"} and {@link FooTable.Paging#"after.ft.paging"} events. 5740 * @instance 5741 * @private 5742 * @param {number} page - The page to set. 5743 * @returns {jQuery.Promise} 5744 * @fires FooTable.Paging#"before.ft.paging" 5745 * @fires FooTable.Paging#"after.ft.paging" 5746 */ 5747 _set: function(page){ 5748 var self = this, 5749 pager = new F.Pager(self.total, self.current, self.size, page, page > self.current); 5750 /** 5751 * The before.ft.paging event is raised before a sort is applied and allows listeners to modify the pager or cancel it completely by calling preventDefault on the jQuery.Event object. 5752 * @event FooTable.Paging#"before.ft.paging" 5753 * @param {jQuery.Event} e - The jQuery.Event object for the event. 5754 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 5755 * @param {FooTable.Pager} pager - The pager that is about to be applied. 5756 */ 5757 return self.ft.raise('before.ft.paging', [pager]).then(function(){ 5758 pager.page = pager.page > pager.total ? pager.total : pager.page; 5759 pager.page = pager.page < 1 ? 1 : pager.page; 5760 if (self.current == page) return $.when(); 5761 self.previous = self.current; 5762 self.current = pager.page; 5763 return self.ft.draw().then(function(){ 5764 /** 5765 * The after.ft.paging event is raised after a pager has been applied. 5766 * @event FooTable.Paging#"after.ft.paging" 5767 * @param {jQuery.Event} e - The jQuery.Event object for the event. 5768 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 5769 * @param {FooTable.Pager} pager - The pager that has been applied. 5770 */ 5771 self.ft.raise('after.ft.paging', [pager]); 5772 }); 5773 }); 5774 }, 5775 /** 5776 * Creates the pagination links using the current state of the plugin. If the total number of pages is the same as 5777 * the last time this function was executed it does nothing. 5778 * @instance 5779 * @private 5780 */ 5781 _createLinks: function(){ 5782 if (this._createdLinks === this.total) return; 5783 var self = this, 5784 multiple = self.total > 1, 5785 link = function(attr, html, klass){ 5786 return $('<li/>', { 5787 'class': klass 5788 }).attr('data-page', attr) 5789 .append($('<a/>', { 5790 'class': 'footable-page-link', 5791 href: '#' 5792 }).data('page', attr).html(html)); 5793 }; 5794 self.$pagination.empty(); 5795 if (multiple) { 5796 self.$pagination.append(link('first', self.strings.first, 'footable-page-nav')); 5797 self.$pagination.append(link('prev', self.strings.prev, 'footable-page-nav')); 5798 if (self.limit > 0 && self.limit < self.total){ 5799 self.$pagination.append(link('prev-limit', self.strings.prevPages, 'footable-page-nav')); 5800 } 5801 } 5802 for (var i = 0, $li; i < self.total; i++){ 5803 $li = link(i + 1, i + 1, 'footable-page'); 5804 self.$pagination.append($li); 5805 } 5806 if (multiple){ 5807 if (self.limit > 0 && self.limit < self.total){ 5808 self.$pagination.append(link('next-limit', self.strings.nextPages, 'footable-page-nav')); 5809 } 5810 self.$pagination.append(link('next', self.strings.next, 'footable-page-nav')); 5811 self.$pagination.append(link('last', self.strings.last, 'footable-page-nav')); 5812 } 5813 self._createdLinks = self.total; 5814 }, 5815 /** 5816 * Sets the state for the navigation links of the pagination control and optionally sets the active class state on the current page link. 5817 * @instance 5818 * @private 5819 * @param {boolean} active - Whether or not to set the active class state on the individual page links. 5820 */ 5821 _setNavigation: function(active){ 5822 if (this.current == 1) { 5823 this.$pagination.children('li[data-page="first"],li[data-page="prev"]').addClass('disabled'); 5824 } else { 5825 this.$pagination.children('li[data-page="first"],li[data-page="prev"]').removeClass('disabled'); 5826 } 5827 5828 if (this.current == this.total) { 5829 this.$pagination.children('li[data-page="next"],li[data-page="last"]').addClass('disabled'); 5830 } else { 5831 this.$pagination.children('li[data-page="next"],li[data-page="last"]').removeClass('disabled'); 5832 } 5833 5834 if ((this.$pagination.children('li.footable-page.visible:first').data('page') || 1) == 1) { 5835 this.$pagination.children('li[data-page="prev-limit"]').addClass('disabled'); 5836 } else { 5837 this.$pagination.children('li[data-page="prev-limit"]').removeClass('disabled'); 5838 } 5839 5840 if ((this.$pagination.children('li.footable-page.visible:last').data('page') || this.limit) == this.total) { 5841 this.$pagination.children('li[data-page="next-limit"]').addClass('disabled'); 5842 } else { 5843 this.$pagination.children('li[data-page="next-limit"]').removeClass('disabled'); 5844 } 5845 5846 if (this.limit > 0 && this.total < this.limit){ 5847 this.$pagination.children('li[data-page="prev-limit"],li[data-page="next-limit"]').css('display', 'none'); 5848 } else { 5849 this.$pagination.children('li[data-page="prev-limit"],li[data-page="next-limit"]').css('display', ''); 5850 } 5851
5852 if (active){ 5853 this.$pagination.children('li.footable-page').removeClass('active').filter('li[data-page="' + this.current + '"]').addClass('active'); 5854 } 5855 }, 5856 /** 5857 * Sets the visible page using the supplied parameters. 5858 * @instance 5859 * @private 5860 * @param {number} page - The page to make visible. 5861 * @param {boolean} right - If set to true the supplied page will be the right most visible pagination link. 5862 */ 5863 _setVisible: function(page, right){ 5864 if (this.limit > 0 && this.total > this.limit){ 5865 if (!this.$pagination.children('li.footable-page[data-page="'+page+'"]').hasClass('visible')){ 5866 var start = 0, end = 0; 5867 if (right == true){ 5868 end = page > this.total ? this.total : page; 5869 start = end - this.limit; 5870 } else { 5871 start = page < 1 ? 0 : page - 1; 5872 end = start + this.limit; 5873 } 5874 if (start < 0){ 5875 start = 0; 5876 end = this.limit > this.total ? this.total : this.limit; 5877 } 5878 if (end > this.total){ 5879 end = this.total; 5880 start = this.total - this.limit < 0 ? 0 : this.total - this.limit; 5881 } 5882 this.$pagination.children('li.footable-page').removeClass('visible').slice(start, end).addClass('visible'); 5883 } 5884 } else { 5885 this.$pagination.children('li.footable-page').removeClass('visible').slice(0, this.total).addClass('visible'); 5886 } 5887 }, 5888 /** 5889 * Handles the click event for all links in the pagination control. 5890 * @instance 5891 * @private 5892 * @param {jQuery.Event} e - The event object for the event. 5893 */ 5894 _onPageClicked: function(e){ 5895 e.preventDefault(); 5896 if ($(e.target).closest('li').is('.active,.disabled')) return; 5897 5898 var self = e.data.self, page = $(this).data('page'); 5899 switch(page){ 5900 case 'first': self.first(); 5901 return; 5902 case 'prev': self.prev(); 5903 return; 5904 case 'next': self.next(); 5905 return; 5906 case 'last': self.last(); 5907 return; 5908 case 'prev-limit': self.prevPages(); 5909 return; 5910 case 'next-limit': self.nextPages(); 5911 return; 5912 default: self._set(page); 5913 return; 5914 } 5915 } 5916 }); 5917 5918 F.components.register('paging', F.Paging, 400); 5919 5920})(jQuery, FooTable); 5921(function(F){ 5922 /** 5923 * An object containing the paging options for the plugin. Added by the {@link FooTable.Paging} component. 5924 * @type {object} 5925 * @prop {boolean} enabled=false - Whether or not to allow paging on the table. 5926 * @prop {string} countFormat="{CP} of {TP}" - A string format used to generate the page count text. 5927 * @prop {number} current=1 - The page number to display. 5928 * @prop {number} limit=5 - The maximum number of page links to display at once. 5929 * @prop {string} position="center" - The string used to specify the alignment of the pagination control. 5930 * @prop {number} size=10 - The number of rows displayed per page. 5931 * @prop {string} container=null - A selector specifying where to place the paging components UI, if null the UI is displayed within a row in the foot of the table. 5932 * @prop {object} strings - An object containing the strings used by the paging buttons. 5933 * @prop {string} strings.first="«" - The string used for the 'first' button. 5934 * @prop {string} strings.prev="‹" - The string used for the 'previous' button. 5935 * @prop {string} strings.next="›" - The string used for the 'next' button. 5936 * @prop {string} strings.last="»" - The string used for the 'last' button. 5937 * @prop {string} strings.prevPages="..." - The string used for the 'previous X pages' button. 5938 * @prop {string} strings.nextPages="..." - The string used for the 'next X pages' button. 5939 */ 5940 F.Defaults.prototype.paging = { 5941 enabled: false, 5942 countFormat: '{CP} of {TP}', 5943 current: 1, 5944 limit: 5, 5945 position: 'center', 5946 size: 10, 5947 container: null, 5948 strings: { 5949 first: '«', 5950 prev: '‹', 5951 next: '›', 5952 last: '»', 5953 prevPages: '...', 5954 nextPages: '...' 5955 } 5956 }; 5957})(FooTable); 5958(function(F){ 5959 /** 5960 * Navigates to the specified page number. Added by the {@link FooTable.Paging} component. 5961 * @instance 5962 * @param {number} num - The page number to go to. 5963 * @returns {jQuery.Promise} 5964 * @fires FooTable.Paging#paging_changing 5965 * @fires FooTable.Paging#paging_changed 5966 * @see FooTable.Paging#goto 5967 */ 5968 F.Table.prototype.gotoPage = function(num){ 5969 return this.use(F.Paging).goto(num); 5970 }; 5971 5972 /** 5973 * Navigates to the next page. Added by the {@link FooTable.Paging} component. 5974 * @instance 5975 * @returns {jQuery.Promise} 5976 * @fires FooTable.Paging#paging_changing 5977 * @fires FooTable.Paging#paging_changed 5978 * @see FooTable.Paging#next 5979 */ 5980 F.Table.prototype.nextPage = function(){ 5981 return this.use(F.Paging).next(); 5982 }; 5983 5984 /** 5985 * Navigates to the previous page. Added by the {@link FooTable.Paging} component. 5986 * @instance 5987 * @returns {jQuery.Promise} 5988 * @fires FooTable.Paging#paging_changing 5989 * @fires FooTable.Paging#paging_changed 5990 * @see FooTable.Paging#prev 5991 */ 5992 F.Table.prototype.prevPage = function(){ 5993 return this.use(F.Paging).prev(); 5994 }; 5995 5996 /** 5997 * Navigates to the first page. Added by the {@link FooTable.Paging} component. 5998 * @instance 5999 * @returns {jQuery.Promise} 6000 * @fires FooTable.Paging#paging_changing 6001 * @fires FooTable.Paging#paging_changed 6002 * @see FooTable.Paging#first 6003 */ 6004 F.Table.prototype.firstPage = function(){ 6005 return this.use(F.Paging).first(); 6006 }; 6007 6008 /** 6009 * Navigates to the last page. Added by the {@link FooTable.Paging} component. 6010 * @instance 6011 * @returns {jQuery.Promise} 6012 * @fires FooTable.Paging#paging_changing 6013 * @fires FooTable.Paging#paging_changed 6014 * @see FooTable.Paging#last 6015 */ 6016 F.Table.prototype.lastPage = function(){ 6017 return this.use(F.Paging).last(); 6018 }; 6019 6020 /** 6021 * Shows the next X number of pages in the pagination control where X is the value set by the {@link FooTable.Defaults#paging} - limit.size option value. Added by the {@link FooTable.Paging} component. 6022 * @instance 6023 * @see FooTable.Paging#nextPages 6024 */ 6025 F.Table.prototype.nextPages = function(){ 6026 return this.use(F.Paging).nextPages(); 6027 }; 6028 6029 /** 6030 * Shows the previous X number of pages in the pagination control where X is the value set by the {@link FooTable.Defaults#paging} - limit.size option value. Added by the {@link FooTable.Paging} component. 6031 * @instance 6032 * @see FooTable.Paging#prevPages 6033 */ 6034 F.Table.prototype.prevPages = function(){ 6035 return this.use(F.Paging).prevPages(); 6036 }; 6037 6038 /** 6039 * Gets or sets the current page size 6040 * @instance 6041 * @param {number} [value] - The new page size to use. 6042 * @returns {(number|undefined)} 6043 * @see FooTable.Paging#pageSize 6044 */ 6045 F.Table.prototype.pageSize = function(value){ 6046 return this.use(F.Paging).pageSize(value); 6047 }; 6048})(FooTable); 6049(function($, F){ 6050 6051 F.Editing = F.Component.extend(/** @lends FooTable.Editing */{ 6052 /** 6053 * The editing component adds a column with edit and delete buttons to each row as well as a single add row button in the footer. 6054 * @constructs 6055 * @extends FooTable.Component 6056 * @param {FooTable.Table} table - The parent {@link FooTable.Table} object for the component. 6057 * @returns {FooTable.Editing} 6058 */ 6059 construct: function(table){ 6060 // call the base constructor 6061 this._super(table, table.o.editing.enabled); 6062 6063 /** 6064 * Whether or not to automatically page to a new row when it is added to the table. 6065 * @type {boolean} 6066 */ 6067 this.pageToNew = table.o.editing.pageToNew; 6068 6069 /** 6070 * Whether or not the editing column and add row button are always visible. 6071 * @type {boolean} 6072 */ 6073 this.alwaysShow = table.o.editing.alwaysShow; 6074 6075 /** 6076 * The options for the editing column. @see {@link FooTable.EditingColumn} for more info. 6077 * @type {object} 6078 * @prop {string} classes="footable-editing" - A space separated string of class names to apply to all cells in the column. 6079 * @prop {string} name="editing" - The name of the column. 6080 * @prop {string} title="" - The title displayed in the header row of the table for the column. 6081 * @prop {boolean} filterable=false - Whether or not the column should be filterable when using the filtering component. 6082 * @prop {boolean} sortable=false - Whether or not the column should be sortable when using the sorting component. 6083 */ 6084 this.column = $.extend(true, {}, table.o.editing.column, {visible: this.alwaysShow}); 6085 6086 /** 6087 * The position of the editing column in the table as well as the alignment of the buttons. 6088 * @type {string} 6089 */ 6090 this.position = table.o.editing.position; 6091 6092 6093 /** 6094 * The text that appears in the show button. This can contain HTML. 6095 * @type {string} 6096 */ 6097 this.showText = table.o.editing.showText; 6098 6099 /** 6100 * The text that appears in the hide button. This can contain HTML. 6101 * @type {string} 6102 */ 6103 this.hideText = table.o.editing.hideText; 6104 6105 /** 6106 * The text that appears in the add button. This can contain HTML. 6107 * @type {string} 6108 */ 6109 this.addText = table.o.editing.addText; 6110 6111 /** 6112 * The text that appears in the edit button. This can contain HTML. 6113 * @type {string} 6114 */ 6115 this.editText = table.o.editing.editText; 6116 6117 /** 6118 * The text that appears in the delete button. This can contain HTML. 6119 * @type {string} 6120 */ 6121 this.deleteText = table.o.editing.deleteText; 6122 6123 /** 6124 * The text that appears in the view button. This can contain HTML. 6125 * @type {string} 6126 */ 6127 this.viewText = table.o.editing.viewText; 6128 6129 /** 6130 * Whether or not to show the Add Row button. 6131 * @type {boolean} 6132 */ 6133 this.allowAdd = table.o.editing.allowAdd; 6134 6135 /** 6136 * Whether or not to show the Edit Row button. 6137 * @type {boolean} 6138 */ 6139 this.allowEdit = table.o.editing.allowEdit; 6140 6141 /** 6142 * Whether or not to show the Delete Row button. 6143 * @type {boolean} 6144 */ 6145 this.allowDelete = table.o.editing.allowDelete; 6146 6147 /** 6148 * Whether or not to show the View Row button. 6149 * @type {boolean} 6150 */ 6151 this.allowView = table.o.editing.allowView; 6152 6153 /** 6154 * Caches the row button elements to help with performance. 6155 * @type {(null|jQuery)} 6156 * @private 6157 */ 6158 this._$buttons = null; 6159 6160 /** 6161 * This object is used to contain the callbacks for the add, edit and delete row buttons. 6162 * @type {object} 6163 * @prop {function} addRow 6164 * @prop {function} editRow 6165 * @prop {function} deleteRow 6166 * @prop {function} viewRow 6167 */ 6168 this.callbacks = { 6169 addRow: F.checkFnValue(this, table.o.editing.addRow), 6170 editRow: F.checkFnValue(this, table.o.editing.editRow), 6171 deleteRow: F.checkFnValue(this, table.o.editing.deleteRow), 6172 viewRow: F.checkFnValue(this, table.o.editing.viewRow) 6173 }; 6174 }, 6175 /* PROTECTED */ 6176 /** 6177 * Checks the supplied data and options for the editing component. 6178 * @instance 6179 * @protected 6180 * @param {object} data - The jQuery data object from the parent table. 6181 * @fires FooTable.Editing#"preinit.ft.editing" 6182 */ 6183 preinit: function(data){ 6184 var self = this; 6185 /** 6186 * The preinit.ft.editing event is raised before the UI is created and provides the tables jQuery data object for additional options parsing. 6187 * Calling preventDefault on this event will disable the component. 6188 * @event FooTable.Editing#"preinit.ft.editing" 6189 * @param {jQuery.Event} e - The jQuery.Event object for the event. 6190 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 6191 * @param {object} data - The jQuery data object of the table raising the event. 6192 */ 6193 this.ft.raise('preinit.ft.editing', [data]).then(function(){ 6194 if (self.ft.$el.hasClass('footable-editing')) 6195 self.enabled = true; 6196 6197 self.enabled = F.is.boolean(data.editing) 6198 ? data.editing 6199 : self.enabled; 6200 6201 if (!self.enabled) return; 6202 6203 self.pageToNew = F.is.boolean(data.editingPageToNew) ? data.editingPageToNew : self.pageToNew; 6204 6205 self.alwaysShow = F.is.boolean(data.editingAlwaysShow) ? data.editingAlwaysShow : self.alwaysShow; 6206 6207 self.position = F.is.string(data.editingPosition) ? data.editingPosition : self.position; 6208 6209 self.showText = F.is.string(data.editingShowText) ? data.editingShowText : self.showText; 6210 6211 self.hideText = F.is.string(data.editingHideText) ? data.editingHideText : self.hideText; 6212 6213 self.addText = F.is.string(data.editingAddText) ? data.editingAddText : self.addText; 6214 6215 self.editText = F.is.string(data.editingEditText) ? data.editingEditText : self.editText; 6216 6217 self.deleteText = F.is.string(data.editingDeleteText) ? data.editingDeleteText : self.deleteText; 6218 6219 self.viewText = F.is.string(data.editingViewText) ? data.editingViewText : self.viewText; 6220 6221 self.allowAdd = F.is.boolean(data.editingAllowAdd) ? data.editingAllowAdd : self.allowAdd; 6222 6223 self.allowEdit = F.is.boolean(data.editingAllowEdit) ? data.editingAllowEdit : self.allowEdit; 6224 6225 self.allowDelete = F.is.boolean(data.editingAllowDelete) ? data.editingAllowDelete : self.allowDelete; 6226 6227 self.allowView = F.is.boolean(data.editingAllowView) ? data.editingAllowView : self.allowView; 6228 6229 self.column = new F.EditingColumn(self.ft, self, $.extend(true, {}, self.column, data.editingColumn, {visible: self.alwaysShow})); 6230 6231 if (self.ft.$el.hasClass('footable-editing-left')) 6232 self.position = 'left'; 6233 6234 if (self.ft.$el.hasClass('footable-editing-right')) 6235 self.position = 'right'; 6236 6237 if (self.position === 'right'){ 6238 self.column.index = self.ft.columns.array.length; 6239 } else { 6240 self.column.index = 0; 6241 for (var i = 0, len = self.ft.columns.array.length; i < len; i++){ 6242 self.ft.columns.array[i].index += 1; 6243 } 6244 } 6245 self.ft.columns.array.push(self.column); 6246 self.ft.columns.array.sort(function(a, b){ return a.index - b.index; }); 6247 6248 self.callbacks.addRow = F.checkFnValue(self, data.editingAddRow, self.callbacks.addRow); 6249 self.callbacks.editRow = F.checkFnValue(self, data.editingEditRow, self.callbacks.editRow); 6250 self.callbacks.deleteRow = F.checkFnValue(self, data.editingDeleteRow, self.callbacks.deleteRow); 6251 self.callbacks.viewRow = F.checkFnValue(self, data.editingViewRow, self.callbacks.viewRow); 6252 }, function(){ 6253 self.enabled = false;
6254 }); 6255 }, 6256 /** 6257 * Initializes the editing component for the plugin using the supplied table and options. 6258 * @instance 6259 * @protected 6260 * @fires FooTable.Editing#"init.ft.editing" 6261 */ 6262 init: function(){ 6263 /** 6264 * The init.ft.editing event is raised before its UI is generated. 6265 * Calling preventDefault on this event will disable the component. 6266 * @event FooTable.Editing#"init.ft.editing" 6267 * @param {jQuery.Event} e - The jQuery.Event object for the event. 6268 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 6269 */ 6270 var self = this; 6271 this.ft.raise('init.ft.editing').then(function(){ 6272 self.$create(); 6273 }, function(){ 6274 self.enabled = false; 6275 }); 6276 }, 6277 /** 6278 * Destroys the editing component removing any UI generated from the table. 6279 * @instance 6280 * @protected 6281 * @fires FooTable.Editing#"destroy.ft.editing" 6282 */ 6283 destroy: function () { 6284 /** 6285 * The destroy.ft.editing event is raised before its UI is removed. 6286 * Calling preventDefault on this event will prevent the component from being destroyed. 6287 * @event FooTable.Editing#"destroy.ft.editing" 6288 * @param {jQuery.Event} e - The jQuery.Event object for the event. 6289 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 6290 */ 6291 var self = this; 6292 this.ft.raise('destroy.ft.editing').then(function(){ 6293 self.ft.$el.removeClass('footable-editing footable-editing-always-show footable-editing-no-add footable-editing-no-edit footable-editing-no-delete footable-editing-no-view') 6294 .off('click.ft.editing').find('tfoot > tr.footable-editing').remove(); 6295 }); 6296 }, 6297 /** 6298 * Creates the editing UI from the current options setting the various jQuery properties of this component. 6299 * @instance 6300 * @protected 6301 */ 6302 $create: function(){ 6303 var self = this, position = self.position === 'right' ? 'footable-editing-right' : 'footable-editing-left'; 6304 self.ft.$el.addClass('footable-editing').addClass(position) 6305 .on('click.ft.editing', '.footable-show', {self: self}, self._onShowClick) 6306 .on('click.ft.editing', '.footable-hide', {self: self}, self._onHideClick) 6307 .on('click.ft.editing', '.footable-edit', {self: self}, self._onEditClick) 6308 .on('click.ft.editing', '.footable-delete', {self: self}, self._onDeleteClick) 6309 .on('click.ft.editing', '.footable-view', {self: self}, self._onViewClick) 6310 .on('click.ft.editing', '.footable-add', {self: self}, self._onAddClick); 6311 6312 self.$cell = $('<td/>').attr('colspan', self.ft.columns.visibleColspan).append(self.$buttonShow()); 6313 if (self.allowAdd){ 6314 self.$cell.append(self.$buttonAdd()); 6315 } 6316 self.$cell.append(self.$buttonHide()); 6317 6318 if (self.alwaysShow){ 6319 self.ft.$el.addClass('footable-editing-always-show'); 6320 } 6321 6322 if (!self.allowAdd) self.ft.$el.addClass('footable-editing-no-add'); 6323 if (!self.allowEdit) self.ft.$el.addClass('footable-editing-no-edit'); 6324 if (!self.allowDelete) self.ft.$el.addClass('footable-editing-no-delete'); 6325 if (!self.allowView) self.ft.$el.addClass('footable-editing-no-view'); 6326 6327 var $tfoot = self.ft.$el.children('tfoot'); 6328 if ($tfoot.length == 0){ 6329 $tfoot = $('<tfoot/>'); 6330 self.ft.$el.append($tfoot); 6331 } 6332 self.$row = $('<tr/>', { 'class': 'footable-editing' }).append(self.$cell).appendTo($tfoot); 6333 }, 6334 /** 6335 * Creates the show button for the editing component. 6336 * @instance 6337 * @protected 6338 * @returns {(string|HTMLElement|jQuery)} 6339 */ 6340 $buttonShow: function(){ 6341 return '<button type="button" class="btn btn-primary footable-show">' + this.showText + '</button>'; 6342 }, 6343 /** 6344 * Creates the hide button for the editing component. 6345 * @instance 6346 * @protected 6347 * @returns {(string|HTMLElement|jQuery)} 6348 */ 6349 $buttonHide: function(){ 6350 return '<button type="button" class="btn btn-default footable-hide">' + this.hideText + '</button>'; 6351 }, 6352 /** 6353 * Creates the add button for the editing component. 6354 * @instance 6355 * @protected 6356 * @returns {(string|HTMLElement|jQuery)} 6357 */ 6358 $buttonAdd: function(){ 6359 return '<button type="button" class="btn btn-primary footable-add">' + this.addText + '</button> '; 6360 }, 6361 /** 6362 * Creates the edit button for the editing component. 6363 * @instance 6364 * @protected 6365 * @returns {(string|HTMLElement|jQuery)} 6366 */ 6367 $buttonEdit: function(){ 6368 return '<button type="button" class="btn btn-default footable-edit">' + this.editText + '</button> '; 6369 }, 6370 /** 6371 * Creates the delete button for the editing component. 6372 * @instance 6373 * @protected 6374 * @returns {(string|HTMLElement|jQuery)} 6375 */ 6376 $buttonDelete: function(){ 6377 return '<button type="button" class="btn btn-default footable-delete">' + this.deleteText + '</button>'; 6378 }, 6379 /** 6380 * Creates the view button for the editing component. 6381 * @instance 6382 * @protected 6383 * @returns {(string|HTMLElement|jQuery)} 6384 */ 6385 $buttonView: function(){ 6386 return '<button type="button" class="btn btn-default footable-view">' + this.viewText + '</button> '; 6387 }, 6388 /** 6389 * Creates the button group for the row buttons. 6390 * @instance 6391 * @protected 6392 * @returns {(string|HTMLElement|jQuery)} 6393 */ 6394 $rowButtons: function(){ 6395 if (F.is.jq(this._$buttons)) return this._$buttons.clone(); 6396 this._$buttons = $('<div class="btn-group btn-group-xs" role="group"></div>'); 6397 if (this.allowView) this._$buttons.append(this.$buttonView()); 6398 if (this.allowEdit) this._$buttons.append(this.$buttonEdit()); 6399 if (this.allowDelete) this._$buttons.append(this.$buttonDelete()); 6400 return this._$buttons; 6401 }, 6402 /** 6403 * Performs the drawing of the component. 6404 */ 6405 draw: function(){ 6406 this.$cell.attr('colspan', this.ft.columns.visibleColspan); 6407 }, 6408 /** 6409 * Handles the edit button click event. 6410 * @instance 6411 * @private 6412 * @param {jQuery.Event} e - The jQuery.Event object for the event. 6413 * @fires FooTable.Editing#"edit.ft.editing" 6414 */ 6415 _onEditClick: function(e){ 6416 e.preventDefault(); 6417 var self = e.data.self, row = $(this).closest('tr').data('__FooTableRow__'); 6418 if (row instanceof F.Row){ 6419 /** 6420 * The edit.ft.editing event is raised before its callback is executed. 6421 * Calling preventDefault on this event will prevent the callback from being executed. 6422 * @event FooTable.Editing#"edit.ft.editing" 6423 * @param {jQuery.Event} e - The jQuery.Event object for the event. 6424 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 6425 * @param {FooTable.Row} row - The row to be edited. 6426 */ 6427 self.ft.raise('edit.ft.editing', [row]).then(function(){ 6428 self.callbacks.editRow.call(self.ft, row); 6429 }); 6430 } 6431 }, 6432 /** 6433 * Handles the delete button click event. 6434 * @instance 6435 * @private 6436 * @param {jQuery.Event} e - The jQuery.Event object for the event. 6437 * @fires FooTable.Editing#"delete.ft.editing" 6438 */ 6439 _onDeleteClick: function(e){ 6440 e.preventDefault(); 6441 var self = e.data.self, row = $(this).closest('tr').data('__FooTableRow__'); 6442 if (row instanceof F.Row){ 6443 /** 6444 * The delete.ft.editing event is raised before its callback is executed. 6445 * Calling preventDefault on this event will prevent the callback from being executed. 6446 * @event FooTable.Editing#"delete.ft.editing" 6447 * @param {jQuery.Event} e - The jQuery.Event object for the event. 6448 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 6449 * @param {FooTable.Row} row - The row to be deleted. 6450 */ 6451 self.ft.raise('delete.ft.editing', [row]).then(function(){ 6452 self.callbacks.deleteRow.call(self.ft, row); 6453 }); 6454 } 6455 }, 6456 /** 6457 * Handles the view button click event. 6458 * @instance 6459 * @private 6460 * @param {jQuery.Event} e - The jQuery.Event object for the event. 6461 * @fires FooTable.Editing#"view.ft.editing" 6462 */ 6463 _onViewClick: function(e){ 6464 e.preventDefault(); 6465 var self = e.data.self, row = $(this).closest('tr').data('__FooTableRow__'); 6466 if (row instanceof F.Row){ 6467 /** 6468 * The view.ft.editing event is raised before its callback is executed. 6469 * Calling preventDefault on this event will prevent the callback from being executed. 6470 * @event FooTable.Editing#"view.ft.editing" 6471 * @param {jQuery.Event} e - The jQuery.Event object for the event. 6472 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 6473 * @param {FooTable.Row} row - The row to be viewed. 6474 */ 6475 self.ft.raise('view.ft.editing', [row]).then(function(){ 6476 self.callbacks.viewRow.call(self.ft, row); 6477 }); 6478 } 6479 }, 6480 /** 6481 * Handles the add button click event. 6482 * @instance 6483 * @private 6484 * @param {jQuery.Event} e - The jQuery.Event object for the event. 6485 * @fires FooTable.Editing#"add.ft.editing" 6486 */ 6487 _onAddClick: function(e){ 6488 e.preventDefault(); 6489 var self = e.data.self; 6490 /** 6491 * The add.ft.editing event is raised before its callback is executed. 6492 * Calling preventDefault on this event will prevent the callback from being executed. 6493 * @event FooTable.Editing#"add.ft.editing" 6494 * @param {jQuery.Event} e - The jQuery.Event object for the event. 6495 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 6496 */ 6497 self.ft.raise('add.ft.editing').then(function(){ 6498 self.callbacks.addRow.call(self.ft); 6499 }); 6500 }, 6501 /** 6502 * Handles the show button click event. 6503 * @instance 6504 * @private 6505 * @param {jQuery.Event} e - The jQuery.Event object for the event.
6506 * @fires FooTable.Editing#"show.ft.editing" 6507 */ 6508 _onShowClick: function(e){ 6509 e.preventDefault(); 6510 var self = e.data.self; 6511 /** 6512 * The show.ft.editing event is raised before its callback is executed. 6513 * Calling preventDefault on this event will prevent the callback from being executed. 6514 * @event FooTable.Editing#"show.ft.editing" 6515 * @param {jQuery.Event} e - The jQuery.Event object for the event. 6516 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 6517 */ 6518 self.ft.raise('show.ft.editing').then(function(){ 6519 self.ft.$el.addClass('footable-editing-show'); 6520 self.column.visible = true; 6521 self.ft.draw(); 6522 }); 6523 }, 6524 /** 6525 * Handles the hide button click event. 6526 * @instance 6527 * @private 6528 * @param {jQuery.Event} e - The jQuery.Event object for the event. 6529 * @fires FooTable.Editing#"show.ft.editing" 6530 */ 6531 _onHideClick: function(e){ 6532 e.preventDefault(); 6533 var self = e.data.self; 6534 /** 6535 * The hide.ft.editing event is raised before its callback is executed. 6536 * Calling preventDefault on this event will prevent the callback from being executed. 6537 * @event FooTable.Editing#"hide.ft.editing" 6538 * @param {jQuery.Event} e - The jQuery.Event object for the event. 6539 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 6540 */ 6541 self.ft.raise('hide.ft.editing').then(function(){ 6542 self.ft.$el.removeClass('footable-editing-show'); 6543 self.column.visible = false; 6544 self.ft.draw(); 6545 }); 6546 } 6547 }); 6548 6549 F.components.register('editing', F.Editing, 850); 6550 6551})(jQuery, FooTable); 6552 6553(function($, F){ 6554 6555 F.EditingColumn = F.Column.extend(/** @lends FooTable.EditingColumn */{ 6556 /** 6557 * The Editing column class is used to create the column containing the editing buttons. 6558 * @constructs 6559 * @extends FooTable.Column 6560 * @param {FooTable.Table} instance - The parent {@link FooTable.Table} this column belongs to. 6561 * @param {FooTable.Editing} editing - The parent {@link FooTable.Editing} component this column is used with. 6562 * @param {object} definition - An object containing all the properties to set for the column. 6563 * @returns {FooTable.EditingColumn} 6564 */ 6565 construct: function(instance, editing, definition){ 6566 this._super(instance, definition, 'editing'); 6567 this.editing = editing; 6568 this.internal = true; 6569 }, 6570 /** 6571 * After the column has been defined this ensures that the $el property is a jQuery object by either creating or updating the current value. 6572 * @instance 6573 * @protected 6574 * @this FooTable.Column 6575 */ 6576 $create: function(){ 6577 (this.$el = !this.virtual && F.is.jq(this.$el) ? this.$el : $('<th/>', {'class': 'footable-editing'})).html(this.title); 6578 }, 6579 /** 6580 * This is supplied either the cell value or jQuery object to parse. Any value can be returned from this method and 6581 * will be provided to the {@link FooTable.EditingColumn#format} function 6582 * to generate the cell contents. 6583 * @instance 6584 * @protected 6585 * @param {(*|jQuery)} valueOrElement - The value or jQuery cell object. 6586 * @returns {(jQuery)} 6587 */ 6588 parser: function(valueOrElement){ 6589 if (F.is.string(valueOrElement)) valueOrElement = $($.trim(valueOrElement)); 6590 if (F.is.element(valueOrElement)) valueOrElement = $(valueOrElement); 6591 if (F.is.jq(valueOrElement)){ 6592 var tagName = valueOrElement.prop('tagName').toLowerCase(); 6593 if (tagName == 'td' || tagName == 'th') return valueOrElement.data('value') || valueOrElement.contents(); 6594 return valueOrElement; 6595 } 6596 return null; 6597 }, 6598 /** 6599 * Creates a cell to be used in the supplied row for this column. 6600 * @param {FooTable.Row} row - The row to create the cell for. 6601 * @returns {FooTable.Cell} 6602 */ 6603 createCell: function(row){ 6604 var $buttons = this.editing.$rowButtons(), $cell = $('<td/>').append($buttons); 6605 if (F.is.jq(row.$el)){ 6606 if (this.index === 0){ 6607 $cell.prependTo(row.$el); 6608 } else { 6609 $cell.insertAfter(row.$el.children().eq(this.index-1)); 6610 } 6611 } 6612 return new F.Cell(this.ft, row, this, $cell || $cell.html()); 6613 } 6614 }); 6615 6616 F.columns.register('editing', F.EditingColumn); 6617 6618})(jQuery, FooTable); 6619(function($, F) { 6620 6621 /**
6622 * An object containing the editing options for the plugin. Added by the {@link FooTable.Editing} component. 6623 * @type {object} 6624 * @prop {boolean} enabled=false - Whether or not to allow editing on the table. 6625 * @prop {boolean} pageToNew=true - Whether or not to automatically page to a new row when it is added to the table. 6626 * @prop {string} position="right" - The position of the editing column in the table as well as the alignment of the buttons. 6627 * @prop {boolean} alwaysShow=false - Whether or not the editing column and add row button are always visible. 6628 * @prop {function} addRow - The callback function to execute when the add row button is clicked. 6629 * @prop {function} editRow - The callback function to execute when the edit row button is clicked. 6630 * @prop {function} deleteRow - The callback function to execute when the delete row button is clicked. 6631 * @prop {function} viewRow - The callback function to execute when the view row button is clicked. 6632 * @prop {string} showText - The text that appears in the show button. This can contain HTML. 6633 * @prop {string} hideText - The text that appears in the hide button. This can contain HTML. 6634 * @prop {string} addText - The text that appears in the add button. This can contain HTML. 6635 * @prop {string} editText - The text that appears in the edit button. This can contain HTML. 6636 * @prop {string} deleteText - The text that appears in the delete button. This can contain HTML. 6637 * @prop {string} viewText - The text that appears in the view button. This can contain HTML. 6638 * @prop {boolean} allowAdd - Whether or not to show the Add Row button. 6639 * @prop {boolean} allowEdit - Whether or not to show the Edit Row button. 6640 * @prop {boolean} allowDelete - Whether or not to show the Delete Row button. 6641 * @prop {boolean} allowView - Whether or not to show the View Row button. 6642 * @prop {object} column - The options for the editing column. @see {@link FooTable.EditingColumn} for more info. 6643 * @prop {string} column.classes="footable-editing" - A space separated string of class names to apply to all cells in the column. 6644 * @prop {string} column.name="editing" - The name of the column. 6645 * @prop {string} column.title="" - The title displayed in the header row of the table for the column. 6646 * @prop {boolean} column.filterable=false - Whether or not the column should be filterable when using the filtering component. 6647 * @prop {boolean} column.sortable=false - Whether or not the column should be sortable when using the sorting component. 6648 */ 6649 F.Defaults.prototype.editing = { 6650 enabled: false, 6651 pageToNew: true, 6652 position: 'right', 6653 alwaysShow: false, 6654 addRow: function(){}, 6655 editRow: function(row){}, 6656 deleteRow: function(row){}, 6657 viewRow: function(row){}, 6658 showText: '<span class="fooicon fooicon-pencil" aria-hidden="true"></span> Edit rows', 6659 hideText: 'Cancel', 6660 addText: 'New row', 6661 editText: '<span class="fooicon fooicon-pencil" aria-hidden="true"></span>', 6662 deleteText: '<span class="fooicon fooicon-trash" aria-hidden="true"></span>', 6663 viewText: '<span class="fooicon fooicon-stats" aria-hidden="true"></span>', 6664 allowAdd: true, 6665 allowEdit: true, 6666 allowDelete: true, 6667 allowView: false, 6668 column: { 6669 classes: 'footable-editing', 6670 name: 'editing', 6671 title: '', 6672 filterable: false, 6673 sortable: false 6674 } 6675 }; 6676 6677})(jQuery, FooTable); 6678 6679(function($, F){ 6680 6681 if (F.is.defined(F.Paging)){ 6682 /** 6683 * Holds a shallow clone of the un-paged {@link FooTable.Rows#array} value before paging occurs and superfluous rows are removed. Added by the {@link FooTable.Editing} component. 6684 * @instance 6685 * @public 6686 * @type {Array<FooTable.Row>} 6687 */ 6688 F.Paging.prototype.unpaged = []; 6689 6690 // override the default predraw method with one that sets the unpaged property. 6691 F.Paging.extend('predraw', function(){ 6692 this.unpaged = this.ft.rows.array.slice(0); // create a shallow clone for later use 6693 this._super(); // call the original method 6694 }); 6695 } 6696 6697})(jQuery, FooTable); 6698(function($, F){ 6699 6700 /** 6701 * Adds the row to the table. 6702 * @param {boolean} [redraw=true] - Whether or not to redraw the table, defaults to true but for bulk operations this 6703 * can be set to false and then followed by a call to the {@link FooTable.Table#draw} method. 6704 * @returns {jQuery.Deferred} 6705 */ 6706 F.Row.prototype.add = function(redraw){ 6707 redraw = F.is.boolean(redraw) ? redraw : true; 6708 var self = this; 6709 return $.Deferred(function(d){ 6710 var index = self.ft.rows.all.push(self) - 1; 6711 if (redraw){ 6712 return self.ft.draw().then(function(){ 6713 d.resolve(index); 6714 }); 6715 } else { 6716 d.resolve(index); 6717 } 6718 }); 6719 }; 6720 6721 /** 6722 * Removes the row from the table. 6723 * @param {boolean} [redraw=true] - Whether or not to redraw the table, defaults to true but for bulk operations this 6724 * can be set to false and then followed by a call to the {@link FooTable.Table#draw} method. 6725 * @returns {jQuery.Deferred} 6726 */ 6727 F.Row.prototype.delete = function(redraw){ 6728 redraw = F.is.boolean(redraw) ? redraw : true; 6729 var self = this; 6730 return $.Deferred(function(d){ 6731 var index = self.ft.rows.all.indexOf(self); 6732 if (F.is.number(index) && index >= 0 && index < self.ft.rows.all.length){ 6733 self.ft.rows.all.splice(index, 1); 6734 if (redraw){ 6735 return self.ft.draw().then(function(){ 6736 d.resolve(self); 6737 }); 6738 } 6739 } 6740 d.resolve(self); 6741 }); 6742 }; 6743 6744 if (F.is.defined(F.Paging)){ 6745 // override the default add method with one that supports paging 6746 F.Row.extend('add', function(redraw){ 6747 redraw = F.is.boolean(redraw) ? redraw : true; 6748 var self = this, 6749 added = this._super(redraw), 6750 editing = self.ft.use(F.Editing), 6751 paging; 6752 if (editing && editing.pageToNew && (paging = self.ft.use(F.Paging)) && redraw){ 6753 return added.then(function(){ 6754 var index = paging.unpaged.indexOf(self); // find this row in the unpaged array (this array will be sorted and filtered)
6755 var page = Math.ceil((index + 1) / paging.size); // calculate the page the new row is on 6756 if (paging.current !== page){ // goto the page if we need to 6757 return paging.goto(page); 6758 } 6759 }); 6760 } 6761 return added; 6762 }); 6763 } 6764 6765 if (F.is.defined(F.Sorting)){ 6766 // override the default val method with one that supports sorting and paging 6767 F.Row.extend('val', function(data, redraw){ 6768 redraw = F.is.boolean(redraw) ? redraw : true; 6769 var result = this._super(data); 6770 if (!F.is.hash(data)){ 6771 return result; 6772 } 6773 var self = this; 6774 if (redraw){ 6775 self.ft.draw().then(function(){ 6776 var editing = self.ft.use(F.Editing), paging; 6777 if (F.is.defined(F.Paging) && editing && editing.pageToNew && (paging = self.ft.use(F.Paging))){ 6778 var index = paging.unpaged.indexOf(self); // find this row in the unpaged array (this array will be sorted and filtered) 6779 var page = Math.ceil((index + 1) / paging.size); // calculate the page the new row is on 6780 if (paging.current !== page){ // goto the page if we need to 6781 return paging.goto(page); 6782 } 6783 } 6784 }); 6785 } 6786 return result; 6787 }); 6788 } 6789 6790})(jQuery, FooTable); 6791(function(F){ 6792 6793 /** 6794 * Adds a row to the underlying {@link FooTable.Rows#all} array. 6795 * @param {(object|FooTable.Row)} dataOrRow - A hash containing the row values or an actual {@link FooTable.Row} object. 6796 * @param {boolean} [redraw=true] - Whether or not to redraw the table, defaults to true but for bulk operations this 6797 * can be set to false and then followed by a call to the {@link FooTable.Table#draw} method. 6798 */ 6799 F.Rows.prototype.add = function(dataOrRow, redraw){ 6800 var row = dataOrRow; 6801 if (F.is.hash(dataOrRow)){ 6802 row = new FooTable.Row(this.ft, this.ft.columns.array, dataOrRow); 6803 } 6804 if (row instanceof FooTable.Row){ 6805 row.add(redraw); 6806 } 6807 }; 6808 6809 /** 6810 * Updates a row in the underlying {@link FooTable.Rows#all} array. 6811 * @param {(number|FooTable.Row)} indexOrRow - The index to update or the actual {@link FooTable.Row} object. 6812 * @param {object} data - A hash containing the new row values. 6813 * @param {boolean} [redraw=true] - Whether or not to redraw the table, defaults to true but for bulk operations this 6814 * can be set to false and then followed by a call to the {@link FooTable.Table#draw} method. 6815 */ 6816 F.Rows.prototype.update = function(indexOrRow, data, redraw){ 6817 var len = this.ft.rows.all.length, 6818 row = indexOrRow; 6819 if (F.is.number(indexOrRow) && indexOrRow >= 0 && indexOrRow < len){ 6820 row = this.ft.rows.all[indexOrRow]; 6821 } 6822 if (row instanceof FooTable.Row && F.is.hash(data)){ 6823 row.val(data, redraw); 6824 } 6825 }; 6826 6827 /** 6828 * Deletes a row from the underlying {@link FooTable.Rows#all} array. 6829 * @param {(number|FooTable.Row)} indexOrRow - The index to delete or the actual {@link FooTable.Row} object. 6830 * @param {boolean} [redraw=true] - Whether or not to redraw the table, defaults to true but for bulk operations this 6831 * can be set to false and then followed by a call to the {@link FooTable.Table#draw} method. 6832 */ 6833 F.Rows.prototype.delete = function(indexOrRow, redraw){ 6834 var len = this.ft.rows.all.length, 6835 row = indexOrRow; 6836 if (F.is.number(indexOrRow) && indexOrRow >= 0 && indexOrRow < len){ 6837 row = this.ft.rows.all[indexOrRow]; 6838 } 6839 if (row instanceof FooTable.Row){ 6840 row.delete(redraw); 6841 } 6842 }; 6843 6844})(FooTable); 6845 6846(function($, F){ 6847 6848 // global int to use if the table has no ID 6849 var _uid = 0, 6850 // a hash value for the current url 6851 _url_hash = (function(str){ 6852 var i, l, hval = 0x811c9dc5; 6853 for (i = 0, l = str.length; i < l; i++) { 6854 hval ^= str.charCodeAt(i); 6855 hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24); 6856 } 6857 return hval >>> 0; 6858 })(location.origin + location.pathname); 6859 6860 F.State = F.Component.extend(/** @lends FooTable.State */{ 6861 /** 6862 * The state component adds the ability for the table to remember its basic state for filtering, paging and sorting. 6863 * @constructs 6864 * @extends FooTable.Component 6865 * @param {FooTable.Table} table - The parent {@link FooTable.Table} object for the component. 6866 * @returns {FooTable.State} 6867 */ 6868 construct: function(table){ 6869 // call the constructor of the base class 6870 this._super(table, table.o.state.enabled); 6871 // Change this value if an update to this component requires any stored data to be reset 6872 this._key = '1'; 6873 /** 6874 * The key to use to store the state for this table. 6875 * @type {(null|string)} 6876 */ 6877 this.key = this._key + (F.is.string(table.o.state.key) ? table.o.state.key : this._uid()); 6878 /** 6879 * Whether or not to allow the filtering component to store it's state. 6880 * @type {boolean} 6881 */ 6882 this.filtering = F.is.boolean(table.o.state.filtering) ? table.o.state.filtering : true; 6883 /** 6884 * Whether or not to allow the paging component to store it's state. 6885 * @type {boolean} 6886 */ 6887 this.paging = F.is.boolean(table.o.state.paging) ? table.o.state.paging : true; 6888 /** 6889 * Whether or not to allow the sorting component to store it's state. 6890 * @type {boolean} 6891 */ 6892 this.sorting = F.is.boolean(table.o.state.sorting) ? table.o.state.sorting : true; 6893 }, 6894 /* PROTECTED */ 6895 /** 6896 * Checks the supplied data and options for the state component. 6897 * @instance 6898 * @protected 6899 * @param {object} data - The jQuery data object from the parent table. 6900 * @fires FooTable.State#"preinit.ft.state" 6901 * @this FooTable.State 6902 */ 6903 preinit: function(data){ 6904 var self = this; 6905 /** 6906 * The preinit.ft.state event is raised before the UI is created and provides the tables jQuery data object for additional options parsing. 6907 * Calling preventDefault on this event will disable the component. 6908 * @event FooTable.State#"preinit.ft.state" 6909 * @param {jQuery.Event} e - The jQuery.Event object for the event. 6910 * @param {FooTable.Table} ft - The instance of the plugin raising the event. 6911 * @param {object} data - The jQuery data object of the table raising the event. 6912 */ 6913 this.ft.raise('preinit.ft.state', [data]).then(function(){ 6914 6915 self.enabled = F.is.boolean(data.state) 6916 ? data.state 6917 : self.enabled; 6918 6919 if (!self.enabled) return; 6920 6921 self.key = self._key + (F.is.string(data.stateKey) ? data.stateKey : self.key); 6922 6923 self.filtering = F.is.boolean(data.stateFiltering) ? data.stateFiltering : self.filtering; 6924 6925 self.paging = F.is.boolean(data.statePaging) ? data.statePaging : self.paging; 6926 6927 self.sorting = F.is.boolean(data.stateSorting) ? data.stateSorting : self.sorting; 6928 6929 }, function(){ 6930 self.enabled = false;
6931 }); 6932 }, 6933 /** 6934 * Gets the state value for the specified key for this table. 6935 * @instance 6936 * @param {string} key - The key to get the value for. 6937 * @returns {(*|null)} 6938 */ 6939 get: function(key){ 6940 return JSON.parse(localStorage.getItem(this.key + ':' + key)); 6941 }, 6942 /** 6943 * Sets the state value for the specified key for this table. 6944 * @instance 6945 * @param {string} key - The key to set the value for. 6946 * @param {*} data - The value to store for the key. This value must be JSON.stringify friendly. 6947 */ 6948 set: function(key, data){ 6949 localStorage.setItem(this.key + ':' + key, JSON.stringify(data)); 6950 }, 6951 /** 6952 * Clears the state value for the specified key for this table. 6953 * @instance 6954 * @param {string} key - The key to clear the value for. 6955 */ 6956 remove: function(key){ 6957 localStorage.removeItem(this.key + ':' + key); 6958 }, 6959 /** 6960 * Executes the {@link FooTable.Component#readState} function on all components. 6961 * @instance 6962 */ 6963 read: function(){ 6964 this.ft.execute(false, true, 'readState'); 6965 }, 6966 /** 6967 * Executes the {@link FooTable.Component#writeState} function on all components. 6968 * @instance 6969 */ 6970 write: function(){ 6971 this.ft.execute(false, true, 'writeState'); 6972 }, 6973 /** 6974 * Executes the {@link FooTable.Component#clearState} function on all components. 6975 * @instance 6976 */ 6977 clear: function(){ 6978 this.ft.execute(false, true, 'clearState'); 6979 }, 6980 /** 6981 * Generates a unique identifier for the current {@link FooTable.Table} if one is not supplied through the options. 6982 * This value is a combination of the url hash and either the element ID or an incremented global int value. 6983 * @instance 6984 * @returns {*} 6985 * @private 6986 */ 6987 _uid: function(){ 6988 var id = this.ft.$el.attr('id'); 6989 return _url_hash + '_' + (F.is.string(id) ? id : ++_uid); 6990 } 6991 }); 6992 6993 F.components.register('state', F.State, 700); 6994 6995})(jQuery, FooTable); 6996(function(F){ 6997 6998 /** 6999 * This method is called from the {@link FooTable.State#read} method and allows a component to retrieve its' stored state. 7000 * @instance 7001 * @protected 7002 * @function 7003 */ 7004 F.Component.prototype.readState = function(){}; 7005 7006 /** 7007 * This method is called from the {@link FooTable.State#write} method and allows a component to write its' current state to the store. 7008 * @instance 7009 * @protected 7010 * @function 7011 */ 7012 F.Component.prototype.writeState = function(){}; 7013 7014 /** 7015 * This method is called from the {@link FooTable.State#clear} method and allows a component to clear any stored state. 7016 * @instance 7017 * @protected 7018 * @function 7019 */ 7020 F.Component.prototype.clearState = function(){}; 7021 7022})(FooTable); 7023(function(F){ 7024 7025 /** 7026 * An object containing the state options for the plugin. Added by the {@link FooTable.State} component. 7027 * @type {object} 7028 * @prop {boolean} enabled=false - Whether or not to allow state to be stored for the table. This overrides the individual component enable options. 7029 * @prop {boolean} filtering=true - Whether or not to allow the filtering state to be stored. 7030 * @prop {boolean} paging=true - Whether or not to allow the filtering state to be stored. 7031 * @prop {boolean} sorting=true - Whether or not to allow the filtering state to be stored. 7032 * @prop {string} key=null - The unique key to use to store the table's data. 7033 */ 7034 F.Defaults.prototype.state = { 7035 enabled: false, 7036 filtering: true, 7037 paging: true, 7038 sorting: true, 7039 key: null 7040 }; 7041 7042})(FooTable); 7043(function(F){ 7044 7045 if (!F.Filtering) return; 7046 7047 /** 7048 * Allows the filtering component to retrieve its' stored state. 7049 */ 7050 F.Filtering.prototype.readState = function(){ 7051 if (this.ft.state.filtering){ 7052 var state = this.ft.state.get('filtering'); 7053 if (F.is.hash(state) && !F.is.emptyArray(state.filters)){ 7054 this.filters = this.ensure(state.filters); 7055 } 7056 } 7057 }; 7058 7059 /** 7060 * Allows the filtering component to write its' current state to the store. 7061 */ 7062 F.Filtering.prototype.writeState = function(){ 7063 if (this.ft.state.filtering) { 7064 var filters = F.arr.map(this.filters, function (f) { 7065 return { 7066 name: f.name, 7067 query: f.query instanceof F.Query ? f.query.val() : f.query, 7068 columns: F.arr.map(f.columns, function (c) { 7069 return c.name; 7070 }), 7071 hidden: f.hidden, 7072 space: f.space, 7073 connectors: f.connectors, 7074 ignoreCase: f.ignoreCase 7075 }; 7076 }); 7077 this.ft.state.set('filtering', {filters: filters}); 7078 } 7079 }; 7080 7081 /** 7082 * Allows the filtering component to clear any stored state. 7083 */ 7084 F.Filtering.prototype.clearState = function(){ 7085 if (this.ft.state.filtering) { 7086 this.ft.state.remove('filtering'); 7087 } 7088 }; 7089 7090})(FooTable); 7091(function(F){ 7092 7093 if (!F.Paging) return; 7094 7095 /** 7096 * Allows the paging component to retrieve its' stored state. 7097 */ 7098 F.Paging.prototype.readState = function(){ 7099 if (this.ft.state.paging) { 7100 var state = this.ft.state.get('paging'); 7101 if (F.is.hash(state)) { 7102 this.current = state.current; 7103 this.size = state.size; 7104 } 7105 } 7106 }; 7107 7108 /** 7109 * Allows the paging component to write its' current state to the store. 7110 */ 7111 F.Paging.prototype.writeState = function(){ 7112 if (this.ft.state.paging) { 7113 this.ft.state.set('paging', { 7114 current: this.current, 7115 size: this.size 7116 }); 7117 } 7118 }; 7119 7120 /** 7121 * Allows the paging component to clear any stored state. 7122 */ 7123 F.Paging.prototype.clearState = function(){ 7124 if (this.ft.state.paging) { 7125 this.ft.state.remove('paging'); 7126 } 7127 }; 7128 7129})(FooTable); 7130(function(F){ 7131 7132 if (!F.Sorting) return; 7133 7134 /** 7135 * Allows the sorting component to retrieve its' stored state. 7136 */ 7137 F.Sorting.prototype.readState = function(){ 7138 if (this.ft.state.sorting) { 7139 var state = this.ft.state.get('sorting'); 7140 if (F.is.hash(state)) { 7141 var column = this.ft.columns.get(state.column); 7142 if (column instanceof F.Column) { 7143 this.column = column; 7144 this.column.direction = state.direction; 7145 } 7146 } 7147 } 7148 }; 7149 7150 /** 7151 * Allows the sorting component to write its' current state to the store. 7152 */ 7153 F.Sorting.prototype.writeState = function(){ 7154 if (this.ft.state.sorting && this.column instanceof F.Column){ 7155 this.ft.state.set('sorting', { 7156 column: this.column.name, 7157 direction: this.column.direction 7158 }); 7159 } 7160 }; 7161 7162 /** 7163 * Allows the sorting component to clear any stored state. 7164 */ 7165 F.Sorting.prototype.clearState = function(){ 7166 if (this.ft.state.sorting) { 7167 this.ft.state.remove('sorting'); 7168 } 7169 }; 7170 7171})(FooTable); 7172(function(F){ 7173 7174 // hook into the _construct method so we can add the state property to the table. 7175 F.Table.extend('_construct', function(ready){ 7176 this.state = this.use(FooTable.State); 7177 return this._super(ready); 7178 }); 7179 7180 // hook into the _preinit method so we can trigger a plugin wide read state operation. 7181 F.Table.extend('_preinit', function(){ 7182 var self = this; 7183 return self._super().then(function(){ 7184 if (self.state.enabled){ 7185 self.state.read(); 7186 } 7187 }); 7188 }); 7189 7190 // hook into the draw method so we can trigger a plugin wide write state operation. 7191 F.Table.extend('draw', function(){ 7192 var self = this; 7193 return self._super().then(function(){ 7194 if (self.state.enabled){ 7195 self.state.write(); 7196 } 7197 }); 7198 }); 7199 7200})(FooTable); 7201(function($, F){ 7202 7203 F.Export = F.Component.extend(/** @lends FooTable.Export */{ 7204 /** 7205 * @summary This component provides some basic export functionality. 7206 * @memberof FooTable 7207 * @constructs Export 7208 * @param {FooTable.Table} table - The current instance of the plugin. 7209 */ 7210 construct: function(table){ 7211 // call the constructor of the base class 7212 this._super(table, true); 7213 /** 7214 * @summary A snapshot of the working set of rows prior to being trimmed by the paging component. 7215 * @memberof FooTable.Export# 7216 * @name snapshot 7217 * @type {FooTable.Row[]} 7218 */ 7219 this.snapshot = []; 7220 }, 7221 /** 7222 * @summary Hooks into the predraw pipeline after sorting and filtering have taken place but prior to paging. 7223 * @memberof FooTable.Export# 7224 * @function predraw 7225 * @description This method allows us to take a snapshot of the working set of rows before they are trimmed by the paging component and is called by the plugin instance. 7226 */ 7227 predraw: function(){ 7228 this.snapshot = this.ft.rows.array.slice(0); 7229 }, 7230 /** 7231 * @summary Return the columns as simple JavaScript objects in an array.
7232 * @memberof FooTable.Export# 7233 * @function columns 7234 * @returns {Object[]} 7235 */ 7236 columns: function(){ 7237 var result = []; 7238 F.arr.each(this.ft.columns.array, function(column){ 7239 if (!column.internal){ 7240 result.push({ 7241 type: column.type, 7242 name: column.name, 7243 title: column.title, 7244 visible: column.visible, 7245 hidden: column.hidden, 7246 classes: column.classes, 7247 style: column.style 7248 }); 7249 } 7250 }); 7251 return result; 7252 }, 7253 /** 7254 * @summary Return the rows as simple JavaScript objects in an array. 7255 * @memberof FooTable.Export# 7256 * @function rows 7257 * @param {boolean} [filtered=false] - Whether or not to exclude filtered rows from the result. 7258 * @returns {Object[]} 7259 */ 7260 rows: function(filtered){ 7261 filtered = F.is.boolean(filtered) ? filtered : false; 7262 var rows = filtered ? this.ft.rows.all : this.snapshot, result = []; 7263 F.arr.each(rows, function(row){ 7264 result.push(row.val()); 7265 }); 7266 return result; 7267 }, 7268 /** 7269 * @summary Return the columns and rows as a properly formatted JSON object. 7270 * @memberof FooTable.Export# 7271 * @function json 7272 * @param {boolean} [filtered=false] - Whether or not to exclude filtered rows from the result. 7273 * @returns {Object} 7274 */ 7275 json: function(filtered){ 7276 return JSON.parse(JSON.stringify({columns: this.columns(),rows: this.rows(filtered)})); 7277 }, 7278 /** 7279 * @summary Return the columns and rows as a properly formatted CSV value. 7280 * @memberof FooTable.Export# 7281 * @function csv 7282 * @param {boolean} [filtered=false] - Whether or not to exclude filtered rows from the result. 7283 * @returns {string} 7284 */ 7285 csv: function(filtered){ 7286 var csv = "", columns = this.columns(), value, escaped; 7287 F.arr.each(columns, function(column, i){ 7288 escaped = '"' + column.title.replace(/"/g, '""') + '"'; 7289 csv += (i === 0 ? escaped : "," + escaped); 7290 }); 7291 csv += "\n"; 7292 7293 var rows = filtered ? this.ft.rows.all : this.snapshot; 7294 F.arr.each(rows, function(row){ 7295 F.arr.each(row.cells, function(cell, i){ 7296 if (!cell.column.internal){ 7297 value = cell.column.stringify.call(cell.column, cell.value, cell.ft.o, cell.row.value); 7298 escaped = '"' + value.replace(/"/g, '""') + '"'; 7299 csv += (i === 0 ? escaped : "," + escaped); 7300 } 7301 }); 7302 csv += "\n"; 7303 }); 7304 return csv; 7305 } 7306 }); 7307 7308 // register the component using a priority of 490 which falls just after filtering (500) and before paging (400). 7309 F.components.register("export", F.Export, 490); 7310 7311})(jQuery, FooTable); 7312(function(F){ 7313 // this is used to define the filtering specific properties on column creation 7314 F.Column.prototype.__export_define__ = function(definition){ 7315 this.stringify = F.checkFnValue(this, definition.stringify, this.stringify); 7316 }; 7317 7318 // overrides the public define method and replaces it with our own 7319 F.Column.extend('define', function(definition){ 7320 this._super(definition); // call the base so we don't have to redefine any previously set properties 7321 this.__export_define__(definition); // then call our own 7322 }); 7323 7324 /** 7325 * @summary Return the supplied value as a string. 7326 * @memberof FooTable.Column# 7327 * @function stringify 7328 * @returns {string} 7329 */ 7330 F.Column.prototype.stringify = function(value, options, rowData){ 7331 return value + ""; 7332 }; 7333 7334 if (F.is.defined(F.DateColumn)){ 7335 // override the base method for DateColumns 7336 F.DateColumn.prototype.stringify = function(value, options, rowData){ 7337 return F.is.object(value) && F.is.boolean(value._isAMomentObject) && value.isValid() ? value.format(this.formatString) : ''; 7338 }; 7339 } 7340 7341 // override the base method for ObjectColumns 7342 F.ObjectColumn.prototype.stringify = function(value, options, rowData){ 7343 return F.is.object(value) ? JSON.stringify(value) : ""; 7344 }; 7345 7346 // override the base method for ArrayColumns 7347 F.ArrayColumn.prototype.stringify = function(value, options, rowData){ 7348 return F.is.array(value) ? JSON.stringify(value) : ""; 7349 }; 7350 7351})(FooTable); 7352(function(F){ 7353 /** 7354 * @summary Return the columns and rows as a properly formatted JSON object. 7355 * @memberof FooTable.Table# 7356 * @function toJSON 7357 * @param {boolean} [filtered=false] - Whether or not to exclude filtered rows from the result. 7358 * @returns {Object} 7359 */ 7360 F.Table.prototype.toJSON = function(filtered){ 7361 return this.use(F.Export).json(filtered); 7362 }; 7363 7364 /** 7365 * @summary Return the columns and rows as a properly formatted CSV value. 7366 * @memberof FooTable.Table# 7367 * @function toCSV 7368 * @param {boolean} [filtered=false] - Whether or not to exclude filtered rows from the result. 7369 * @returns {string} 7370 */ 7371 F.Table.prototype.toCSV = function(filtered){ 7372 return this.use(F.Export).csv(filtered); 7373 }; 7374 7375})(FooTable);
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.