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