code.gitea.io/gitea@v1.21.7/web_src/fomantic/build/semantic.js (about) 1 /* 2 * # Fomantic UI - 2.8.7 3 * https://github.com/fomantic/Fomantic-UI 4 * http://fomantic-ui.com/ 5 * 6 * Copyright 2014 Contributors 7 * Released under the MIT license 8 * http://opensource.org/licenses/MIT 9 * 10 */ 11 /*! 12 * # Fomantic-UI - API 13 * http://github.com/fomantic/Fomantic-UI/ 14 * 15 * 16 * Released under the MIT license 17 * http://opensource.org/licenses/MIT 18 * 19 */ 20 21 ;(function ($, window, document, undefined) { 22 23 'use strict'; 24 25 $.isWindow = $.isWindow || function(obj) { 26 return obj != null && obj === obj.window; 27 }; 28 29 window = (typeof window != 'undefined' && window.Math == Math) 30 ? window 31 : (typeof self != 'undefined' && self.Math == Math) 32 ? self 33 : Function('return this')() 34 ; 35 36 $.api = $.fn.api = function(parameters) { 37 38 var 39 // use window context if none specified 40 $allModules = $.isFunction(this) 41 ? $(window) 42 : $(this), 43 moduleSelector = $allModules.selector || '', 44 time = new Date().getTime(), 45 performance = [], 46 47 query = arguments[0], 48 methodInvoked = (typeof query == 'string'), 49 queryArguments = [].slice.call(arguments, 1), 50 51 returnedValue 52 ; 53 54 $allModules 55 .each(function() { 56 var 57 settings = ( $.isPlainObject(parameters) ) 58 ? $.extend(true, {}, $.fn.api.settings, parameters) 59 : $.extend({}, $.fn.api.settings), 60 61 // internal aliases 62 namespace = settings.namespace, 63 metadata = settings.metadata, 64 selector = settings.selector, 65 error = settings.error, 66 className = settings.className, 67 68 // define namespaces for modules 69 eventNamespace = '.' + namespace, 70 moduleNamespace = 'module-' + namespace, 71 72 // element that creates request 73 $module = $(this), 74 $form = $module.closest(selector.form), 75 76 // context used for state 77 $context = (settings.stateContext) 78 ? $(settings.stateContext) 79 : $module, 80 81 // request details 82 ajaxSettings, 83 requestSettings, 84 url, 85 data, 86 requestStartTime, 87 88 // standard module 89 element = this, 90 context = $context[0], 91 instance = $module.data(moduleNamespace), 92 module 93 ; 94 95 module = { 96 97 initialize: function() { 98 if(!methodInvoked) { 99 module.bind.events(); 100 } 101 module.instantiate(); 102 }, 103 104 instantiate: function() { 105 module.verbose('Storing instance of module', module); 106 instance = module; 107 $module 108 .data(moduleNamespace, instance) 109 ; 110 }, 111 112 destroy: function() { 113 module.verbose('Destroying previous module for', element); 114 $module 115 .removeData(moduleNamespace) 116 .off(eventNamespace) 117 ; 118 }, 119 120 bind: { 121 events: function() { 122 var 123 triggerEvent = module.get.event() 124 ; 125 if( triggerEvent ) { 126 module.verbose('Attaching API events to element', triggerEvent); 127 $module 128 .on(triggerEvent + eventNamespace, module.event.trigger) 129 ; 130 } 131 else if(settings.on == 'now') { 132 module.debug('Querying API endpoint immediately'); 133 module.query(); 134 } 135 } 136 }, 137 138 decode: { 139 json: function(response) { 140 if(response !== undefined && typeof response == 'string') { 141 try { 142 response = JSON.parse(response); 143 } 144 catch(e) { 145 // isnt json string 146 } 147 } 148 return response; 149 } 150 }, 151 152 read: { 153 cachedResponse: function(url) { 154 var 155 response 156 ; 157 if(window.Storage === undefined) { 158 module.error(error.noStorage); 159 return; 160 } 161 response = sessionStorage.getItem(url); 162 module.debug('Using cached response', url, response); 163 response = module.decode.json(response); 164 return response; 165 } 166 }, 167 write: { 168 cachedResponse: function(url, response) { 169 if(response && response === '') { 170 module.debug('Response empty, not caching', response); 171 return; 172 } 173 if(window.Storage === undefined) { 174 module.error(error.noStorage); 175 return; 176 } 177 if( $.isPlainObject(response) ) { 178 response = JSON.stringify(response); 179 } 180 sessionStorage.setItem(url, response); 181 module.verbose('Storing cached response for url', url, response); 182 } 183 }, 184 185 query: function() { 186 187 if(module.is.disabled()) { 188 module.debug('Element is disabled API request aborted'); 189 return; 190 } 191 192 if(module.is.loading()) { 193 if(settings.interruptRequests) { 194 module.debug('Interrupting previous request'); 195 module.abort(); 196 } 197 else { 198 module.debug('Cancelling request, previous request is still pending'); 199 return; 200 } 201 } 202 203 // pass element metadata to url (value, text) 204 if(settings.defaultData) { 205 $.extend(true, settings.urlData, module.get.defaultData()); 206 } 207 208 // Add form content 209 if(settings.serializeForm) { 210 settings.data = module.add.formData(settings.data); 211 } 212 213 // call beforesend and get any settings changes 214 requestSettings = module.get.settings(); 215 216 // check if before send cancelled request 217 if(requestSettings === false) { 218 module.cancelled = true; 219 module.error(error.beforeSend); 220 return; 221 } 222 else { 223 module.cancelled = false; 224 } 225 226 // get url 227 url = module.get.templatedURL(); 228 229 if(!url && !module.is.mocked()) { 230 module.error(error.missingURL); 231 return; 232 } 233 234 // replace variables 235 url = module.add.urlData( url ); 236 // missing url parameters 237 if( !url && !module.is.mocked()) { 238 return; 239 } 240 241 requestSettings.url = settings.base + url; 242 243 // look for jQuery ajax parameters in settings 244 ajaxSettings = $.extend(true, {}, settings, { 245 type : settings.method || settings.type, 246 data : data, 247 url : settings.base + url, 248 beforeSend : settings.beforeXHR, 249 success : function() {}, 250 failure : function() {}, 251 complete : function() {} 252 }); 253 254 module.debug('Querying URL', ajaxSettings.url); 255 module.verbose('Using AJAX settings', ajaxSettings); 256 if(settings.cache === 'local' && module.read.cachedResponse(url)) { 257 module.debug('Response returned from local cache'); 258 module.request = module.create.request(); 259 module.request.resolveWith(context, [ module.read.cachedResponse(url) ]); 260 return; 261 } 262 263 if( !settings.throttle ) { 264 module.debug('Sending request', data, ajaxSettings.method); 265 module.send.request(); 266 } 267 else { 268 if(!settings.throttleFirstRequest && !module.timer) { 269 module.debug('Sending request', data, ajaxSettings.method); 270 module.send.request(); 271 module.timer = setTimeout(function(){}, settings.throttle); 272 } 273 else { 274 module.debug('Throttling request', settings.throttle); 275 clearTimeout(module.timer); 276 module.timer = setTimeout(function() { 277 if(module.timer) { 278 delete module.timer; 279 } 280 module.debug('Sending throttled request', data, ajaxSettings.method); 281 module.send.request(); 282 }, settings.throttle); 283 } 284 } 285 286 }, 287 288 should: { 289 removeError: function() { 290 return ( settings.hideError === true || (settings.hideError === 'auto' && !module.is.form()) ); 291 } 292 }, 293 294 is: { 295 disabled: function() { 296 return ($module.filter(selector.disabled).length > 0); 297 }, 298 expectingJSON: function() { 299 return settings.dataType === 'json' || settings.dataType === 'jsonp'; 300 }, 301 form: function() { 302 return $module.is('form') || $context.is('form'); 303 }, 304 mocked: function() { 305 return (settings.mockResponse || settings.mockResponseAsync || settings.response || settings.responseAsync); 306 }, 307 input: function() { 308 return $module.is('input'); 309 }, 310 loading: function() { 311 return (module.request) 312 ? (module.request.state() == 'pending') 313 : false 314 ; 315 }, 316 abortedRequest: function(xhr) { 317 if(xhr && xhr.readyState !== undefined && xhr.readyState === 0) { 318 module.verbose('XHR request determined to be aborted'); 319 return true; 320 } 321 else { 322 module.verbose('XHR request was not aborted'); 323 return false; 324 } 325 }, 326 validResponse: function(response) { 327 if( (!module.is.expectingJSON()) || !$.isFunction(settings.successTest) ) { 328 module.verbose('Response is not JSON, skipping validation', settings.successTest, response); 329 return true; 330 } 331 module.debug('Checking JSON returned success', settings.successTest, response); 332 if( settings.successTest(response) ) { 333 module.debug('Response passed success test', response); 334 return true; 335 } 336 else { 337 module.debug('Response failed success test', response); 338 return false; 339 } 340 } 341 }, 342 343 was: { 344 cancelled: function() { 345 return (module.cancelled || false); 346 }, 347 succesful: function() { 348 module.verbose('This behavior will be deleted due to typo. Use "was successful" instead.'); 349 return module.was.successful(); 350 }, 351 successful: function() { 352 return (module.request && module.request.state() == 'resolved'); 353 }, 354 failure: function() { 355 return (module.request && module.request.state() == 'rejected'); 356 }, 357 complete: function() { 358 return (module.request && (module.request.state() == 'resolved' || module.request.state() == 'rejected') ); 359 } 360 }, 361 362 add: { 363 urlData: function(url, urlData) { 364 var 365 requiredVariables, 366 optionalVariables 367 ; 368 if(url) { 369 requiredVariables = url.match(settings.regExp.required); 370 optionalVariables = url.match(settings.regExp.optional); 371 urlData = urlData || settings.urlData; 372 if(requiredVariables) { 373 module.debug('Looking for required URL variables', requiredVariables); 374 $.each(requiredVariables, function(index, templatedString) { 375 var 376 // allow legacy {$var} style 377 variable = (templatedString.indexOf('$') !== -1) 378 ? templatedString.substr(2, templatedString.length - 3) 379 : templatedString.substr(1, templatedString.length - 2), 380 value = ($.isPlainObject(urlData) && urlData[variable] !== undefined) 381 ? urlData[variable] 382 : ($module.data(variable) !== undefined) 383 ? $module.data(variable) 384 : ($context.data(variable) !== undefined) 385 ? $context.data(variable) 386 : urlData[variable] 387 ; 388 // remove value 389 if(value === undefined) { 390 module.error(error.requiredParameter, variable, url); 391 url = false; 392 return false; 393 } 394 else { 395 module.verbose('Found required variable', variable, value); 396 value = (settings.encodeParameters) 397 ? module.get.urlEncodedValue(value) 398 : value 399 ; 400 url = url.replace(templatedString, value); 401 } 402 }); 403 } 404 if(optionalVariables) { 405 module.debug('Looking for optional URL variables', requiredVariables); 406 $.each(optionalVariables, function(index, templatedString) { 407 var 408 // allow legacy {/$var} style 409 variable = (templatedString.indexOf('$') !== -1) 410 ? templatedString.substr(3, templatedString.length - 4) 411 : templatedString.substr(2, templatedString.length - 3), 412 value = ($.isPlainObject(urlData) && urlData[variable] !== undefined) 413 ? urlData[variable] 414 : ($module.data(variable) !== undefined) 415 ? $module.data(variable) 416 : ($context.data(variable) !== undefined) 417 ? $context.data(variable) 418 : urlData[variable] 419 ; 420 // optional replacement 421 if(value !== undefined) { 422 module.verbose('Optional variable Found', variable, value); 423 url = url.replace(templatedString, value); 424 } 425 else { 426 module.verbose('Optional variable not found', variable); 427 // remove preceding slash if set 428 if(url.indexOf('/' + templatedString) !== -1) { 429 url = url.replace('/' + templatedString, ''); 430 } 431 else { 432 url = url.replace(templatedString, ''); 433 } 434 } 435 }); 436 } 437 } 438 return url; 439 }, 440 formData: function(data) { 441 var 442 canSerialize = ($.fn.serializeObject !== undefined), 443 formData = (canSerialize) 444 ? $form.serializeObject() 445 : $form.serialize(), 446 hasOtherData 447 ; 448 data = data || settings.data; 449 hasOtherData = $.isPlainObject(data); 450 451 if(hasOtherData) { 452 if(canSerialize) { 453 module.debug('Extending existing data with form data', data, formData); 454 data = $.extend(true, {}, data, formData); 455 } 456 else { 457 module.error(error.missingSerialize); 458 module.debug('Cant extend data. Replacing data with form data', data, formData); 459 data = formData; 460 } 461 } 462 else { 463 module.debug('Adding form data', formData); 464 data = formData; 465 } 466 return data; 467 } 468 }, 469 470 send: { 471 request: function() { 472 module.set.loading(); 473 module.request = module.create.request(); 474 if( module.is.mocked() ) { 475 module.mockedXHR = module.create.mockedXHR(); 476 } 477 else { 478 module.xhr = module.create.xhr(); 479 } 480 settings.onRequest.call(context, module.request, module.xhr); 481 } 482 }, 483 484 event: { 485 trigger: function(event) { 486 module.query(); 487 if(event.type == 'submit' || event.type == 'click') { 488 event.preventDefault(); 489 } 490 }, 491 xhr: { 492 always: function() { 493 // nothing special 494 }, 495 done: function(response, textStatus, xhr) { 496 var 497 context = this, 498 elapsedTime = (new Date().getTime() - requestStartTime), 499 timeLeft = (settings.loadingDuration - elapsedTime), 500 translatedResponse = ( $.isFunction(settings.onResponse) ) 501 ? module.is.expectingJSON() && !settings.rawResponse 502 ? settings.onResponse.call(context, $.extend(true, {}, response)) 503 : settings.onResponse.call(context, response) 504 : false 505 ; 506 timeLeft = (timeLeft > 0) 507 ? timeLeft 508 : 0 509 ; 510 if(translatedResponse) { 511 module.debug('Modified API response in onResponse callback', settings.onResponse, translatedResponse, response); 512 response = translatedResponse; 513 } 514 if(timeLeft > 0) { 515 module.debug('Response completed early delaying state change by', timeLeft); 516 } 517 setTimeout(function() { 518 if( module.is.validResponse(response) ) { 519 module.request.resolveWith(context, [response, xhr]); 520 } 521 else { 522 module.request.rejectWith(context, [xhr, 'invalid']); 523 } 524 }, timeLeft); 525 }, 526 fail: function(xhr, status, httpMessage) { 527 var 528 context = this, 529 elapsedTime = (new Date().getTime() - requestStartTime), 530 timeLeft = (settings.loadingDuration - elapsedTime) 531 ; 532 timeLeft = (timeLeft > 0) 533 ? timeLeft 534 : 0 535 ; 536 if(timeLeft > 0) { 537 module.debug('Response completed early delaying state change by', timeLeft); 538 } 539 setTimeout(function() { 540 if( module.is.abortedRequest(xhr) ) { 541 module.request.rejectWith(context, [xhr, 'aborted', httpMessage]); 542 } 543 else { 544 module.request.rejectWith(context, [xhr, 'error', status, httpMessage]); 545 } 546 }, timeLeft); 547 } 548 }, 549 request: { 550 done: function(response, xhr) { 551 module.debug('Successful API Response', response); 552 if(settings.cache === 'local' && url) { 553 module.write.cachedResponse(url, response); 554 module.debug('Saving server response locally', module.cache); 555 } 556 settings.onSuccess.call(context, response, $module, xhr); 557 }, 558 complete: function(firstParameter, secondParameter) { 559 var 560 xhr, 561 response 562 ; 563 // have to guess callback parameters based on request success 564 if( module.was.successful() ) { 565 response = firstParameter; 566 xhr = secondParameter; 567 } 568 else { 569 xhr = firstParameter; 570 response = module.get.responseFromXHR(xhr); 571 } 572 module.remove.loading(); 573 settings.onComplete.call(context, response, $module, xhr); 574 }, 575 fail: function(xhr, status, httpMessage) { 576 var 577 // pull response from xhr if available 578 response = module.get.responseFromXHR(xhr), 579 errorMessage = module.get.errorFromRequest(response, status, httpMessage) 580 ; 581 if(status == 'aborted') { 582 module.debug('XHR Aborted (Most likely caused by page navigation or CORS Policy)', status, httpMessage); 583 settings.onAbort.call(context, status, $module, xhr); 584 return true; 585 } 586 else if(status == 'invalid') { 587 module.debug('JSON did not pass success test. A server-side error has most likely occurred', response); 588 } 589 else if(status == 'error') { 590 if(xhr !== undefined) { 591 module.debug('XHR produced a server error', status, httpMessage); 592 // make sure we have an error to display to console 593 if( (xhr.status < 200 || xhr.status >= 300) && httpMessage !== undefined && httpMessage !== '') { 594 module.error(error.statusMessage + httpMessage, ajaxSettings.url); 595 } 596 settings.onError.call(context, errorMessage, $module, xhr); 597 } 598 } 599 600 if(settings.errorDuration && status !== 'aborted') { 601 module.debug('Adding error state'); 602 module.set.error(); 603 if( module.should.removeError() ) { 604 setTimeout(module.remove.error, settings.errorDuration); 605 } 606 } 607 module.debug('API Request failed', errorMessage, xhr); 608 settings.onFailure.call(context, response, $module, xhr); 609 } 610 } 611 }, 612 613 create: { 614 615 request: function() { 616 // api request promise 617 return $.Deferred() 618 .always(module.event.request.complete) 619 .done(module.event.request.done) 620 .fail(module.event.request.fail) 621 ; 622 }, 623 624 mockedXHR: function () { 625 var 626 // xhr does not simulate these properties of xhr but must return them 627 textStatus = false, 628 status = false, 629 httpMessage = false, 630 responder = settings.mockResponse || settings.response, 631 asyncResponder = settings.mockResponseAsync || settings.responseAsync, 632 asyncCallback, 633 response, 634 mockedXHR 635 ; 636 637 mockedXHR = $.Deferred() 638 .always(module.event.xhr.complete) 639 .done(module.event.xhr.done) 640 .fail(module.event.xhr.fail) 641 ; 642 643 if(responder) { 644 if( $.isFunction(responder) ) { 645 module.debug('Using specified synchronous callback', responder); 646 response = responder.call(context, requestSettings); 647 } 648 else { 649 module.debug('Using settings specified response', responder); 650 response = responder; 651 } 652 // simulating response 653 mockedXHR.resolveWith(context, [ response, textStatus, { responseText: response }]); 654 } 655 else if( $.isFunction(asyncResponder) ) { 656 asyncCallback = function(response) { 657 module.debug('Async callback returned response', response); 658 659 if(response) { 660 mockedXHR.resolveWith(context, [ response, textStatus, { responseText: response }]); 661 } 662 else { 663 mockedXHR.rejectWith(context, [{ responseText: response }, status, httpMessage]); 664 } 665 }; 666 module.debug('Using specified async response callback', asyncResponder); 667 asyncResponder.call(context, requestSettings, asyncCallback); 668 } 669 return mockedXHR; 670 }, 671 672 xhr: function() { 673 var 674 xhr 675 ; 676 // ajax request promise 677 xhr = $.ajax(ajaxSettings) 678 .always(module.event.xhr.always) 679 .done(module.event.xhr.done) 680 .fail(module.event.xhr.fail) 681 ; 682 module.verbose('Created server request', xhr, ajaxSettings); 683 return xhr; 684 } 685 }, 686 687 set: { 688 error: function() { 689 module.verbose('Adding error state to element', $context); 690 $context.addClass(className.error); 691 }, 692 loading: function() { 693 module.verbose('Adding loading state to element', $context); 694 $context.addClass(className.loading); 695 requestStartTime = new Date().getTime(); 696 } 697 }, 698 699 remove: { 700 error: function() { 701 module.verbose('Removing error state from element', $context); 702 $context.removeClass(className.error); 703 }, 704 loading: function() { 705 module.verbose('Removing loading state from element', $context); 706 $context.removeClass(className.loading); 707 } 708 }, 709 710 get: { 711 responseFromXHR: function(xhr) { 712 return $.isPlainObject(xhr) 713 ? (module.is.expectingJSON()) 714 ? module.decode.json(xhr.responseText) 715 : xhr.responseText 716 : false 717 ; 718 }, 719 errorFromRequest: function(response, status, httpMessage) { 720 return ($.isPlainObject(response) && response.error !== undefined) 721 ? response.error // use json error message 722 : (settings.error[status] !== undefined) // use server error message 723 ? settings.error[status] 724 : httpMessage 725 ; 726 }, 727 request: function() { 728 return module.request || false; 729 }, 730 xhr: function() { 731 return module.xhr || false; 732 }, 733 settings: function() { 734 var 735 runSettings 736 ; 737 runSettings = settings.beforeSend.call($module, settings); 738 if(runSettings) { 739 if(runSettings.success !== undefined) { 740 module.debug('Legacy success callback detected', runSettings); 741 module.error(error.legacyParameters, runSettings.success); 742 runSettings.onSuccess = runSettings.success; 743 } 744 if(runSettings.failure !== undefined) { 745 module.debug('Legacy failure callback detected', runSettings); 746 module.error(error.legacyParameters, runSettings.failure); 747 runSettings.onFailure = runSettings.failure; 748 } 749 if(runSettings.complete !== undefined) { 750 module.debug('Legacy complete callback detected', runSettings); 751 module.error(error.legacyParameters, runSettings.complete); 752 runSettings.onComplete = runSettings.complete; 753 } 754 } 755 if(runSettings === undefined) { 756 module.error(error.noReturnedValue); 757 } 758 if(runSettings === false) { 759 return runSettings; 760 } 761 return (runSettings !== undefined) 762 ? $.extend(true, {}, runSettings) 763 : $.extend(true, {}, settings) 764 ; 765 }, 766 urlEncodedValue: function(value) { 767 var 768 decodedValue = window.decodeURIComponent(value), 769 encodedValue = window.encodeURIComponent(value), 770 alreadyEncoded = (decodedValue !== value) 771 ; 772 if(alreadyEncoded) { 773 module.debug('URL value is already encoded, avoiding double encoding', value); 774 return value; 775 } 776 module.verbose('Encoding value using encodeURIComponent', value, encodedValue); 777 return encodedValue; 778 }, 779 defaultData: function() { 780 var 781 data = {} 782 ; 783 if( !$.isWindow(element) ) { 784 if( module.is.input() ) { 785 data.value = $module.val(); 786 } 787 else if( module.is.form() ) { 788 789 } 790 else { 791 data.text = $module.text(); 792 } 793 } 794 return data; 795 }, 796 event: function() { 797 if( $.isWindow(element) || settings.on == 'now' ) { 798 module.debug('API called without element, no events attached'); 799 return false; 800 } 801 else if(settings.on == 'auto') { 802 if( $module.is('input') ) { 803 return (element.oninput !== undefined) 804 ? 'input' 805 : (element.onpropertychange !== undefined) 806 ? 'propertychange' 807 : 'keyup' 808 ; 809 } 810 else if( $module.is('form') ) { 811 return 'submit'; 812 } 813 else { 814 return 'click'; 815 } 816 } 817 else { 818 return settings.on; 819 } 820 }, 821 templatedURL: function(action) { 822 action = action || $module.data(metadata.action) || settings.action || false; 823 url = $module.data(metadata.url) || settings.url || false; 824 if(url) { 825 module.debug('Using specified url', url); 826 return url; 827 } 828 if(action) { 829 module.debug('Looking up url for action', action, settings.api); 830 if(settings.api[action] === undefined && !module.is.mocked()) { 831 module.error(error.missingAction, settings.action, settings.api); 832 return; 833 } 834 url = settings.api[action]; 835 } 836 else if( module.is.form() ) { 837 url = $module.attr('action') || $context.attr('action') || false; 838 module.debug('No url or action specified, defaulting to form action', url); 839 } 840 return url; 841 } 842 }, 843 844 abort: function() { 845 var 846 xhr = module.get.xhr() 847 ; 848 if( xhr && xhr.state() !== 'resolved') { 849 module.debug('Cancelling API request'); 850 xhr.abort(); 851 } 852 }, 853 854 // reset state 855 reset: function() { 856 module.remove.error(); 857 module.remove.loading(); 858 }, 859 860 setting: function(name, value) { 861 module.debug('Changing setting', name, value); 862 if( $.isPlainObject(name) ) { 863 $.extend(true, settings, name); 864 } 865 else if(value !== undefined) { 866 if($.isPlainObject(settings[name])) { 867 $.extend(true, settings[name], value); 868 } 869 else { 870 settings[name] = value; 871 } 872 } 873 else { 874 return settings[name]; 875 } 876 }, 877 internal: function(name, value) { 878 if( $.isPlainObject(name) ) { 879 $.extend(true, module, name); 880 } 881 else if(value !== undefined) { 882 module[name] = value; 883 } 884 else { 885 return module[name]; 886 } 887 }, 888 debug: function() { 889 if(!settings.silent && settings.debug) { 890 if(settings.performance) { 891 module.performance.log(arguments); 892 } 893 else { 894 module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); 895 module.debug.apply(console, arguments); 896 } 897 } 898 }, 899 verbose: function() { 900 if(!settings.silent && settings.verbose && settings.debug) { 901 if(settings.performance) { 902 module.performance.log(arguments); 903 } 904 else { 905 module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); 906 module.verbose.apply(console, arguments); 907 } 908 } 909 }, 910 error: function() { 911 if(!settings.silent) { 912 module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); 913 module.error.apply(console, arguments); 914 } 915 }, 916 performance: { 917 log: function(message) { 918 var 919 currentTime, 920 executionTime, 921 previousTime 922 ; 923 if(settings.performance) { 924 currentTime = new Date().getTime(); 925 previousTime = time || currentTime; 926 executionTime = currentTime - previousTime; 927 time = currentTime; 928 performance.push({ 929 'Name' : message[0], 930 'Arguments' : [].slice.call(message, 1) || '', 931 //'Element' : element, 932 'Execution Time' : executionTime 933 }); 934 } 935 clearTimeout(module.performance.timer); 936 module.performance.timer = setTimeout(module.performance.display, 500); 937 }, 938 display: function() { 939 var 940 title = settings.name + ':', 941 totalTime = 0 942 ; 943 time = false; 944 clearTimeout(module.performance.timer); 945 $.each(performance, function(index, data) { 946 totalTime += data['Execution Time']; 947 }); 948 title += ' ' + totalTime + 'ms'; 949 if(moduleSelector) { 950 title += ' \'' + moduleSelector + '\''; 951 } 952 if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { 953 console.groupCollapsed(title); 954 if(console.table) { 955 console.table(performance); 956 } 957 else { 958 $.each(performance, function(index, data) { 959 console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); 960 }); 961 } 962 console.groupEnd(); 963 } 964 performance = []; 965 } 966 }, 967 invoke: function(query, passedArguments, context) { 968 var 969 object = instance, 970 maxDepth, 971 found, 972 response 973 ; 974 passedArguments = passedArguments || queryArguments; 975 context = element || context; 976 if(typeof query == 'string' && object !== undefined) { 977 query = query.split(/[\. ]/); 978 maxDepth = query.length - 1; 979 $.each(query, function(depth, value) { 980 var camelCaseValue = (depth != maxDepth) 981 ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) 982 : query 983 ; 984 if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { 985 object = object[camelCaseValue]; 986 } 987 else if( object[camelCaseValue] !== undefined ) { 988 found = object[camelCaseValue]; 989 return false; 990 } 991 else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { 992 object = object[value]; 993 } 994 else if( object[value] !== undefined ) { 995 found = object[value]; 996 return false; 997 } 998 else { 999 module.error(error.method, query); 1000 return false; 1001 } 1002 }); 1003 } 1004 if ( $.isFunction( found ) ) { 1005 response = found.apply(context, passedArguments); 1006 } 1007 else if(found !== undefined) { 1008 response = found; 1009 } 1010 if(Array.isArray(returnedValue)) { 1011 returnedValue.push(response); 1012 } 1013 else if(returnedValue !== undefined) { 1014 returnedValue = [returnedValue, response]; 1015 } 1016 else if(response !== undefined) { 1017 returnedValue = response; 1018 } 1019 return found; 1020 } 1021 }; 1022 1023 if(methodInvoked) { 1024 if(instance === undefined) { 1025 module.initialize(); 1026 } 1027 module.invoke(query); 1028 } 1029 else { 1030 if(instance !== undefined) { 1031 instance.invoke('destroy'); 1032 } 1033 module.initialize(); 1034 } 1035 }) 1036 ; 1037 1038 return (returnedValue !== undefined) 1039 ? returnedValue 1040 : this 1041 ; 1042 }; 1043 1044 $.api.settings = { 1045 1046 name : 'API', 1047 namespace : 'api', 1048 1049 debug : false, 1050 verbose : false, 1051 performance : true, 1052 1053 // object containing all templates endpoints 1054 api : {}, 1055 1056 // whether to cache responses 1057 cache : true, 1058 1059 // whether new requests should abort previous requests 1060 interruptRequests : true, 1061 1062 // event binding 1063 on : 'auto', 1064 1065 // context for applying state classes 1066 stateContext : false, 1067 1068 // duration for loading state 1069 loadingDuration : 0, 1070 1071 // whether to hide errors after a period of time 1072 hideError : 'auto', 1073 1074 // duration for error state 1075 errorDuration : 2000, 1076 1077 // whether parameters should be encoded with encodeURIComponent 1078 encodeParameters : true, 1079 1080 // API action to use 1081 action : false, 1082 1083 // templated URL to use 1084 url : false, 1085 1086 // base URL to apply to all endpoints 1087 base : '', 1088 1089 // data that will 1090 urlData : {}, 1091 1092 // whether to add default data to url data 1093 defaultData : true, 1094 1095 // whether to serialize closest form 1096 serializeForm : false, 1097 1098 // how long to wait before request should occur 1099 throttle : 0, 1100 1101 // whether to throttle first request or only repeated 1102 throttleFirstRequest : true, 1103 1104 // standard ajax settings 1105 method : 'get', 1106 data : {}, 1107 dataType : 'json', 1108 1109 // mock response 1110 mockResponse : false, 1111 mockResponseAsync : false, 1112 1113 // aliases for mock 1114 response : false, 1115 responseAsync : false, 1116 1117 // whether onResponse should work with response value without force converting into an object 1118 rawResponse : false, 1119 1120 // callbacks before request 1121 beforeSend : function(settings) { return settings; }, 1122 beforeXHR : function(xhr) {}, 1123 onRequest : function(promise, xhr) {}, 1124 1125 // after request 1126 onResponse : false, // function(response) { }, 1127 1128 // response was successful, if JSON passed validation 1129 onSuccess : function(response, $module) {}, 1130 1131 // request finished without aborting 1132 onComplete : function(response, $module) {}, 1133 1134 // failed JSON success test 1135 onFailure : function(response, $module) {}, 1136 1137 // server error 1138 onError : function(errorMessage, $module) {}, 1139 1140 // request aborted 1141 onAbort : function(errorMessage, $module) {}, 1142 1143 successTest : false, 1144 1145 // errors 1146 error : { 1147 beforeSend : 'The before send function has aborted the request', 1148 error : 'There was an error with your request', 1149 exitConditions : 'API Request Aborted. Exit conditions met', 1150 JSONParse : 'JSON could not be parsed during error handling', 1151 legacyParameters : 'You are using legacy API success callback names', 1152 method : 'The method you called is not defined', 1153 missingAction : 'API action used but no url was defined', 1154 missingSerialize : 'jquery-serialize-object is required to add form data to an existing data object', 1155 missingURL : 'No URL specified for api event', 1156 noReturnedValue : 'The beforeSend callback must return a settings object, beforeSend ignored.', 1157 noStorage : 'Caching responses locally requires session storage', 1158 parseError : 'There was an error parsing your request', 1159 requiredParameter : 'Missing a required URL parameter: ', 1160 statusMessage : 'Server gave an error: ', 1161 timeout : 'Your request timed out' 1162 }, 1163 1164 regExp : { 1165 required : /\{\$*[A-z0-9]+\}/g, 1166 optional : /\{\/\$*[A-z0-9]+\}/g, 1167 }, 1168 1169 className: { 1170 loading : 'loading', 1171 error : 'error' 1172 }, 1173 1174 selector: { 1175 disabled : '.disabled', 1176 form : 'form' 1177 }, 1178 1179 metadata: { 1180 action : 'action', 1181 url : 'url' 1182 } 1183 }; 1184 1185 1186 1187 })( jQuery, window, document ); 1188 1189 /*! 1190 * # Fomantic-UI - Checkbox 1191 * http://github.com/fomantic/Fomantic-UI/ 1192 * 1193 * 1194 * Released under the MIT license 1195 * http://opensource.org/licenses/MIT 1196 * 1197 */ 1198 1199 ;(function ($, window, document, undefined) { 1200 1201 'use strict'; 1202 1203 $.isFunction = $.isFunction || function(obj) { 1204 return typeof obj === "function" && typeof obj.nodeType !== "number"; 1205 }; 1206 1207 window = (typeof window != 'undefined' && window.Math == Math) 1208 ? window 1209 : (typeof self != 'undefined' && self.Math == Math) 1210 ? self 1211 : Function('return this')() 1212 ; 1213 1214 $.fn.checkbox = function(parameters) { 1215 var 1216 $allModules = $(this), 1217 moduleSelector = $allModules.selector || '', 1218 1219 time = new Date().getTime(), 1220 performance = [], 1221 1222 query = arguments[0], 1223 methodInvoked = (typeof query == 'string'), 1224 queryArguments = [].slice.call(arguments, 1), 1225 returnedValue 1226 ; 1227 1228 $allModules 1229 .each(function() { 1230 var 1231 settings = $.extend(true, {}, $.fn.checkbox.settings, parameters), 1232 1233 className = settings.className, 1234 namespace = settings.namespace, 1235 selector = settings.selector, 1236 error = settings.error, 1237 1238 eventNamespace = '.' + namespace, 1239 moduleNamespace = 'module-' + namespace, 1240 1241 $module = $(this), 1242 $label = $(this).children(selector.label), 1243 $input = $(this).children(selector.input), 1244 input = $input[0], 1245 1246 initialLoad = false, 1247 shortcutPressed = false, 1248 instance = $module.data(moduleNamespace), 1249 1250 observer, 1251 element = this, 1252 module 1253 ; 1254 1255 module = { 1256 1257 initialize: function() { 1258 module.verbose('Initializing checkbox', settings); 1259 1260 module.create.label(); 1261 module.bind.events(); 1262 1263 module.set.tabbable(); 1264 module.hide.input(); 1265 1266 module.observeChanges(); 1267 module.instantiate(); 1268 module.setup(); 1269 }, 1270 1271 instantiate: function() { 1272 module.verbose('Storing instance of module', module); 1273 instance = module; 1274 $module 1275 .data(moduleNamespace, module) 1276 ; 1277 }, 1278 1279 destroy: function() { 1280 module.verbose('Destroying module'); 1281 module.unbind.events(); 1282 module.show.input(); 1283 $module.removeData(moduleNamespace); 1284 }, 1285 1286 fix: { 1287 reference: function() { 1288 if( $module.is(selector.input) ) { 1289 module.debug('Behavior called on <input> adjusting invoked element'); 1290 $module = $module.closest(selector.checkbox); 1291 module.refresh(); 1292 } 1293 } 1294 }, 1295 1296 setup: function() { 1297 module.set.initialLoad(); 1298 if( module.is.indeterminate() ) { 1299 module.debug('Initial value is indeterminate'); 1300 module.indeterminate(); 1301 } 1302 else if( module.is.checked() ) { 1303 module.debug('Initial value is checked'); 1304 module.check(); 1305 } 1306 else { 1307 module.debug('Initial value is unchecked'); 1308 module.uncheck(); 1309 } 1310 module.remove.initialLoad(); 1311 }, 1312 1313 refresh: function() { 1314 $label = $module.children(selector.label); 1315 $input = $module.children(selector.input); 1316 input = $input[0]; 1317 }, 1318 1319 hide: { 1320 input: function() { 1321 module.verbose('Modifying <input> z-index to be unselectable'); 1322 $input.addClass(className.hidden); 1323 } 1324 }, 1325 show: { 1326 input: function() { 1327 module.verbose('Modifying <input> z-index to be selectable'); 1328 $input.removeClass(className.hidden); 1329 } 1330 }, 1331 1332 observeChanges: function() { 1333 if('MutationObserver' in window) { 1334 observer = new MutationObserver(function(mutations) { 1335 module.debug('DOM tree modified, updating selector cache'); 1336 module.refresh(); 1337 }); 1338 observer.observe(element, { 1339 childList : true, 1340 subtree : true 1341 }); 1342 module.debug('Setting up mutation observer', observer); 1343 } 1344 }, 1345 1346 attachEvents: function(selector, event) { 1347 var 1348 $element = $(selector) 1349 ; 1350 event = $.isFunction(module[event]) 1351 ? module[event] 1352 : module.toggle 1353 ; 1354 if($element.length > 0) { 1355 module.debug('Attaching checkbox events to element', selector, event); 1356 $element 1357 .on('click' + eventNamespace, event) 1358 ; 1359 } 1360 else { 1361 module.error(error.notFound); 1362 } 1363 }, 1364 1365 preventDefaultOnInputTarget: function() { 1366 if(typeof event !== 'undefined' && event !== null && $(event.target).is(selector.input)) { 1367 module.verbose('Preventing default check action after manual check action'); 1368 event.preventDefault(); 1369 } 1370 }, 1371 1372 event: { 1373 change: function(event) { 1374 if( !module.should.ignoreCallbacks() ) { 1375 settings.onChange.call(input); 1376 } 1377 }, 1378 click: function(event) { 1379 var 1380 $target = $(event.target) 1381 ; 1382 if( $target.is(selector.input) ) { 1383 module.verbose('Using default check action on initialized checkbox'); 1384 return; 1385 } 1386 if( $target.is(selector.link) ) { 1387 module.debug('Clicking link inside checkbox, skipping toggle'); 1388 return; 1389 } 1390 module.toggle(); 1391 $input.focus(); 1392 event.preventDefault(); 1393 }, 1394 keydown: function(event) { 1395 var 1396 key = event.which, 1397 keyCode = { 1398 enter : 13, 1399 space : 32, 1400 escape : 27, 1401 left : 37, 1402 up : 38, 1403 right : 39, 1404 down : 40 1405 } 1406 ; 1407 1408 var r = module.get.radios(), 1409 rIndex = r.index($module), 1410 rLen = r.length, 1411 checkIndex = false; 1412 1413 if(key == keyCode.left || key == keyCode.up) { 1414 checkIndex = (rIndex === 0 ? rLen : rIndex) - 1; 1415 } else if(key == keyCode.right || key == keyCode.down) { 1416 checkIndex = rIndex === rLen-1 ? 0 : rIndex+1; 1417 } 1418 1419 if (!module.should.ignoreCallbacks() && checkIndex !== false) { 1420 if(settings.beforeUnchecked.apply(input)===false) { 1421 module.verbose('Option not allowed to be unchecked, cancelling key navigation'); 1422 return false; 1423 } 1424 if (settings.beforeChecked.apply($(r[checkIndex]).children(selector.input)[0])===false) { 1425 module.verbose('Next option should not allow check, cancelling key navigation'); 1426 return false; 1427 } 1428 } 1429 1430 if(key == keyCode.escape) { 1431 module.verbose('Escape key pressed blurring field'); 1432 $input.blur(); 1433 shortcutPressed = true; 1434 } 1435 else if(!event.ctrlKey && ( key == keyCode.space || (key == keyCode.enter && settings.enableEnterKey)) ) { 1436 module.verbose('Enter/space key pressed, toggling checkbox'); 1437 module.toggle(); 1438 shortcutPressed = true; 1439 } 1440 else { 1441 shortcutPressed = false; 1442 } 1443 }, 1444 keyup: function(event) { 1445 if(shortcutPressed) { 1446 event.preventDefault(); 1447 } 1448 } 1449 }, 1450 1451 check: function() { 1452 if( !module.should.allowCheck() ) { 1453 return; 1454 } 1455 module.debug('Checking checkbox', $input); 1456 module.set.checked(); 1457 if( !module.should.ignoreCallbacks() ) { 1458 settings.onChecked.call(input); 1459 module.trigger.change(); 1460 } 1461 module.preventDefaultOnInputTarget(); 1462 }, 1463 1464 uncheck: function() { 1465 if( !module.should.allowUncheck() ) { 1466 return; 1467 } 1468 module.debug('Unchecking checkbox'); 1469 module.set.unchecked(); 1470 if( !module.should.ignoreCallbacks() ) { 1471 settings.onUnchecked.call(input); 1472 module.trigger.change(); 1473 } 1474 module.preventDefaultOnInputTarget(); 1475 }, 1476 1477 indeterminate: function() { 1478 if( module.should.allowIndeterminate() ) { 1479 module.debug('Checkbox is already indeterminate'); 1480 return; 1481 } 1482 module.debug('Making checkbox indeterminate'); 1483 module.set.indeterminate(); 1484 if( !module.should.ignoreCallbacks() ) { 1485 settings.onIndeterminate.call(input); 1486 module.trigger.change(); 1487 } 1488 }, 1489 1490 determinate: function() { 1491 if( module.should.allowDeterminate() ) { 1492 module.debug('Checkbox is already determinate'); 1493 return; 1494 } 1495 module.debug('Making checkbox determinate'); 1496 module.set.determinate(); 1497 if( !module.should.ignoreCallbacks() ) { 1498 settings.onDeterminate.call(input); 1499 module.trigger.change(); 1500 } 1501 }, 1502 1503 enable: function() { 1504 if( module.is.enabled() ) { 1505 module.debug('Checkbox is already enabled'); 1506 return; 1507 } 1508 module.debug('Enabling checkbox'); 1509 module.set.enabled(); 1510 if( !module.should.ignoreCallbacks() ) { 1511 settings.onEnable.call(input); 1512 // preserve legacy callbacks 1513 settings.onEnabled.call(input); 1514 module.trigger.change(); 1515 } 1516 }, 1517 1518 disable: function() { 1519 if( module.is.disabled() ) { 1520 module.debug('Checkbox is already disabled'); 1521 return; 1522 } 1523 module.debug('Disabling checkbox'); 1524 module.set.disabled(); 1525 if( !module.should.ignoreCallbacks() ) { 1526 settings.onDisable.call(input); 1527 // preserve legacy callbacks 1528 settings.onDisabled.call(input); 1529 module.trigger.change(); 1530 } 1531 }, 1532 1533 get: { 1534 radios: function() { 1535 var 1536 name = module.get.name() 1537 ; 1538 return $('input[name="' + name + '"]').closest(selector.checkbox); 1539 }, 1540 otherRadios: function() { 1541 return module.get.radios().not($module); 1542 }, 1543 name: function() { 1544 return $input.attr('name'); 1545 } 1546 }, 1547 1548 is: { 1549 initialLoad: function() { 1550 return initialLoad; 1551 }, 1552 radio: function() { 1553 return ($input.hasClass(className.radio) || $input.attr('type') == 'radio'); 1554 }, 1555 indeterminate: function() { 1556 return $input.prop('indeterminate') !== undefined && $input.prop('indeterminate'); 1557 }, 1558 checked: function() { 1559 return $input.prop('checked') !== undefined && $input.prop('checked'); 1560 }, 1561 disabled: function() { 1562 return $input.prop('disabled') !== undefined && $input.prop('disabled'); 1563 }, 1564 enabled: function() { 1565 return !module.is.disabled(); 1566 }, 1567 determinate: function() { 1568 return !module.is.indeterminate(); 1569 }, 1570 unchecked: function() { 1571 return !module.is.checked(); 1572 } 1573 }, 1574 1575 should: { 1576 allowCheck: function() { 1577 if(module.is.determinate() && module.is.checked() && !module.is.initialLoad() ) { 1578 module.debug('Should not allow check, checkbox is already checked'); 1579 return false; 1580 } 1581 if(!module.should.ignoreCallbacks() && settings.beforeChecked.apply(input) === false) { 1582 module.debug('Should not allow check, beforeChecked cancelled'); 1583 return false; 1584 } 1585 return true; 1586 }, 1587 allowUncheck: function() { 1588 if(module.is.determinate() && module.is.unchecked() && !module.is.initialLoad() ) { 1589 module.debug('Should not allow uncheck, checkbox is already unchecked'); 1590 return false; 1591 } 1592 if(!module.should.ignoreCallbacks() && settings.beforeUnchecked.apply(input) === false) { 1593 module.debug('Should not allow uncheck, beforeUnchecked cancelled'); 1594 return false; 1595 } 1596 return true; 1597 }, 1598 allowIndeterminate: function() { 1599 if(module.is.indeterminate() && !module.is.initialLoad() ) { 1600 module.debug('Should not allow indeterminate, checkbox is already indeterminate'); 1601 return false; 1602 } 1603 if(!module.should.ignoreCallbacks() && settings.beforeIndeterminate.apply(input) === false) { 1604 module.debug('Should not allow indeterminate, beforeIndeterminate cancelled'); 1605 return false; 1606 } 1607 return true; 1608 }, 1609 allowDeterminate: function() { 1610 if(module.is.determinate() && !module.is.initialLoad() ) { 1611 module.debug('Should not allow determinate, checkbox is already determinate'); 1612 return false; 1613 } 1614 if(!module.should.ignoreCallbacks() && settings.beforeDeterminate.apply(input) === false) { 1615 module.debug('Should not allow determinate, beforeDeterminate cancelled'); 1616 return false; 1617 } 1618 return true; 1619 }, 1620 ignoreCallbacks: function() { 1621 return (initialLoad && !settings.fireOnInit); 1622 } 1623 }, 1624 1625 can: { 1626 change: function() { 1627 return !( $module.hasClass(className.disabled) || $module.hasClass(className.readOnly) || $input.prop('disabled') || $input.prop('readonly') ); 1628 }, 1629 uncheck: function() { 1630 return (typeof settings.uncheckable === 'boolean') 1631 ? settings.uncheckable 1632 : !module.is.radio() 1633 ; 1634 } 1635 }, 1636 1637 set: { 1638 initialLoad: function() { 1639 initialLoad = true; 1640 }, 1641 checked: function() { 1642 module.verbose('Setting class to checked'); 1643 $module 1644 .removeClass(className.indeterminate) 1645 .addClass(className.checked) 1646 ; 1647 if( module.is.radio() ) { 1648 module.uncheckOthers(); 1649 } 1650 if(!module.is.indeterminate() && module.is.checked()) { 1651 module.debug('Input is already checked, skipping input property change'); 1652 return; 1653 } 1654 module.verbose('Setting state to checked', input); 1655 $input 1656 .prop('indeterminate', false) 1657 .prop('checked', true) 1658 ; 1659 }, 1660 unchecked: function() { 1661 module.verbose('Removing checked class'); 1662 $module 1663 .removeClass(className.indeterminate) 1664 .removeClass(className.checked) 1665 ; 1666 if(!module.is.indeterminate() && module.is.unchecked() ) { 1667 module.debug('Input is already unchecked'); 1668 return; 1669 } 1670 module.debug('Setting state to unchecked'); 1671 $input 1672 .prop('indeterminate', false) 1673 .prop('checked', false) 1674 ; 1675 }, 1676 indeterminate: function() { 1677 module.verbose('Setting class to indeterminate'); 1678 $module 1679 .addClass(className.indeterminate) 1680 ; 1681 if( module.is.indeterminate() ) { 1682 module.debug('Input is already indeterminate, skipping input property change'); 1683 return; 1684 } 1685 module.debug('Setting state to indeterminate'); 1686 $input 1687 .prop('indeterminate', true) 1688 ; 1689 }, 1690 determinate: function() { 1691 module.verbose('Removing indeterminate class'); 1692 $module 1693 .removeClass(className.indeterminate) 1694 ; 1695 if( module.is.determinate() ) { 1696 module.debug('Input is already determinate, skipping input property change'); 1697 return; 1698 } 1699 module.debug('Setting state to determinate'); 1700 $input 1701 .prop('indeterminate', false) 1702 ; 1703 }, 1704 disabled: function() { 1705 module.verbose('Setting class to disabled'); 1706 $module 1707 .addClass(className.disabled) 1708 ; 1709 if( module.is.disabled() ) { 1710 module.debug('Input is already disabled, skipping input property change'); 1711 return; 1712 } 1713 module.debug('Setting state to disabled'); 1714 $input 1715 .prop('disabled', 'disabled') 1716 ; 1717 }, 1718 enabled: function() { 1719 module.verbose('Removing disabled class'); 1720 $module.removeClass(className.disabled); 1721 if( module.is.enabled() ) { 1722 module.debug('Input is already enabled, skipping input property change'); 1723 return; 1724 } 1725 module.debug('Setting state to enabled'); 1726 $input 1727 .prop('disabled', false) 1728 ; 1729 }, 1730 tabbable: function() { 1731 module.verbose('Adding tabindex to checkbox'); 1732 if( $input.attr('tabindex') === undefined) { 1733 $input.attr('tabindex', 0); 1734 } 1735 } 1736 }, 1737 1738 remove: { 1739 initialLoad: function() { 1740 initialLoad = false; 1741 } 1742 }, 1743 1744 trigger: { 1745 change: function() { 1746 var 1747 inputElement = $input[0] 1748 ; 1749 if(inputElement) { 1750 var events = document.createEvent('HTMLEvents'); 1751 module.verbose('Triggering native change event'); 1752 events.initEvent('change', true, false); 1753 inputElement.dispatchEvent(events); 1754 } 1755 } 1756 }, 1757 1758 1759 create: { 1760 label: function() { 1761 if($input.prevAll(selector.label).length > 0) { 1762 $input.prev(selector.label).detach().insertAfter($input); 1763 module.debug('Moving existing label', $label); 1764 } 1765 else if( !module.has.label() ) { 1766 $label = $('<label>').insertAfter($input); 1767 module.debug('Creating label', $label); 1768 } 1769 } 1770 }, 1771 1772 has: { 1773 label: function() { 1774 return ($label.length > 0); 1775 } 1776 }, 1777 1778 bind: { 1779 events: function() { 1780 module.verbose('Attaching checkbox events'); 1781 $module 1782 .on('click' + eventNamespace, module.event.click) 1783 .on('change' + eventNamespace, module.event.change) 1784 .on('keydown' + eventNamespace, selector.input, module.event.keydown) 1785 .on('keyup' + eventNamespace, selector.input, module.event.keyup) 1786 ; 1787 } 1788 }, 1789 1790 unbind: { 1791 events: function() { 1792 module.debug('Removing events'); 1793 $module 1794 .off(eventNamespace) 1795 ; 1796 } 1797 }, 1798 1799 uncheckOthers: function() { 1800 var 1801 $radios = module.get.otherRadios() 1802 ; 1803 module.debug('Unchecking other radios', $radios); 1804 $radios.removeClass(className.checked); 1805 }, 1806 1807 toggle: function() { 1808 if( !module.can.change() ) { 1809 if(!module.is.radio()) { 1810 module.debug('Checkbox is read-only or disabled, ignoring toggle'); 1811 } 1812 return; 1813 } 1814 if( module.is.indeterminate() || module.is.unchecked() ) { 1815 module.debug('Currently unchecked'); 1816 module.check(); 1817 } 1818 else if( module.is.checked() && module.can.uncheck() ) { 1819 module.debug('Currently checked'); 1820 module.uncheck(); 1821 } 1822 }, 1823 setting: function(name, value) { 1824 module.debug('Changing setting', name, value); 1825 if( $.isPlainObject(name) ) { 1826 $.extend(true, settings, name); 1827 } 1828 else if(value !== undefined) { 1829 if($.isPlainObject(settings[name])) { 1830 $.extend(true, settings[name], value); 1831 } 1832 else { 1833 settings[name] = value; 1834 } 1835 } 1836 else { 1837 return settings[name]; 1838 } 1839 }, 1840 internal: function(name, value) { 1841 if( $.isPlainObject(name) ) { 1842 $.extend(true, module, name); 1843 } 1844 else if(value !== undefined) { 1845 module[name] = value; 1846 } 1847 else { 1848 return module[name]; 1849 } 1850 }, 1851 debug: function() { 1852 if(!settings.silent && settings.debug) { 1853 if(settings.performance) { 1854 module.performance.log(arguments); 1855 } 1856 else { 1857 module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); 1858 module.debug.apply(console, arguments); 1859 } 1860 } 1861 }, 1862 verbose: function() { 1863 if(!settings.silent && settings.verbose && settings.debug) { 1864 if(settings.performance) { 1865 module.performance.log(arguments); 1866 } 1867 else { 1868 module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); 1869 module.verbose.apply(console, arguments); 1870 } 1871 } 1872 }, 1873 error: function() { 1874 if(!settings.silent) { 1875 module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); 1876 module.error.apply(console, arguments); 1877 } 1878 }, 1879 performance: { 1880 log: function(message) { 1881 var 1882 currentTime, 1883 executionTime, 1884 previousTime 1885 ; 1886 if(settings.performance) { 1887 currentTime = new Date().getTime(); 1888 previousTime = time || currentTime; 1889 executionTime = currentTime - previousTime; 1890 time = currentTime; 1891 performance.push({ 1892 'Name' : message[0], 1893 'Arguments' : [].slice.call(message, 1) || '', 1894 'Element' : element, 1895 'Execution Time' : executionTime 1896 }); 1897 } 1898 clearTimeout(module.performance.timer); 1899 module.performance.timer = setTimeout(module.performance.display, 500); 1900 }, 1901 display: function() { 1902 var 1903 title = settings.name + ':', 1904 totalTime = 0 1905 ; 1906 time = false; 1907 clearTimeout(module.performance.timer); 1908 $.each(performance, function(index, data) { 1909 totalTime += data['Execution Time']; 1910 }); 1911 title += ' ' + totalTime + 'ms'; 1912 if(moduleSelector) { 1913 title += ' \'' + moduleSelector + '\''; 1914 } 1915 if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { 1916 console.groupCollapsed(title); 1917 if(console.table) { 1918 console.table(performance); 1919 } 1920 else { 1921 $.each(performance, function(index, data) { 1922 console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); 1923 }); 1924 } 1925 console.groupEnd(); 1926 } 1927 performance = []; 1928 } 1929 }, 1930 invoke: function(query, passedArguments, context) { 1931 var 1932 object = instance, 1933 maxDepth, 1934 found, 1935 response 1936 ; 1937 passedArguments = passedArguments || queryArguments; 1938 context = element || context; 1939 if(typeof query == 'string' && object !== undefined) { 1940 query = query.split(/[\. ]/); 1941 maxDepth = query.length - 1; 1942 $.each(query, function(depth, value) { 1943 var camelCaseValue = (depth != maxDepth) 1944 ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) 1945 : query 1946 ; 1947 if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { 1948 object = object[camelCaseValue]; 1949 } 1950 else if( object[camelCaseValue] !== undefined ) { 1951 found = object[camelCaseValue]; 1952 return false; 1953 } 1954 else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { 1955 object = object[value]; 1956 } 1957 else if( object[value] !== undefined ) { 1958 found = object[value]; 1959 return false; 1960 } 1961 else { 1962 module.error(error.method, query); 1963 return false; 1964 } 1965 }); 1966 } 1967 if ( $.isFunction( found ) ) { 1968 response = found.apply(context, passedArguments); 1969 } 1970 else if(found !== undefined) { 1971 response = found; 1972 } 1973 if(Array.isArray(returnedValue)) { 1974 returnedValue.push(response); 1975 } 1976 else if(returnedValue !== undefined) { 1977 returnedValue = [returnedValue, response]; 1978 } 1979 else if(response !== undefined) { 1980 returnedValue = response; 1981 } 1982 return found; 1983 } 1984 }; 1985 1986 if(methodInvoked) { 1987 if(instance === undefined) { 1988 module.initialize(); 1989 } 1990 module.invoke(query); 1991 } 1992 else { 1993 if(instance !== undefined) { 1994 instance.invoke('destroy'); 1995 } 1996 module.initialize(); 1997 } 1998 }) 1999 ; 2000 2001 return (returnedValue !== undefined) 2002 ? returnedValue 2003 : this 2004 ; 2005 }; 2006 2007 $.fn.checkbox.settings = { 2008 2009 name : 'Checkbox', 2010 namespace : 'checkbox', 2011 2012 silent : false, 2013 debug : false, 2014 verbose : true, 2015 performance : true, 2016 2017 // delegated event context 2018 uncheckable : 'auto', 2019 fireOnInit : false, 2020 enableEnterKey : true, 2021 2022 onChange : function(){}, 2023 2024 beforeChecked : function(){}, 2025 beforeUnchecked : function(){}, 2026 beforeDeterminate : function(){}, 2027 beforeIndeterminate : function(){}, 2028 2029 onChecked : function(){}, 2030 onUnchecked : function(){}, 2031 2032 onDeterminate : function() {}, 2033 onIndeterminate : function() {}, 2034 2035 onEnable : function(){}, 2036 onDisable : function(){}, 2037 2038 // preserve misspelled callbacks (will be removed in 3.0) 2039 onEnabled : function(){}, 2040 onDisabled : function(){}, 2041 2042 className : { 2043 checked : 'checked', 2044 indeterminate : 'indeterminate', 2045 disabled : 'disabled', 2046 hidden : 'hidden', 2047 radio : 'radio', 2048 readOnly : 'read-only' 2049 }, 2050 2051 error : { 2052 method : 'The method you called is not defined' 2053 }, 2054 2055 selector : { 2056 checkbox : '.ui.checkbox', 2057 label : 'label, .box', 2058 input : 'input[type="checkbox"], input[type="radio"]', 2059 link : 'a[href]' 2060 } 2061 2062 }; 2063 2064 })( jQuery, window, document ); 2065 2066 /*! 2067 * # Fomantic-UI - Dimmer 2068 * http://github.com/fomantic/Fomantic-UI/ 2069 * 2070 * 2071 * Released under the MIT license 2072 * http://opensource.org/licenses/MIT 2073 * 2074 */ 2075 2076 ;(function ($, window, document, undefined) { 2077 2078 'use strict'; 2079 2080 $.isFunction = $.isFunction || function(obj) { 2081 return typeof obj === "function" && typeof obj.nodeType !== "number"; 2082 }; 2083 2084 window = (typeof window != 'undefined' && window.Math == Math) 2085 ? window 2086 : (typeof self != 'undefined' && self.Math == Math) 2087 ? self 2088 : Function('return this')() 2089 ; 2090 2091 $.fn.dimmer = function(parameters) { 2092 var 2093 $allModules = $(this), 2094 2095 time = new Date().getTime(), 2096 performance = [], 2097 2098 query = arguments[0], 2099 methodInvoked = (typeof query == 'string'), 2100 queryArguments = [].slice.call(arguments, 1), 2101 2102 returnedValue 2103 ; 2104 2105 $allModules 2106 .each(function() { 2107 var 2108 settings = ( $.isPlainObject(parameters) ) 2109 ? $.extend(true, {}, $.fn.dimmer.settings, parameters) 2110 : $.extend({}, $.fn.dimmer.settings), 2111 2112 selector = settings.selector, 2113 namespace = settings.namespace, 2114 className = settings.className, 2115 error = settings.error, 2116 2117 eventNamespace = '.' + namespace, 2118 moduleNamespace = 'module-' + namespace, 2119 moduleSelector = $allModules.selector || '', 2120 2121 clickEvent = "click", unstableClickEvent = ('ontouchstart' in document.documentElement) 2122 ? 'touchstart' 2123 : 'click', 2124 2125 $module = $(this), 2126 $dimmer, 2127 $dimmable, 2128 2129 element = this, 2130 instance = $module.data(moduleNamespace), 2131 module 2132 ; 2133 2134 module = { 2135 2136 preinitialize: function() { 2137 if( module.is.dimmer() ) { 2138 2139 $dimmable = $module.parent(); 2140 $dimmer = $module; 2141 } 2142 else { 2143 $dimmable = $module; 2144 if( module.has.dimmer() ) { 2145 if(settings.dimmerName) { 2146 $dimmer = $dimmable.find(selector.dimmer).filter('.' + settings.dimmerName); 2147 } 2148 else { 2149 $dimmer = $dimmable.find(selector.dimmer); 2150 } 2151 } 2152 else { 2153 $dimmer = module.create(); 2154 } 2155 } 2156 }, 2157 2158 initialize: function() { 2159 module.debug('Initializing dimmer', settings); 2160 2161 module.bind.events(); 2162 module.set.dimmable(); 2163 module.instantiate(); 2164 }, 2165 2166 instantiate: function() { 2167 module.verbose('Storing instance of module', module); 2168 instance = module; 2169 $module 2170 .data(moduleNamespace, instance) 2171 ; 2172 }, 2173 2174 destroy: function() { 2175 module.verbose('Destroying previous module', $dimmer); 2176 module.unbind.events(); 2177 module.remove.variation(); 2178 $dimmable 2179 .off(eventNamespace) 2180 ; 2181 }, 2182 2183 bind: { 2184 events: function() { 2185 if(settings.on == 'hover') { 2186 $dimmable 2187 .on('mouseenter' + eventNamespace, module.show) 2188 .on('mouseleave' + eventNamespace, module.hide) 2189 ; 2190 } 2191 else if(settings.on == 'click') { 2192 $dimmable 2193 .on(clickEvent + eventNamespace, module.toggle) 2194 ; 2195 } 2196 if( module.is.page() ) { 2197 module.debug('Setting as a page dimmer', $dimmable); 2198 module.set.pageDimmer(); 2199 } 2200 2201 if( module.is.closable() ) { 2202 module.verbose('Adding dimmer close event', $dimmer); 2203 $dimmable 2204 .on(clickEvent + eventNamespace, selector.dimmer, module.event.click) 2205 ; 2206 } 2207 } 2208 }, 2209 2210 unbind: { 2211 events: function() { 2212 $module 2213 .removeData(moduleNamespace) 2214 ; 2215 $dimmable 2216 .off(eventNamespace) 2217 ; 2218 } 2219 }, 2220 2221 event: { 2222 click: function(event) { 2223 module.verbose('Determining if event occured on dimmer', event); 2224 if( $dimmer.find(event.target).length === 0 || $(event.target).is(selector.content) ) { 2225 module.hide(); 2226 event.stopImmediatePropagation(); 2227 } 2228 } 2229 }, 2230 2231 addContent: function(element) { 2232 var 2233 $content = $(element) 2234 ; 2235 module.debug('Add content to dimmer', $content); 2236 if($content.parent()[0] !== $dimmer[0]) { 2237 $content.detach().appendTo($dimmer); 2238 } 2239 }, 2240 2241 create: function() { 2242 var 2243 $element = $( settings.template.dimmer(settings) ) 2244 ; 2245 if(settings.dimmerName) { 2246 module.debug('Creating named dimmer', settings.dimmerName); 2247 $element.addClass(settings.dimmerName); 2248 } 2249 $element 2250 .appendTo($dimmable) 2251 ; 2252 return $element; 2253 }, 2254 2255 show: function(callback) { 2256 callback = $.isFunction(callback) 2257 ? callback 2258 : function(){} 2259 ; 2260 module.debug('Showing dimmer', $dimmer, settings); 2261 module.set.variation(); 2262 if( (!module.is.dimmed() || module.is.animating()) && module.is.enabled() ) { 2263 module.animate.show(callback); 2264 settings.onShow.call(element); 2265 settings.onChange.call(element); 2266 } 2267 else { 2268 module.debug('Dimmer is already shown or disabled'); 2269 } 2270 }, 2271 2272 hide: function(callback) { 2273 callback = $.isFunction(callback) 2274 ? callback 2275 : function(){} 2276 ; 2277 if( module.is.dimmed() || module.is.animating() ) { 2278 module.debug('Hiding dimmer', $dimmer); 2279 module.animate.hide(callback); 2280 settings.onHide.call(element); 2281 settings.onChange.call(element); 2282 } 2283 else { 2284 module.debug('Dimmer is not visible'); 2285 } 2286 }, 2287 2288 toggle: function() { 2289 module.verbose('Toggling dimmer visibility', $dimmer); 2290 if( !module.is.dimmed() ) { 2291 module.show(); 2292 } 2293 else { 2294 if ( module.is.closable() ) { 2295 module.hide(); 2296 } 2297 } 2298 }, 2299 2300 animate: { 2301 show: function(callback) { 2302 callback = $.isFunction(callback) 2303 ? callback 2304 : function(){} 2305 ; 2306 if(settings.useCSS && $.fn.transition !== undefined && $dimmer.transition('is supported')) { 2307 if(settings.useFlex) { 2308 module.debug('Using flex dimmer'); 2309 module.remove.legacy(); 2310 } 2311 else { 2312 module.debug('Using legacy non-flex dimmer'); 2313 module.set.legacy(); 2314 } 2315 if(settings.opacity !== 'auto') { 2316 module.set.opacity(); 2317 } 2318 $dimmer 2319 .transition({ 2320 displayType : settings.useFlex 2321 ? 'flex' 2322 : 'block', 2323 animation : settings.transition + ' in', 2324 queue : false, 2325 duration : module.get.duration(), 2326 useFailSafe : true, 2327 onStart : function() { 2328 module.set.dimmed(); 2329 }, 2330 onComplete : function() { 2331 module.set.active(); 2332 callback(); 2333 } 2334 }) 2335 ; 2336 } 2337 else { 2338 module.verbose('Showing dimmer animation with javascript'); 2339 module.set.dimmed(); 2340 if(settings.opacity == 'auto') { 2341 settings.opacity = 0.8; 2342 } 2343 $dimmer 2344 .stop() 2345 .css({ 2346 opacity : 0, 2347 width : '100%', 2348 height : '100%' 2349 }) 2350 .fadeTo(module.get.duration(), settings.opacity, function() { 2351 $dimmer.removeAttr('style'); 2352 module.set.active(); 2353 callback(); 2354 }) 2355 ; 2356 } 2357 }, 2358 hide: function(callback) { 2359 callback = $.isFunction(callback) 2360 ? callback 2361 : function(){} 2362 ; 2363 if(settings.useCSS && $.fn.transition !== undefined && $dimmer.transition('is supported')) { 2364 module.verbose('Hiding dimmer with css'); 2365 $dimmer 2366 .transition({ 2367 displayType : settings.useFlex 2368 ? 'flex' 2369 : 'block', 2370 animation : settings.transition + ' out', 2371 queue : false, 2372 duration : module.get.duration(), 2373 useFailSafe : true, 2374 onComplete : function() { 2375 module.remove.dimmed(); 2376 module.remove.variation(); 2377 module.remove.active(); 2378 callback(); 2379 } 2380 }) 2381 ; 2382 } 2383 else { 2384 module.verbose('Hiding dimmer with javascript'); 2385 $dimmer 2386 .stop() 2387 .fadeOut(module.get.duration(), function() { 2388 module.remove.dimmed(); 2389 module.remove.active(); 2390 $dimmer.removeAttr('style'); 2391 callback(); 2392 }) 2393 ; 2394 } 2395 } 2396 }, 2397 2398 get: { 2399 dimmer: function() { 2400 return $dimmer; 2401 }, 2402 duration: function() { 2403 if(typeof settings.duration == 'object') { 2404 if( module.is.active() ) { 2405 return settings.duration.hide; 2406 } 2407 else { 2408 return settings.duration.show; 2409 } 2410 } 2411 return settings.duration; 2412 } 2413 }, 2414 2415 has: { 2416 dimmer: function() { 2417 if(settings.dimmerName) { 2418 return ($module.find(selector.dimmer).filter('.' + settings.dimmerName).length > 0); 2419 } 2420 else { 2421 return ( $module.find(selector.dimmer).length > 0 ); 2422 } 2423 } 2424 }, 2425 2426 is: { 2427 active: function() { 2428 return $dimmer.hasClass(className.active); 2429 }, 2430 animating: function() { 2431 return ( $dimmer.is(':animated') || $dimmer.hasClass(className.animating) ); 2432 }, 2433 closable: function() { 2434 if(settings.closable == 'auto') { 2435 if(settings.on == 'hover') { 2436 return false; 2437 } 2438 return true; 2439 } 2440 return settings.closable; 2441 }, 2442 dimmer: function() { 2443 return $module.hasClass(className.dimmer); 2444 }, 2445 dimmable: function() { 2446 return $module.hasClass(className.dimmable); 2447 }, 2448 dimmed: function() { 2449 return $dimmable.hasClass(className.dimmed); 2450 }, 2451 disabled: function() { 2452 return $dimmable.hasClass(className.disabled); 2453 }, 2454 enabled: function() { 2455 return !module.is.disabled(); 2456 }, 2457 page: function () { 2458 return $dimmable.is('body'); 2459 }, 2460 pageDimmer: function() { 2461 return $dimmer.hasClass(className.pageDimmer); 2462 } 2463 }, 2464 2465 can: { 2466 show: function() { 2467 return !$dimmer.hasClass(className.disabled); 2468 } 2469 }, 2470 2471 set: { 2472 opacity: function(opacity) { 2473 var 2474 color = $dimmer.css('background-color'), 2475 colorArray = color.split(','), 2476 isRGB = (colorArray && colorArray.length >= 3) 2477 ; 2478 opacity = settings.opacity === 0 ? 0 : settings.opacity || opacity; 2479 if(isRGB) { 2480 colorArray[2] = colorArray[2].replace(')',''); 2481 colorArray[3] = opacity + ')'; 2482 color = colorArray.join(','); 2483 } 2484 else { 2485 color = 'rgba(0, 0, 0, ' + opacity + ')'; 2486 } 2487 module.debug('Setting opacity to', opacity); 2488 $dimmer.css('background-color', color); 2489 }, 2490 legacy: function() { 2491 $dimmer.addClass(className.legacy); 2492 }, 2493 active: function() { 2494 $dimmer.addClass(className.active); 2495 }, 2496 dimmable: function() { 2497 $dimmable.addClass(className.dimmable); 2498 }, 2499 dimmed: function() { 2500 $dimmable.addClass(className.dimmed); 2501 }, 2502 pageDimmer: function() { 2503 $dimmer.addClass(className.pageDimmer); 2504 }, 2505 disabled: function() { 2506 $dimmer.addClass(className.disabled); 2507 }, 2508 variation: function(variation) { 2509 variation = variation || settings.variation; 2510 if(variation) { 2511 $dimmer.addClass(variation); 2512 } 2513 } 2514 }, 2515 2516 remove: { 2517 active: function() { 2518 $dimmer 2519 .removeClass(className.active) 2520 ; 2521 }, 2522 legacy: function() { 2523 $dimmer.removeClass(className.legacy); 2524 }, 2525 dimmed: function() { 2526 $dimmable.removeClass(className.dimmed); 2527 }, 2528 disabled: function() { 2529 $dimmer.removeClass(className.disabled); 2530 }, 2531 variation: function(variation) { 2532 variation = variation || settings.variation; 2533 if(variation) { 2534 $dimmer.removeClass(variation); 2535 } 2536 } 2537 }, 2538 2539 setting: function(name, value) { 2540 module.debug('Changing setting', name, value); 2541 if( $.isPlainObject(name) ) { 2542 $.extend(true, settings, name); 2543 } 2544 else if(value !== undefined) { 2545 if($.isPlainObject(settings[name])) { 2546 $.extend(true, settings[name], value); 2547 } 2548 else { 2549 settings[name] = value; 2550 } 2551 } 2552 else { 2553 return settings[name]; 2554 } 2555 }, 2556 internal: function(name, value) { 2557 if( $.isPlainObject(name) ) { 2558 $.extend(true, module, name); 2559 } 2560 else if(value !== undefined) { 2561 module[name] = value; 2562 } 2563 else { 2564 return module[name]; 2565 } 2566 }, 2567 debug: function() { 2568 if(!settings.silent && settings.debug) { 2569 if(settings.performance) { 2570 module.performance.log(arguments); 2571 } 2572 else { 2573 module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); 2574 module.debug.apply(console, arguments); 2575 } 2576 } 2577 }, 2578 verbose: function() { 2579 if(!settings.silent && settings.verbose && settings.debug) { 2580 if(settings.performance) { 2581 module.performance.log(arguments); 2582 } 2583 else { 2584 module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); 2585 module.verbose.apply(console, arguments); 2586 } 2587 } 2588 }, 2589 error: function() { 2590 if(!settings.silent) { 2591 module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); 2592 module.error.apply(console, arguments); 2593 } 2594 }, 2595 performance: { 2596 log: function(message) { 2597 var 2598 currentTime, 2599 executionTime, 2600 previousTime 2601 ; 2602 if(settings.performance) { 2603 currentTime = new Date().getTime(); 2604 previousTime = time || currentTime; 2605 executionTime = currentTime - previousTime; 2606 time = currentTime; 2607 performance.push({ 2608 'Name' : message[0], 2609 'Arguments' : [].slice.call(message, 1) || '', 2610 'Element' : element, 2611 'Execution Time' : executionTime 2612 }); 2613 } 2614 clearTimeout(module.performance.timer); 2615 module.performance.timer = setTimeout(module.performance.display, 500); 2616 }, 2617 display: function() { 2618 var 2619 title = settings.name + ':', 2620 totalTime = 0 2621 ; 2622 time = false; 2623 clearTimeout(module.performance.timer); 2624 $.each(performance, function(index, data) { 2625 totalTime += data['Execution Time']; 2626 }); 2627 title += ' ' + totalTime + 'ms'; 2628 if(moduleSelector) { 2629 title += ' \'' + moduleSelector + '\''; 2630 } 2631 if($allModules.length > 1) { 2632 title += ' ' + '(' + $allModules.length + ')'; 2633 } 2634 if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { 2635 console.groupCollapsed(title); 2636 if(console.table) { 2637 console.table(performance); 2638 } 2639 else { 2640 $.each(performance, function(index, data) { 2641 console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); 2642 }); 2643 } 2644 console.groupEnd(); 2645 } 2646 performance = []; 2647 } 2648 }, 2649 invoke: function(query, passedArguments, context) { 2650 var 2651 object = instance, 2652 maxDepth, 2653 found, 2654 response 2655 ; 2656 passedArguments = passedArguments || queryArguments; 2657 context = element || context; 2658 if(typeof query == 'string' && object !== undefined) { 2659 query = query.split(/[\. ]/); 2660 maxDepth = query.length - 1; 2661 $.each(query, function(depth, value) { 2662 var camelCaseValue = (depth != maxDepth) 2663 ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) 2664 : query 2665 ; 2666 if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { 2667 object = object[camelCaseValue]; 2668 } 2669 else if( object[camelCaseValue] !== undefined ) { 2670 found = object[camelCaseValue]; 2671 return false; 2672 } 2673 else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { 2674 object = object[value]; 2675 } 2676 else if( object[value] !== undefined ) { 2677 found = object[value]; 2678 return false; 2679 } 2680 else { 2681 module.error(error.method, query); 2682 return false; 2683 } 2684 }); 2685 } 2686 if ( $.isFunction( found ) ) { 2687 response = found.apply(context, passedArguments); 2688 } 2689 else if(found !== undefined) { 2690 response = found; 2691 } 2692 if(Array.isArray(returnedValue)) { 2693 returnedValue.push(response); 2694 } 2695 else if(returnedValue !== undefined) { 2696 returnedValue = [returnedValue, response]; 2697 } 2698 else if(response !== undefined) { 2699 returnedValue = response; 2700 } 2701 return found; 2702 } 2703 }; 2704 2705 module.preinitialize(); 2706 2707 if(methodInvoked) { 2708 if(instance === undefined) { 2709 module.initialize(); 2710 } 2711 module.invoke(query); 2712 } 2713 else { 2714 if(instance !== undefined) { 2715 instance.invoke('destroy'); 2716 } 2717 module.initialize(); 2718 } 2719 }) 2720 ; 2721 2722 return (returnedValue !== undefined) 2723 ? returnedValue 2724 : this 2725 ; 2726 }; 2727 2728 $.fn.dimmer.settings = { 2729 2730 name : 'Dimmer', 2731 namespace : 'dimmer', 2732 2733 silent : false, 2734 debug : false, 2735 verbose : false, 2736 performance : true, 2737 2738 // whether should use flex layout 2739 useFlex : true, 2740 2741 // name to distinguish between multiple dimmers in context 2742 dimmerName : false, 2743 2744 // whether to add a variation type 2745 variation : false, 2746 2747 // whether to bind close events 2748 closable : 'auto', 2749 2750 // whether to use css animations 2751 useCSS : true, 2752 2753 // css animation to use 2754 transition : 'fade', 2755 2756 // event to bind to 2757 on : false, 2758 2759 // overriding opacity value 2760 opacity : 'auto', 2761 2762 // transition durations 2763 duration : { 2764 show : 500, 2765 hide : 500 2766 }, 2767 // whether the dynamically created dimmer should have a loader 2768 displayLoader: false, 2769 loaderText : false, 2770 loaderVariation : '', 2771 2772 onChange : function(){}, 2773 onShow : function(){}, 2774 onHide : function(){}, 2775 2776 error : { 2777 method : 'The method you called is not defined.' 2778 }, 2779 2780 className : { 2781 active : 'active', 2782 animating : 'animating', 2783 dimmable : 'dimmable', 2784 dimmed : 'dimmed', 2785 dimmer : 'dimmer', 2786 disabled : 'disabled', 2787 hide : 'hide', 2788 legacy : 'legacy', 2789 pageDimmer : 'page', 2790 show : 'show', 2791 loader : 'ui loader' 2792 }, 2793 2794 selector: { 2795 dimmer : '> .ui.dimmer', 2796 content : '.ui.dimmer > .content, .ui.dimmer > .content > .center' 2797 }, 2798 2799 template: { 2800 dimmer: function(settings) { 2801 var d = $('<div/>').addClass('ui dimmer'),l; 2802 if(settings.displayLoader) { 2803 l = $('<div/>') 2804 .addClass(settings.className.loader) 2805 .addClass(settings.loaderVariation); 2806 if(!!settings.loaderText){ 2807 l.text(settings.loaderText); 2808 l.addClass('text'); 2809 } 2810 d.append(l); 2811 } 2812 return d; 2813 } 2814 } 2815 2816 }; 2817 2818 })( jQuery, window, document ); 2819 2820 /*! 2821 * # Fomantic-UI - Dropdown 2822 * http://github.com/fomantic/Fomantic-UI/ 2823 * 2824 * 2825 * Released under the MIT license 2826 * http://opensource.org/licenses/MIT 2827 * 2828 */ 2829 2830 ;(function ($, window, document, undefined) { 2831 2832 'use strict'; 2833 2834 $.isFunction = $.isFunction || function(obj) { 2835 return typeof obj === "function" && typeof obj.nodeType !== "number"; 2836 }; 2837 2838 window = (typeof window != 'undefined' && window.Math == Math) 2839 ? window 2840 : (typeof self != 'undefined' && self.Math == Math) 2841 ? self 2842 : Function('return this')() 2843 ; 2844 2845 $.fn.dropdown = function(parameters) { 2846 var 2847 $allModules = $(this), 2848 $document = $(document), 2849 2850 moduleSelector = $allModules.selector || '', 2851 2852 hasTouch = ('ontouchstart' in document.documentElement), 2853 clickEvent = "click", unstableClickEvent = hasTouch 2854 ? 'touchstart' 2855 : 'click', 2856 2857 time = new Date().getTime(), 2858 performance = [], 2859 2860 query = arguments[0], 2861 methodInvoked = (typeof query == 'string'), 2862 queryArguments = [].slice.call(arguments, 1), 2863 returnedValue 2864 ; 2865 2866 $allModules 2867 .each(function(elementIndex) { 2868 var 2869 settings = ( $.isPlainObject(parameters) ) 2870 ? $.extend(true, {}, $.fn.dropdown.settings, parameters) 2871 : $.extend({}, $.fn.dropdown.settings), 2872 2873 className = settings.className, 2874 message = settings.message, 2875 fields = settings.fields, 2876 keys = settings.keys, 2877 metadata = settings.metadata, 2878 namespace = settings.namespace, 2879 regExp = settings.regExp, 2880 selector = settings.selector, 2881 error = settings.error, 2882 templates = settings.templates, 2883 2884 eventNamespace = '.' + namespace, 2885 moduleNamespace = 'module-' + namespace, 2886 2887 $module = $(this), 2888 $context = $(settings.context), 2889 $text = $module.find(selector.text), 2890 $search = $module.find(selector.search), 2891 $sizer = $module.find(selector.sizer), 2892 $input = $module.find(selector.input), 2893 $icon = $module.find(selector.icon), 2894 $clear = $module.find(selector.clearIcon), 2895 2896 $combo = ($module.prev().find(selector.text).length > 0) 2897 ? $module.prev().find(selector.text) 2898 : $module.prev(), 2899 2900 $menu = $module.children(selector.menu), 2901 $item = $menu.find(selector.item), 2902 $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(), 2903 2904 activated = false, 2905 itemActivated = false, 2906 internalChange = false, 2907 iconClicked = false, 2908 element = this, 2909 instance = $module.data(moduleNamespace), 2910 2911 selectActionActive, 2912 initialLoad, 2913 pageLostFocus, 2914 willRefocus, 2915 elementNamespace, 2916 id, 2917 selectObserver, 2918 menuObserver, 2919 classObserver, 2920 module 2921 ; 2922 2923 module = { 2924 2925 initialize: function() { 2926 module.debug('Initializing dropdown', settings); 2927 2928 if( module.is.alreadySetup() ) { 2929 module.setup.reference(); 2930 } 2931 else { 2932 if (settings.ignoreDiacritics && !String.prototype.normalize) { 2933 settings.ignoreDiacritics = false; 2934 module.error(error.noNormalize, element); 2935 } 2936 2937 module.setup.layout(); 2938 2939 if(settings.values) { 2940 module.set.initialLoad(); 2941 module.change.values(settings.values); 2942 module.remove.initialLoad(); 2943 } 2944 2945 module.refreshData(); 2946 2947 module.save.defaults(); 2948 module.restore.selected(); 2949 2950 module.create.id(); 2951 module.bind.events(); 2952 2953 module.observeChanges(); 2954 module.instantiate(); 2955 } 2956 2957 }, 2958 2959 instantiate: function() { 2960 module.verbose('Storing instance of dropdown', module); 2961 instance = module; 2962 $module 2963 .data(moduleNamespace, module) 2964 ; 2965 }, 2966 2967 destroy: function() { 2968 module.verbose('Destroying previous dropdown', $module); 2969 module.remove.tabbable(); 2970 module.remove.active(); 2971 $menu.transition('stop all'); 2972 $menu.removeClass(className.visible).addClass(className.hidden); 2973 $module 2974 .off(eventNamespace) 2975 .removeData(moduleNamespace) 2976 ; 2977 $menu 2978 .off(eventNamespace) 2979 ; 2980 $document 2981 .off(elementNamespace) 2982 ; 2983 module.disconnect.menuObserver(); 2984 module.disconnect.selectObserver(); 2985 module.disconnect.classObserver(); 2986 }, 2987 2988 observeChanges: function() { 2989 if('MutationObserver' in window) { 2990 selectObserver = new MutationObserver(module.event.select.mutation); 2991 menuObserver = new MutationObserver(module.event.menu.mutation); 2992 classObserver = new MutationObserver(module.event.class.mutation); 2993 module.debug('Setting up mutation observer', selectObserver, menuObserver, classObserver); 2994 module.observe.select(); 2995 module.observe.menu(); 2996 module.observe.class(); 2997 } 2998 }, 2999 3000 disconnect: { 3001 menuObserver: function() { 3002 if(menuObserver) { 3003 menuObserver.disconnect(); 3004 } 3005 }, 3006 selectObserver: function() { 3007 if(selectObserver) { 3008 selectObserver.disconnect(); 3009 } 3010 }, 3011 classObserver: function() { 3012 if(classObserver) { 3013 classObserver.disconnect(); 3014 } 3015 } 3016 }, 3017 observe: { 3018 select: function() { 3019 if(module.has.input() && selectObserver) { 3020 selectObserver.observe($module[0], { 3021 childList : true, 3022 subtree : true 3023 }); 3024 } 3025 }, 3026 menu: function() { 3027 if(module.has.menu() && menuObserver) { 3028 menuObserver.observe($menu[0], { 3029 childList : true, 3030 subtree : true 3031 }); 3032 } 3033 }, 3034 class: function() { 3035 if(module.has.search() && classObserver) { 3036 classObserver.observe($module[0], { 3037 attributes : true 3038 }); 3039 } 3040 } 3041 }, 3042 3043 create: { 3044 id: function() { 3045 id = (Math.random().toString(16) + '000000000').substr(2, 8); 3046 elementNamespace = '.' + id; 3047 module.verbose('Creating unique id for element', id); 3048 }, 3049 userChoice: function(values) { 3050 var 3051 $userChoices, 3052 $userChoice, 3053 isUserValue, 3054 html 3055 ; 3056 values = values || module.get.userValues(); 3057 if(!values) { 3058 return false; 3059 } 3060 values = Array.isArray(values) 3061 ? values 3062 : [values] 3063 ; 3064 $.each(values, function(index, value) { 3065 if(module.get.item(value) === false) { 3066 html = settings.templates.addition( module.add.variables(message.addResult, value) ); 3067 $userChoice = $('<div />') 3068 .html(html) 3069 .attr('data-' + metadata.value, value) 3070 .attr('data-' + metadata.text, value) 3071 .addClass(className.addition) 3072 .addClass(className.item) 3073 ; 3074 if(settings.hideAdditions) { 3075 $userChoice.addClass(className.hidden); 3076 } 3077 $userChoices = ($userChoices === undefined) 3078 ? $userChoice 3079 : $userChoices.add($userChoice) 3080 ; 3081 module.verbose('Creating user choices for value', value, $userChoice); 3082 } 3083 }); 3084 return $userChoices; 3085 }, 3086 userLabels: function(value) { 3087 var 3088 userValues = module.get.userValues() 3089 ; 3090 if(userValues) { 3091 module.debug('Adding user labels', userValues); 3092 $.each(userValues, function(index, value) { 3093 module.verbose('Adding custom user value'); 3094 module.add.label(value, value); 3095 }); 3096 } 3097 }, 3098 menu: function() { 3099 $menu = $('<div />') 3100 .addClass(className.menu) 3101 .appendTo($module) 3102 ; 3103 }, 3104 sizer: function() { 3105 $sizer = $('<span />') 3106 .addClass(className.sizer) 3107 .insertAfter($search) 3108 ; 3109 } 3110 }, 3111 3112 search: function(query) { 3113 query = (query !== undefined) 3114 ? query 3115 : module.get.query() 3116 ; 3117 module.verbose('Searching for query', query); 3118 if(module.has.minCharacters(query)) { 3119 module.filter(query); 3120 } 3121 else { 3122 module.hide(null,true); 3123 } 3124 }, 3125 3126 select: { 3127 firstUnfiltered: function() { 3128 module.verbose('Selecting first non-filtered element'); 3129 module.remove.selectedItem(); 3130 $item 3131 .not(selector.unselectable) 3132 .not(selector.addition + selector.hidden) 3133 .eq(0) 3134 .addClass(className.selected) 3135 ; 3136 }, 3137 nextAvailable: function($selected) { 3138 $selected = $selected.eq(0); 3139 var 3140 $nextAvailable = $selected.nextAll(selector.item).not(selector.unselectable).eq(0), 3141 $prevAvailable = $selected.prevAll(selector.item).not(selector.unselectable).eq(0), 3142 hasNext = ($nextAvailable.length > 0) 3143 ; 3144 if(hasNext) { 3145 module.verbose('Moving selection to', $nextAvailable); 3146 $nextAvailable.addClass(className.selected); 3147 } 3148 else { 3149 module.verbose('Moving selection to', $prevAvailable); 3150 $prevAvailable.addClass(className.selected); 3151 } 3152 } 3153 }, 3154 3155 setup: { 3156 api: function() { 3157 var 3158 apiSettings = { 3159 debug : settings.debug, 3160 urlData : { 3161 value : module.get.value(), 3162 query : module.get.query() 3163 }, 3164 on : false 3165 } 3166 ; 3167 module.verbose('First request, initializing API'); 3168 $module 3169 .api(apiSettings) 3170 ; 3171 }, 3172 layout: function() { 3173 if( $module.is('select') ) { 3174 module.setup.select(); 3175 module.setup.returnedObject(); 3176 } 3177 if( !module.has.menu() ) { 3178 module.create.menu(); 3179 } 3180 if ( module.is.selection() && module.is.clearable() && !module.has.clearItem() ) { 3181 module.verbose('Adding clear icon'); 3182 $clear = $('<i />') 3183 .addClass('remove icon') 3184 .insertBefore($text) 3185 ; 3186 } 3187 if( module.is.search() && !module.has.search() ) { 3188 module.verbose('Adding search input'); 3189 $search = $('<input />') 3190 .addClass(className.search) 3191 .prop('autocomplete', 'off') 3192 .insertBefore($text) 3193 ; 3194 } 3195 if( module.is.multiple() && module.is.searchSelection() && !module.has.sizer()) { 3196 module.create.sizer(); 3197 } 3198 if(settings.allowTab) { 3199 module.set.tabbable(); 3200 } 3201 }, 3202 select: function() { 3203 var 3204 selectValues = module.get.selectValues() 3205 ; 3206 module.debug('Dropdown initialized on a select', selectValues); 3207 if( $module.is('select') ) { 3208 $input = $module; 3209 } 3210 // see if select is placed correctly already 3211 if($input.parent(selector.dropdown).length > 0) { 3212 module.debug('UI dropdown already exists. Creating dropdown menu only'); 3213 $module = $input.closest(selector.dropdown); 3214 if( !module.has.menu() ) { 3215 module.create.menu(); 3216 } 3217 $menu = $module.children(selector.menu); 3218 module.setup.menu(selectValues); 3219 } 3220 else { 3221 module.debug('Creating entire dropdown from select'); 3222 $module = $('<div />') 3223 .attr('class', $input.attr('class') ) 3224 .addClass(className.selection) 3225 .addClass(className.dropdown) 3226 .html( templates.dropdown(selectValues, fields, settings.preserveHTML, settings.className) ) 3227 .insertBefore($input) 3228 ; 3229 if($input.hasClass(className.multiple) && $input.prop('multiple') === false) { 3230 module.error(error.missingMultiple); 3231 $input.prop('multiple', true); 3232 } 3233 if($input.is('[multiple]')) { 3234 module.set.multiple(); 3235 } 3236 if ($input.prop('disabled')) { 3237 module.debug('Disabling dropdown'); 3238 $module.addClass(className.disabled); 3239 } 3240 $input 3241 .removeAttr('required') 3242 .removeAttr('class') 3243 .detach() 3244 .prependTo($module) 3245 ; 3246 } 3247 module.refresh(); 3248 }, 3249 menu: function(values) { 3250 $menu.html( templates.menu(values, fields,settings.preserveHTML,settings.className)); 3251 $item = $menu.find(selector.item); 3252 $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); 3253 }, 3254 reference: function() { 3255 module.debug('Dropdown behavior was called on select, replacing with closest dropdown'); 3256 // replace module reference 3257 $module = $module.parent(selector.dropdown); 3258 instance = $module.data(moduleNamespace); 3259 element = $module.get(0); 3260 module.refresh(); 3261 module.setup.returnedObject(); 3262 }, 3263 returnedObject: function() { 3264 var 3265 $firstModules = $allModules.slice(0, elementIndex), 3266 $lastModules = $allModules.slice(elementIndex + 1) 3267 ; 3268 // adjust all modules to use correct reference 3269 $allModules = $firstModules.add($module).add($lastModules); 3270 } 3271 }, 3272 3273 refresh: function() { 3274 module.refreshSelectors(); 3275 module.refreshData(); 3276 }, 3277 3278 refreshItems: function() { 3279 $item = $menu.find(selector.item); 3280 $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); 3281 }, 3282 3283 refreshSelectors: function() { 3284 module.verbose('Refreshing selector cache'); 3285 $text = $module.find(selector.text); 3286 $search = $module.find(selector.search); 3287 $input = $module.find(selector.input); 3288 $icon = $module.find(selector.icon); 3289 $combo = ($module.prev().find(selector.text).length > 0) 3290 ? $module.prev().find(selector.text) 3291 : $module.prev() 3292 ; 3293 $menu = $module.children(selector.menu); 3294 $item = $menu.find(selector.item); 3295 $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); 3296 }, 3297 3298 refreshData: function() { 3299 module.verbose('Refreshing cached metadata'); 3300 $item 3301 .removeData(metadata.text) 3302 .removeData(metadata.value) 3303 ; 3304 }, 3305 3306 clearData: function() { 3307 module.verbose('Clearing metadata'); 3308 $item 3309 .removeData(metadata.text) 3310 .removeData(metadata.value) 3311 ; 3312 $module 3313 .removeData(metadata.defaultText) 3314 .removeData(metadata.defaultValue) 3315 .removeData(metadata.placeholderText) 3316 ; 3317 }, 3318 3319 toggle: function() { 3320 module.verbose('Toggling menu visibility'); 3321 if( !module.is.active() ) { 3322 module.show(); 3323 } 3324 else { 3325 module.hide(); 3326 } 3327 }, 3328 3329 show: function(callback, preventFocus) { 3330 callback = $.isFunction(callback) 3331 ? callback 3332 : function(){} 3333 ; 3334 if(!module.can.show() && module.is.remote()) { 3335 module.debug('No API results retrieved, searching before show'); 3336 module.queryRemote(module.get.query(), module.show); 3337 } 3338 if( module.can.show() && !module.is.active() ) { 3339 module.debug('Showing dropdown'); 3340 if(module.has.message() && !(module.has.maxSelections() || module.has.allResultsFiltered()) ) { 3341 module.remove.message(); 3342 } 3343 if(module.is.allFiltered()) { 3344 return true; 3345 } 3346 if(settings.onShow.call(element) !== false) { 3347 module.animate.show(function() { 3348 if( module.can.click() ) { 3349 module.bind.intent(); 3350 } 3351 if(module.has.search() && !preventFocus) { 3352 module.focusSearch(); 3353 } 3354 module.set.visible(); 3355 callback.call(element); 3356 }); 3357 } 3358 } 3359 }, 3360 3361 hide: function(callback, preventBlur) { 3362 callback = $.isFunction(callback) 3363 ? callback 3364 : function(){} 3365 ; 3366 if( module.is.active() && !module.is.animatingOutward() ) { 3367 module.debug('Hiding dropdown'); 3368 if(settings.onHide.call(element) !== false) { 3369 module.animate.hide(function() { 3370 module.remove.visible(); 3371 // hidding search focus 3372 if ( module.is.focusedOnSearch() && preventBlur !== true ) { 3373 $search.blur(); 3374 } 3375 callback.call(element); 3376 }); 3377 } 3378 } else if( module.can.click() ) { 3379 module.unbind.intent(); 3380 } 3381 iconClicked = false; 3382 }, 3383 3384 hideOthers: function() { 3385 module.verbose('Finding other dropdowns to hide'); 3386 $allModules 3387 .not($module) 3388 .has(selector.menu + '.' + className.visible) 3389 .dropdown('hide') 3390 ; 3391 }, 3392 3393 hideMenu: function() { 3394 module.verbose('Hiding menu instantaneously'); 3395 module.remove.active(); 3396 module.remove.visible(); 3397 $menu.transition('hide'); 3398 }, 3399 3400 hideSubMenus: function() { 3401 var 3402 $subMenus = $menu.children(selector.item).find(selector.menu) 3403 ; 3404 module.verbose('Hiding sub menus', $subMenus); 3405 $subMenus.transition('hide'); 3406 }, 3407 3408 bind: { 3409 events: function() { 3410 module.bind.keyboardEvents(); 3411 module.bind.inputEvents(); 3412 module.bind.mouseEvents(); 3413 }, 3414 keyboardEvents: function() { 3415 module.verbose('Binding keyboard events'); 3416 $module 3417 .on('keydown' + eventNamespace, module.event.keydown) 3418 ; 3419 if( module.has.search() ) { 3420 $module 3421 .on(module.get.inputEvent() + eventNamespace, selector.search, module.event.input) 3422 ; 3423 } 3424 if( module.is.multiple() ) { 3425 $document 3426 .on('keydown' + elementNamespace, module.event.document.keydown) 3427 ; 3428 } 3429 }, 3430 inputEvents: function() { 3431 module.verbose('Binding input change events'); 3432 $module 3433 .on('change' + eventNamespace, selector.input, module.event.change) 3434 ; 3435 }, 3436 mouseEvents: function() { 3437 module.verbose('Binding mouse events'); 3438 if(module.is.multiple()) { 3439 $module 3440 .on(clickEvent + eventNamespace, selector.label, module.event.label.click) 3441 .on(clickEvent + eventNamespace, selector.remove, module.event.remove.click) 3442 ; 3443 } 3444 if( module.is.searchSelection() ) { 3445 $module 3446 .on('mousedown' + eventNamespace, module.event.mousedown) 3447 .on('mouseup' + eventNamespace, module.event.mouseup) 3448 .on('mousedown' + eventNamespace, selector.menu, module.event.menu.mousedown) 3449 .on('mouseup' + eventNamespace, selector.menu, module.event.menu.mouseup) 3450 .on(clickEvent + eventNamespace, selector.icon, module.event.icon.click) 3451 .on(clickEvent + eventNamespace, selector.clearIcon, module.event.clearIcon.click) 3452 .on('focus' + eventNamespace, selector.search, module.event.search.focus) 3453 .on(clickEvent + eventNamespace, selector.search, module.event.search.focus) 3454 .on('blur' + eventNamespace, selector.search, module.event.search.blur) 3455 .on(clickEvent + eventNamespace, selector.text, module.event.text.focus) 3456 ; 3457 if(module.is.multiple()) { 3458 $module 3459 .on(clickEvent + eventNamespace, module.event.click) 3460 ; 3461 } 3462 } 3463 else { 3464 if(settings.on == 'click') { 3465 $module 3466 .on(clickEvent + eventNamespace, selector.icon, module.event.icon.click) 3467 .on(clickEvent + eventNamespace, module.event.test.toggle) 3468 ; 3469 } 3470 else if(settings.on == 'hover') { 3471 $module 3472 .on('mouseenter' + eventNamespace, module.delay.show) 3473 .on('mouseleave' + eventNamespace, module.delay.hide) 3474 ; 3475 } 3476 else { 3477 $module 3478 .on(settings.on + eventNamespace, module.toggle) 3479 ; 3480 } 3481 $module 3482 .on('mousedown' + eventNamespace, module.event.mousedown) 3483 .on('mouseup' + eventNamespace, module.event.mouseup) 3484 .on('focus' + eventNamespace, module.event.focus) 3485 .on(clickEvent + eventNamespace, selector.clearIcon, module.event.clearIcon.click) 3486 ; 3487 if(module.has.menuSearch() ) { 3488 $module 3489 .on('blur' + eventNamespace, selector.search, module.event.search.blur) 3490 ; 3491 } 3492 else { 3493 $module 3494 .on('blur' + eventNamespace, module.event.blur) 3495 ; 3496 } 3497 } 3498 $menu 3499 .on((hasTouch ? 'touchstart' : 'mouseenter') + eventNamespace, selector.item, module.event.item.mouseenter) 3500 .on('mouseleave' + eventNamespace, selector.item, module.event.item.mouseleave) 3501 .on('click' + eventNamespace, selector.item, module.event.item.click) 3502 ; 3503 }, 3504 intent: function() { 3505 module.verbose('Binding hide intent event to document'); 3506 if(hasTouch) { 3507 $document 3508 .on('touchstart' + elementNamespace, module.event.test.touch) 3509 .on('touchmove' + elementNamespace, module.event.test.touch) 3510 ; 3511 } 3512 $document 3513 .on(clickEvent + elementNamespace, module.event.test.hide) 3514 ; 3515 } 3516 }, 3517 3518 unbind: { 3519 intent: function() { 3520 module.verbose('Removing hide intent event from document'); 3521 if(hasTouch) { 3522 $document 3523 .off('touchstart' + elementNamespace) 3524 .off('touchmove' + elementNamespace) 3525 ; 3526 } 3527 $document 3528 .off(clickEvent + elementNamespace) 3529 ; 3530 } 3531 }, 3532 3533 filter: function(query) { 3534 var 3535 searchTerm = (query !== undefined) 3536 ? query 3537 : module.get.query(), 3538 afterFiltered = function() { 3539 if(module.is.multiple()) { 3540 module.filterActive(); 3541 } 3542 if(query || (!query && module.get.activeItem().length == 0)) { 3543 module.select.firstUnfiltered(); 3544 } 3545 if( module.has.allResultsFiltered() ) { 3546 if( settings.onNoResults.call(element, searchTerm) ) { 3547 if(settings.allowAdditions) { 3548 if(settings.hideAdditions) { 3549 module.verbose('User addition with no menu, setting empty style'); 3550 module.set.empty(); 3551 module.hideMenu(); 3552 } 3553 } 3554 else { 3555 module.verbose('All items filtered, showing message', searchTerm); 3556 module.add.message(message.noResults); 3557 } 3558 } 3559 else { 3560 module.verbose('All items filtered, hiding dropdown', searchTerm); 3561 module.hideMenu(); 3562 } 3563 } 3564 else { 3565 module.remove.empty(); 3566 module.remove.message(); 3567 } 3568 if(settings.allowAdditions) { 3569 module.add.userSuggestion(module.escape.htmlEntities(query)); 3570 } 3571 if(module.is.searchSelection() && module.can.show() && module.is.focusedOnSearch() ) { 3572 module.show(); 3573 } 3574 } 3575 ; 3576 if(settings.useLabels && module.has.maxSelections()) { 3577 return; 3578 } 3579 if(settings.apiSettings) { 3580 if( module.can.useAPI() ) { 3581 module.queryRemote(searchTerm, function() { 3582 if(settings.filterRemoteData) { 3583 module.filterItems(searchTerm); 3584 } 3585 var preSelected = $input.val(); 3586 if(!Array.isArray(preSelected)) { 3587 preSelected = preSelected && preSelected!=="" ? preSelected.split(settings.delimiter) : []; 3588 } 3589 $.each(preSelected,function(index,value){ 3590 $item.filter('[data-value="'+value+'"]') 3591 .addClass(className.filtered) 3592 ; 3593 }); 3594 afterFiltered(); 3595 }); 3596 } 3597 else { 3598 module.error(error.noAPI); 3599 } 3600 } 3601 else { 3602 module.filterItems(searchTerm); 3603 afterFiltered(); 3604 } 3605 }, 3606 3607 queryRemote: function(query, callback) { 3608 var 3609 apiSettings = { 3610 errorDuration : false, 3611 cache : 'local', 3612 throttle : settings.throttle, 3613 urlData : { 3614 query: query 3615 }, 3616 onError: function() { 3617 module.add.message(message.serverError); 3618 callback(); 3619 }, 3620 onFailure: function() { 3621 module.add.message(message.serverError); 3622 callback(); 3623 }, 3624 onSuccess : function(response) { 3625 var 3626 values = response[fields.remoteValues] 3627 ; 3628 if (!Array.isArray(values)){ 3629 values = []; 3630 } 3631 module.remove.message(); 3632 var menuConfig = {}; 3633 menuConfig[fields.values] = values; 3634 module.setup.menu(menuConfig); 3635 3636 if(values.length===0 && !settings.allowAdditions) { 3637 module.add.message(message.noResults); 3638 } 3639 callback(); 3640 } 3641 } 3642 ; 3643 if( !$module.api('get request') ) { 3644 module.setup.api(); 3645 } 3646 apiSettings = $.extend(true, {}, apiSettings, settings.apiSettings); 3647 $module 3648 .api('setting', apiSettings) 3649 .api('query') 3650 ; 3651 }, 3652 3653 filterItems: function(query) { 3654 var 3655 searchTerm = module.remove.diacritics(query !== undefined 3656 ? query 3657 : module.get.query() 3658 ), 3659 results = null, 3660 escapedTerm = module.escape.string(searchTerm), 3661 regExpFlags = (settings.ignoreSearchCase ? 'i' : '') + 'gm', 3662 beginsWithRegExp = new RegExp('^' + escapedTerm, regExpFlags) 3663 ; 3664 // avoid loop if we're matching nothing 3665 if( module.has.query() ) { 3666 results = []; 3667 3668 module.verbose('Searching for matching values', searchTerm); 3669 $item 3670 .each(function(){ 3671 var 3672 $choice = $(this), 3673 text, 3674 value 3675 ; 3676 if($choice.hasClass(className.unfilterable)) { 3677 results.push(this); 3678 return true; 3679 } 3680 if(settings.match === 'both' || settings.match === 'text') { 3681 text = module.remove.diacritics(String(module.get.choiceText($choice, false))); 3682 if(text.search(beginsWithRegExp) !== -1) { 3683 results.push(this); 3684 return true; 3685 } 3686 else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, text)) { 3687 results.push(this); 3688 return true; 3689 } 3690 else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, text)) { 3691 results.push(this); 3692 return true; 3693 } 3694 } 3695 if(settings.match === 'both' || settings.match === 'value') { 3696 value = module.remove.diacritics(String(module.get.choiceValue($choice, text))); 3697 if(value.search(beginsWithRegExp) !== -1) { 3698 results.push(this); 3699 return true; 3700 } 3701 else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, value)) { 3702 results.push(this); 3703 return true; 3704 } 3705 else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, value)) { 3706 results.push(this); 3707 return true; 3708 } 3709 } 3710 }) 3711 ; 3712 } 3713 module.debug('Showing only matched items', searchTerm); 3714 module.remove.filteredItem(); 3715 if(results) { 3716 $item 3717 .not(results) 3718 .addClass(className.filtered) 3719 ; 3720 } 3721 3722 if(!module.has.query()) { 3723 $divider 3724 .removeClass(className.hidden); 3725 } else if(settings.hideDividers === true) { 3726 $divider 3727 .addClass(className.hidden); 3728 } else if(settings.hideDividers === 'empty') { 3729 $divider 3730 .removeClass(className.hidden) 3731 .filter(function() { 3732 // First find the last divider in this divider group 3733 // Dividers which are direct siblings are considered a group 3734 var lastDivider = $(this).nextUntil(selector.item); 3735 3736 return (lastDivider.length ? lastDivider : $(this)) 3737 // Count all non-filtered items until the next divider (or end of the dropdown) 3738 .nextUntil(selector.divider) 3739 .filter(selector.item + ":not(." + className.filtered + ")") 3740 // Hide divider if no items are found 3741 .length === 0; 3742 }) 3743 .addClass(className.hidden); 3744 } 3745 }, 3746 3747 fuzzySearch: function(query, term) { 3748 var 3749 termLength = term.length, 3750 queryLength = query.length 3751 ; 3752 query = (settings.ignoreSearchCase ? query.toLowerCase() : query); 3753 term = (settings.ignoreSearchCase ? term.toLowerCase() : term); 3754 if(queryLength > termLength) { 3755 return false; 3756 } 3757 if(queryLength === termLength) { 3758 return (query === term); 3759 } 3760 search: for (var characterIndex = 0, nextCharacterIndex = 0; characterIndex < queryLength; characterIndex++) { 3761 var 3762 queryCharacter = query.charCodeAt(characterIndex) 3763 ; 3764 while(nextCharacterIndex < termLength) { 3765 if(term.charCodeAt(nextCharacterIndex++) === queryCharacter) { 3766 continue search; 3767 } 3768 } 3769 return false; 3770 } 3771 return true; 3772 }, 3773 exactSearch: function (query, term) { 3774 query = (settings.ignoreSearchCase ? query.toLowerCase() : query); 3775 term = (settings.ignoreSearchCase ? term.toLowerCase() : term); 3776 return term.indexOf(query) > -1; 3777 3778 }, 3779 filterActive: function() { 3780 if(settings.useLabels) { 3781 $item.filter('.' + className.active) 3782 .addClass(className.filtered) 3783 ; 3784 } 3785 }, 3786 3787 focusSearch: function(skipHandler) { 3788 if( module.has.search() && !module.is.focusedOnSearch() ) { 3789 if(skipHandler) { 3790 $module.off('focus' + eventNamespace, selector.search); 3791 $search.focus(); 3792 $module.on('focus' + eventNamespace, selector.search, module.event.search.focus); 3793 } 3794 else { 3795 $search.focus(); 3796 } 3797 } 3798 }, 3799 3800 blurSearch: function() { 3801 if( module.has.search() ) { 3802 $search.blur(); 3803 } 3804 }, 3805 3806 forceSelection: function() { 3807 var 3808 $currentlySelected = $item.not(className.filtered).filter('.' + className.selected).eq(0), 3809 $activeItem = $item.not(className.filtered).filter('.' + className.active).eq(0), 3810 $selectedItem = ($currentlySelected.length > 0) 3811 ? $currentlySelected 3812 : $activeItem, 3813 hasSelected = ($selectedItem.length > 0) 3814 ; 3815 if(settings.allowAdditions || (hasSelected && !module.is.multiple())) { 3816 module.debug('Forcing partial selection to selected item', $selectedItem); 3817 module.event.item.click.call($selectedItem, {}, true); 3818 } 3819 else { 3820 module.remove.searchTerm(); 3821 } 3822 }, 3823 3824 change: { 3825 values: function(values) { 3826 if(!settings.allowAdditions) { 3827 module.clear(); 3828 } 3829 module.debug('Creating dropdown with specified values', values); 3830 var menuConfig = {}; 3831 menuConfig[fields.values] = values; 3832 module.setup.menu(menuConfig); 3833 $.each(values, function(index, item) { 3834 if(item.selected == true) { 3835 module.debug('Setting initial selection to', item[fields.value]); 3836 module.set.selected(item[fields.value]); 3837 if(!module.is.multiple()) { 3838 return false; 3839 } 3840 } 3841 }); 3842 3843 if(module.has.selectInput()) { 3844 module.disconnect.selectObserver(); 3845 $input.html(''); 3846 $input.append('<option disabled selected value></option>'); 3847 $.each(values, function(index, item) { 3848 var 3849 value = settings.templates.deQuote(item[fields.value]), 3850 name = settings.templates.escape( 3851 item[fields.name] || '', 3852 settings.preserveHTML 3853 ) 3854 ; 3855 $input.append('<option value="' + value + '">' + name + '</option>'); 3856 }); 3857 module.observe.select(); 3858 } 3859 } 3860 }, 3861 3862 event: { 3863 change: function() { 3864 if(!internalChange) { 3865 module.debug('Input changed, updating selection'); 3866 module.set.selected(); 3867 } 3868 }, 3869 focus: function() { 3870 if(settings.showOnFocus && !activated && module.is.hidden() && !pageLostFocus) { 3871 module.show(); 3872 } 3873 }, 3874 blur: function(event) { 3875 pageLostFocus = (document.activeElement === this); 3876 if(!activated && !pageLostFocus) { 3877 module.remove.activeLabel(); 3878 module.hide(); 3879 } 3880 }, 3881 mousedown: function() { 3882 if(module.is.searchSelection()) { 3883 // prevent menu hiding on immediate re-focus 3884 willRefocus = true; 3885 } 3886 else { 3887 // prevents focus callback from occurring on mousedown 3888 activated = true; 3889 } 3890 }, 3891 mouseup: function() { 3892 if(module.is.searchSelection()) { 3893 // prevent menu hiding on immediate re-focus 3894 willRefocus = false; 3895 } 3896 else { 3897 activated = false; 3898 } 3899 }, 3900 click: function(event) { 3901 var 3902 $target = $(event.target) 3903 ; 3904 // focus search 3905 if($target.is($module)) { 3906 if(!module.is.focusedOnSearch()) { 3907 module.focusSearch(); 3908 } 3909 else { 3910 module.show(); 3911 } 3912 } 3913 }, 3914 search: { 3915 focus: function(event) { 3916 activated = true; 3917 if(module.is.multiple()) { 3918 module.remove.activeLabel(); 3919 } 3920 if(settings.showOnFocus || (event.type !== 'focus' && event.type !== 'focusin')) { 3921 module.search(); 3922 } 3923 }, 3924 blur: function(event) { 3925 pageLostFocus = (document.activeElement === this); 3926 if(module.is.searchSelection() && !willRefocus) { 3927 if(!itemActivated && !pageLostFocus) { 3928 if(settings.forceSelection) { 3929 module.forceSelection(); 3930 } else if(!settings.allowAdditions){ 3931 module.remove.searchTerm(); 3932 } 3933 module.hide(); 3934 } 3935 } 3936 willRefocus = false; 3937 } 3938 }, 3939 clearIcon: { 3940 click: function(event) { 3941 module.clear(); 3942 if(module.is.searchSelection()) { 3943 module.remove.searchTerm(); 3944 } 3945 module.hide(); 3946 event.stopPropagation(); 3947 } 3948 }, 3949 icon: { 3950 click: function(event) { 3951 iconClicked=true; 3952 if(module.has.search()) { 3953 if(!module.is.active()) { 3954 if(settings.showOnFocus){ 3955 module.focusSearch(); 3956 } else { 3957 module.toggle(); 3958 } 3959 } else { 3960 module.blurSearch(); 3961 } 3962 } else { 3963 module.toggle(); 3964 } 3965 } 3966 }, 3967 text: { 3968 focus: function(event) { 3969 activated = true; 3970 module.focusSearch(); 3971 } 3972 }, 3973 input: function(event) { 3974 if(module.is.multiple() || module.is.searchSelection()) { 3975 module.set.filtered(); 3976 } 3977 clearTimeout(module.timer); 3978 module.timer = setTimeout(module.search, settings.delay.search); 3979 }, 3980 label: { 3981 click: function(event) { 3982 var 3983 $label = $(this), 3984 $labels = $module.find(selector.label), 3985 $activeLabels = $labels.filter('.' + className.active), 3986 $nextActive = $label.nextAll('.' + className.active), 3987 $prevActive = $label.prevAll('.' + className.active), 3988 $range = ($nextActive.length > 0) 3989 ? $label.nextUntil($nextActive).add($activeLabels).add($label) 3990 : $label.prevUntil($prevActive).add($activeLabels).add($label) 3991 ; 3992 if(event.shiftKey) { 3993 $activeLabels.removeClass(className.active); 3994 $range.addClass(className.active); 3995 } 3996 else if(event.ctrlKey) { 3997 $label.toggleClass(className.active); 3998 } 3999 else { 4000 $activeLabels.removeClass(className.active); 4001 $label.addClass(className.active); 4002 } 4003 settings.onLabelSelect.apply(this, $labels.filter('.' + className.active)); 4004 } 4005 }, 4006 remove: { 4007 click: function() { 4008 var 4009 $label = $(this).parent() 4010 ; 4011 if( $label.hasClass(className.active) ) { 4012 // remove all selected labels 4013 module.remove.activeLabels(); 4014 } 4015 else { 4016 // remove this label only 4017 module.remove.activeLabels( $label ); 4018 } 4019 } 4020 }, 4021 test: { 4022 toggle: function(event) { 4023 var 4024 toggleBehavior = (module.is.multiple()) 4025 ? module.show 4026 : module.toggle 4027 ; 4028 if(module.is.bubbledLabelClick(event) || module.is.bubbledIconClick(event)) { 4029 return; 4030 } 4031 if( module.determine.eventOnElement(event, toggleBehavior) ) { 4032 event.preventDefault(); 4033 } 4034 }, 4035 touch: function(event) { 4036 module.determine.eventOnElement(event, function() { 4037 if(event.type == 'touchstart') { 4038 module.timer = setTimeout(function() { 4039 module.hide(); 4040 }, settings.delay.touch); 4041 } 4042 else if(event.type == 'touchmove') { 4043 clearTimeout(module.timer); 4044 } 4045 }); 4046 event.stopPropagation(); 4047 }, 4048 hide: function(event) { 4049 if(module.determine.eventInModule(event, module.hide)){ 4050 if(element.id && $(event.target).attr('for') === element.id){ 4051 event.preventDefault(); 4052 } 4053 } 4054 } 4055 }, 4056 class: { 4057 mutation: function(mutations) { 4058 mutations.forEach(function(mutation) { 4059 if(mutation.attributeName === "class") { 4060 module.check.disabled(); 4061 } 4062 }); 4063 } 4064 }, 4065 select: { 4066 mutation: function(mutations) { 4067 module.debug('<select> modified, recreating menu'); 4068 if(module.is.selectMutation(mutations)) { 4069 module.disconnect.selectObserver(); 4070 module.refresh(); 4071 module.setup.select(); 4072 module.set.selected(); 4073 module.observe.select(); 4074 } 4075 } 4076 }, 4077 menu: { 4078 mutation: function(mutations) { 4079 var 4080 mutation = mutations[0], 4081 $addedNode = mutation.addedNodes 4082 ? $(mutation.addedNodes[0]) 4083 : $(false), 4084 $removedNode = mutation.removedNodes 4085 ? $(mutation.removedNodes[0]) 4086 : $(false), 4087 $changedNodes = $addedNode.add($removedNode), 4088 isUserAddition = $changedNodes.is(selector.addition) || $changedNodes.closest(selector.addition).length > 0, 4089 isMessage = $changedNodes.is(selector.message) || $changedNodes.closest(selector.message).length > 0 4090 ; 4091 if(isUserAddition || isMessage) { 4092 module.debug('Updating item selector cache'); 4093 module.refreshItems(); 4094 } 4095 else { 4096 module.debug('Menu modified, updating selector cache'); 4097 module.refresh(); 4098 } 4099 }, 4100 mousedown: function() { 4101 itemActivated = true; 4102 }, 4103 mouseup: function() { 4104 itemActivated = false; 4105 } 4106 }, 4107 item: { 4108 mouseenter: function(event) { 4109 var 4110 $target = $(event.target), 4111 $item = $(this), 4112 $subMenu = $item.children(selector.menu), 4113 $otherMenus = $item.siblings(selector.item).children(selector.menu), 4114 hasSubMenu = ($subMenu.length > 0), 4115 isBubbledEvent = ($subMenu.find($target).length > 0) 4116 ; 4117 if( !isBubbledEvent && hasSubMenu ) { 4118 clearTimeout(module.itemTimer); 4119 module.itemTimer = setTimeout(function() { 4120 module.verbose('Showing sub-menu', $subMenu); 4121 $.each($otherMenus, function() { 4122 module.animate.hide(false, $(this)); 4123 }); 4124 module.animate.show(false, $subMenu); 4125 }, settings.delay.show); 4126 event.preventDefault(); 4127 } 4128 }, 4129 mouseleave: function(event) { 4130 var 4131 $subMenu = $(this).children(selector.menu) 4132 ; 4133 if($subMenu.length > 0) { 4134 clearTimeout(module.itemTimer); 4135 module.itemTimer = setTimeout(function() { 4136 module.verbose('Hiding sub-menu', $subMenu); 4137 module.animate.hide(false, $subMenu); 4138 }, settings.delay.hide); 4139 } 4140 }, 4141 click: function (event, skipRefocus) { 4142 var 4143 $choice = $(this), 4144 $target = (event) 4145 ? $(event.target) 4146 : $(''), 4147 $subMenu = $choice.find(selector.menu), 4148 text = module.get.choiceText($choice), 4149 value = module.get.choiceValue($choice, text), 4150 hasSubMenu = ($subMenu.length > 0), 4151 isBubbledEvent = ($subMenu.find($target).length > 0) 4152 ; 4153 // prevents IE11 bug where menu receives focus even though `tabindex=-1` 4154 if (document.activeElement.tagName.toLowerCase() !== 'input') { 4155 $(document.activeElement).blur(); 4156 } 4157 if(!isBubbledEvent && (!hasSubMenu || settings.allowCategorySelection)) { 4158 if(module.is.searchSelection()) { 4159 if(settings.allowAdditions) { 4160 module.remove.userAddition(); 4161 } 4162 module.remove.searchTerm(); 4163 if(!module.is.focusedOnSearch() && !(skipRefocus == true)) { 4164 module.focusSearch(true); 4165 } 4166 } 4167 if(!settings.useLabels) { 4168 module.remove.filteredItem(); 4169 module.set.scrollPosition($choice); 4170 } 4171 module.determine.selectAction.call(this, text, value); 4172 } 4173 } 4174 }, 4175 4176 document: { 4177 // label selection should occur even when element has no focus 4178 keydown: function(event) { 4179 var 4180 pressedKey = event.which, 4181 isShortcutKey = module.is.inObject(pressedKey, keys) 4182 ; 4183 if(isShortcutKey) { 4184 var 4185 $label = $module.find(selector.label), 4186 $activeLabel = $label.filter('.' + className.active), 4187 activeValue = $activeLabel.data(metadata.value), 4188 labelIndex = $label.index($activeLabel), 4189 labelCount = $label.length, 4190 hasActiveLabel = ($activeLabel.length > 0), 4191 hasMultipleActive = ($activeLabel.length > 1), 4192 isFirstLabel = (labelIndex === 0), 4193 isLastLabel = (labelIndex + 1 == labelCount), 4194 isSearch = module.is.searchSelection(), 4195 isFocusedOnSearch = module.is.focusedOnSearch(), 4196 isFocused = module.is.focused(), 4197 caretAtStart = (isFocusedOnSearch && module.get.caretPosition(false) === 0), 4198 isSelectedSearch = (caretAtStart && module.get.caretPosition(true) !== 0), 4199 $nextLabel 4200 ; 4201 if(isSearch && !hasActiveLabel && !isFocusedOnSearch) { 4202 return; 4203 } 4204 4205 if(pressedKey == keys.leftArrow) { 4206 // activate previous label 4207 if((isFocused || caretAtStart) && !hasActiveLabel) { 4208 module.verbose('Selecting previous label'); 4209 $label.last().addClass(className.active); 4210 } 4211 else if(hasActiveLabel) { 4212 if(!event.shiftKey) { 4213 module.verbose('Selecting previous label'); 4214 $label.removeClass(className.active); 4215 } 4216 else { 4217 module.verbose('Adding previous label to selection'); 4218 } 4219 if(isFirstLabel && !hasMultipleActive) { 4220 $activeLabel.addClass(className.active); 4221 } 4222 else { 4223 $activeLabel.prev(selector.siblingLabel) 4224 .addClass(className.active) 4225 .end() 4226 ; 4227 } 4228 event.preventDefault(); 4229 } 4230 } 4231 else if(pressedKey == keys.rightArrow) { 4232 // activate first label 4233 if(isFocused && !hasActiveLabel) { 4234 $label.first().addClass(className.active); 4235 } 4236 // activate next label 4237 if(hasActiveLabel) { 4238 if(!event.shiftKey) { 4239 module.verbose('Selecting next label'); 4240 $label.removeClass(className.active); 4241 } 4242 else { 4243 module.verbose('Adding next label to selection'); 4244 } 4245 if(isLastLabel) { 4246 if(isSearch) { 4247 if(!isFocusedOnSearch) { 4248 module.focusSearch(); 4249 } 4250 else { 4251 $label.removeClass(className.active); 4252 } 4253 } 4254 else if(hasMultipleActive) { 4255 $activeLabel.next(selector.siblingLabel).addClass(className.active); 4256 } 4257 else { 4258 $activeLabel.addClass(className.active); 4259 } 4260 } 4261 else { 4262 $activeLabel.next(selector.siblingLabel).addClass(className.active); 4263 } 4264 event.preventDefault(); 4265 } 4266 } 4267 else if(pressedKey == keys.deleteKey || pressedKey == keys.backspace) { 4268 if(hasActiveLabel) { 4269 module.verbose('Removing active labels'); 4270 if(isLastLabel) { 4271 if(isSearch && !isFocusedOnSearch) { 4272 module.focusSearch(); 4273 } 4274 } 4275 $activeLabel.last().next(selector.siblingLabel).addClass(className.active); 4276 module.remove.activeLabels($activeLabel); 4277 event.preventDefault(); 4278 } 4279 else if(caretAtStart && !isSelectedSearch && !hasActiveLabel && pressedKey == keys.backspace) { 4280 module.verbose('Removing last label on input backspace'); 4281 $activeLabel = $label.last().addClass(className.active); 4282 module.remove.activeLabels($activeLabel); 4283 } 4284 } 4285 else { 4286 $activeLabel.removeClass(className.active); 4287 } 4288 } 4289 } 4290 }, 4291 4292 keydown: function(event) { 4293 var 4294 pressedKey = event.which, 4295 isShortcutKey = module.is.inObject(pressedKey, keys) 4296 ; 4297 if(isShortcutKey) { 4298 var 4299 $currentlySelected = $item.not(selector.unselectable).filter('.' + className.selected).eq(0), 4300 $activeItem = $menu.children('.' + className.active).eq(0), 4301 $selectedItem = ($currentlySelected.length > 0) 4302 ? $currentlySelected 4303 : $activeItem, 4304 $visibleItems = ($selectedItem.length > 0) 4305 ? $selectedItem.siblings(':not(.' + className.filtered +')').addBack() 4306 : $menu.children(':not(.' + className.filtered +')'), 4307 $subMenu = $selectedItem.children(selector.menu), 4308 $parentMenu = $selectedItem.closest(selector.menu), 4309 inVisibleMenu = ($parentMenu.hasClass(className.visible) || $parentMenu.hasClass(className.animating) || $parentMenu.parent(selector.menu).length > 0), 4310 hasSubMenu = ($subMenu.length> 0), 4311 hasSelectedItem = ($selectedItem.length > 0), 4312 selectedIsSelectable = ($selectedItem.not(selector.unselectable).length > 0), 4313 delimiterPressed = (pressedKey == keys.delimiter && settings.allowAdditions && module.is.multiple()), 4314 isAdditionWithoutMenu = (settings.allowAdditions && settings.hideAdditions && (pressedKey == keys.enter || delimiterPressed) && selectedIsSelectable), 4315 $nextItem, 4316 isSubMenuItem, 4317 newIndex 4318 ; 4319 // allow selection with menu closed 4320 if(isAdditionWithoutMenu) { 4321 module.verbose('Selecting item from keyboard shortcut', $selectedItem); 4322 module.event.item.click.call($selectedItem, event); 4323 if(module.is.searchSelection()) { 4324 module.remove.searchTerm(); 4325 } 4326 if(module.is.multiple()){ 4327 event.preventDefault(); 4328 } 4329 } 4330 4331 // visible menu keyboard shortcuts 4332 if( module.is.visible() ) { 4333 4334 // enter (select or open sub-menu) 4335 if(pressedKey == keys.enter || delimiterPressed) { 4336 if(pressedKey == keys.enter && hasSelectedItem && hasSubMenu && !settings.allowCategorySelection) { 4337 module.verbose('Pressed enter on unselectable category, opening sub menu'); 4338 pressedKey = keys.rightArrow; 4339 } 4340 else if(selectedIsSelectable) { 4341 module.verbose('Selecting item from keyboard shortcut', $selectedItem); 4342 module.event.item.click.call($selectedItem, event); 4343 if(module.is.searchSelection()) { 4344 module.remove.searchTerm(); 4345 if(module.is.multiple()) { 4346 $search.focus(); 4347 } 4348 } 4349 } 4350 event.preventDefault(); 4351 } 4352 4353 // sub-menu actions 4354 if(hasSelectedItem) { 4355 4356 if(pressedKey == keys.leftArrow) { 4357 4358 isSubMenuItem = ($parentMenu[0] !== $menu[0]); 4359 4360 if(isSubMenuItem) { 4361 module.verbose('Left key pressed, closing sub-menu'); 4362 module.animate.hide(false, $parentMenu); 4363 $selectedItem 4364 .removeClass(className.selected) 4365 ; 4366 $parentMenu 4367 .closest(selector.item) 4368 .addClass(className.selected) 4369 ; 4370 event.preventDefault(); 4371 } 4372 } 4373 4374 // right arrow (show sub-menu) 4375 if(pressedKey == keys.rightArrow) { 4376 if(hasSubMenu) { 4377 module.verbose('Right key pressed, opening sub-menu'); 4378 module.animate.show(false, $subMenu); 4379 $selectedItem 4380 .removeClass(className.selected) 4381 ; 4382 $subMenu 4383 .find(selector.item).eq(0) 4384 .addClass(className.selected) 4385 ; 4386 event.preventDefault(); 4387 } 4388 } 4389 } 4390 4391 // up arrow (traverse menu up) 4392 if(pressedKey == keys.upArrow) { 4393 $nextItem = (hasSelectedItem && inVisibleMenu) 4394 ? $selectedItem.prevAll(selector.item + ':not(' + selector.unselectable + ')').eq(0) 4395 : $item.eq(0) 4396 ; 4397 if($visibleItems.index( $nextItem ) < 0) { 4398 module.verbose('Up key pressed but reached top of current menu'); 4399 event.preventDefault(); 4400 return; 4401 } 4402 else { 4403 module.verbose('Up key pressed, changing active item'); 4404 $selectedItem 4405 .removeClass(className.selected) 4406 ; 4407 $nextItem 4408 .addClass(className.selected) 4409 ; 4410 module.set.scrollPosition($nextItem); 4411 if(settings.selectOnKeydown && module.is.single()) { 4412 module.set.selectedItem($nextItem); 4413 } 4414 } 4415 event.preventDefault(); 4416 } 4417 4418 // down arrow (traverse menu down) 4419 if(pressedKey == keys.downArrow) { 4420 $nextItem = (hasSelectedItem && inVisibleMenu) 4421 ? $nextItem = $selectedItem.nextAll(selector.item + ':not(' + selector.unselectable + ')').eq(0) 4422 : $item.eq(0) 4423 ; 4424 if($nextItem.length === 0) { 4425 module.verbose('Down key pressed but reached bottom of current menu'); 4426 event.preventDefault(); 4427 return; 4428 } 4429 else { 4430 module.verbose('Down key pressed, changing active item'); 4431 $item 4432 .removeClass(className.selected) 4433 ; 4434 $nextItem 4435 .addClass(className.selected) 4436 ; 4437 module.set.scrollPosition($nextItem); 4438 if(settings.selectOnKeydown && module.is.single()) { 4439 module.set.selectedItem($nextItem); 4440 } 4441 } 4442 event.preventDefault(); 4443 } 4444 4445 // page down (show next page) 4446 if(pressedKey == keys.pageUp) { 4447 module.scrollPage('up'); 4448 event.preventDefault(); 4449 } 4450 if(pressedKey == keys.pageDown) { 4451 module.scrollPage('down'); 4452 event.preventDefault(); 4453 } 4454 4455 // escape (close menu) 4456 if(pressedKey == keys.escape) { 4457 module.verbose('Escape key pressed, closing dropdown'); 4458 module.hide(); 4459 } 4460 4461 } 4462 else { 4463 // delimiter key 4464 if(delimiterPressed) { 4465 event.preventDefault(); 4466 } 4467 // down arrow (open menu) 4468 if(pressedKey == keys.downArrow && !module.is.visible()) { 4469 module.verbose('Down key pressed, showing dropdown'); 4470 module.show(); 4471 event.preventDefault(); 4472 } 4473 } 4474 } 4475 else { 4476 if( !module.has.search() ) { 4477 module.set.selectedLetter( String.fromCharCode(pressedKey) ); 4478 } 4479 } 4480 } 4481 }, 4482 4483 trigger: { 4484 change: function() { 4485 var 4486 inputElement = $input[0] 4487 ; 4488 if(inputElement) { 4489 var events = document.createEvent('HTMLEvents'); 4490 module.verbose('Triggering native change event'); 4491 events.initEvent('change', true, false); 4492 inputElement.dispatchEvent(events); 4493 } 4494 } 4495 }, 4496 4497 determine: { 4498 selectAction: function(text, value) { 4499 selectActionActive = true; 4500 module.verbose('Determining action', settings.action); 4501 if( $.isFunction( module.action[settings.action] ) ) { 4502 module.verbose('Triggering preset action', settings.action, text, value); 4503 module.action[ settings.action ].call(element, text, value, this); 4504 } 4505 else if( $.isFunction(settings.action) ) { 4506 module.verbose('Triggering user action', settings.action, text, value); 4507 settings.action.call(element, text, value, this); 4508 } 4509 else { 4510 module.error(error.action, settings.action); 4511 } 4512 selectActionActive = false; 4513 }, 4514 eventInModule: function(event, callback) { 4515 var 4516 $target = $(event.target), 4517 inDocument = ($target.closest(document.documentElement).length > 0), 4518 inModule = ($target.closest($module).length > 0) 4519 ; 4520 callback = $.isFunction(callback) 4521 ? callback 4522 : function(){} 4523 ; 4524 if(inDocument && !inModule) { 4525 module.verbose('Triggering event', callback); 4526 callback(); 4527 return true; 4528 } 4529 else { 4530 module.verbose('Event occurred in dropdown, canceling callback'); 4531 return false; 4532 } 4533 }, 4534 eventOnElement: function(event, callback) { 4535 var 4536 $target = $(event.target), 4537 $label = $target.closest(selector.siblingLabel), 4538 inVisibleDOM = document.body.contains(event.target), 4539 notOnLabel = ($module.find($label).length === 0 || !(module.is.multiple() && settings.useLabels)), 4540 notInMenu = ($target.closest($menu).length === 0) 4541 ; 4542 callback = $.isFunction(callback) 4543 ? callback 4544 : function(){} 4545 ; 4546 if(inVisibleDOM && notOnLabel && notInMenu) { 4547 module.verbose('Triggering event', callback); 4548 callback(); 4549 return true; 4550 } 4551 else { 4552 module.verbose('Event occurred in dropdown menu, canceling callback'); 4553 return false; 4554 } 4555 } 4556 }, 4557 4558 action: { 4559 4560 nothing: function() {}, 4561 4562 activate: function(text, value, element) { 4563 value = (value !== undefined) 4564 ? value 4565 : text 4566 ; 4567 if( module.can.activate( $(element) ) ) { 4568 module.set.selected(value, $(element)); 4569 if(!module.is.multiple()) { 4570 module.hideAndClear(); 4571 } 4572 } 4573 }, 4574 4575 select: function(text, value, element) { 4576 value = (value !== undefined) 4577 ? value 4578 : text 4579 ; 4580 if( module.can.activate( $(element) ) ) { 4581 module.set.value(value, text, $(element)); 4582 if(!module.is.multiple()) { 4583 module.hideAndClear(); 4584 } 4585 } 4586 }, 4587 4588 combo: function(text, value, element) { 4589 value = (value !== undefined) 4590 ? value 4591 : text 4592 ; 4593 module.set.selected(value, $(element)); 4594 module.hideAndClear(); 4595 }, 4596 4597 hide: function(text, value, element) { 4598 module.set.value(value, text, $(element)); 4599 module.hideAndClear(); 4600 } 4601 4602 }, 4603 4604 get: { 4605 id: function() { 4606 return id; 4607 }, 4608 defaultText: function() { 4609 return $module.data(metadata.defaultText); 4610 }, 4611 defaultValue: function() { 4612 return $module.data(metadata.defaultValue); 4613 }, 4614 placeholderText: function() { 4615 if(settings.placeholder != 'auto' && typeof settings.placeholder == 'string') { 4616 return settings.placeholder; 4617 } 4618 return $module.data(metadata.placeholderText) || ''; 4619 }, 4620 text: function() { 4621 return settings.preserveHTML ? $text.html() : $text.text(); 4622 }, 4623 query: function() { 4624 return String($search.val()).trim(); 4625 }, 4626 searchWidth: function(value) { 4627 value = (value !== undefined) 4628 ? value 4629 : $search.val() 4630 ; 4631 $sizer.text(value); 4632 // prevent rounding issues 4633 return Math.ceil( $sizer.width() + 1); 4634 }, 4635 selectionCount: function() { 4636 var 4637 values = module.get.values(), 4638 count 4639 ; 4640 count = ( module.is.multiple() ) 4641 ? Array.isArray(values) 4642 ? values.length 4643 : 0 4644 : (module.get.value() !== '') 4645 ? 1 4646 : 0 4647 ; 4648 return count; 4649 }, 4650 transition: function($subMenu) { 4651 return (settings.transition == 'auto') 4652 ? module.is.upward($subMenu) 4653 ? 'slide up' 4654 : 'slide down' 4655 : settings.transition 4656 ; 4657 }, 4658 userValues: function() { 4659 var 4660 values = module.get.values() 4661 ; 4662 if(!values) { 4663 return false; 4664 } 4665 values = Array.isArray(values) 4666 ? values 4667 : [values] 4668 ; 4669 return $.grep(values, function(value) { 4670 return (module.get.item(value) === false); 4671 }); 4672 }, 4673 uniqueArray: function(array) { 4674 return $.grep(array, function (value, index) { 4675 return $.inArray(value, array) === index; 4676 }); 4677 }, 4678 caretPosition: function(returnEndPos) { 4679 var 4680 input = $search.get(0), 4681 range, 4682 rangeLength 4683 ; 4684 if(returnEndPos && 'selectionEnd' in input){ 4685 return input.selectionEnd; 4686 } 4687 else if(!returnEndPos && 'selectionStart' in input) { 4688 return input.selectionStart; 4689 } 4690 if (document.selection) { 4691 input.focus(); 4692 range = document.selection.createRange(); 4693 rangeLength = range.text.length; 4694 if(returnEndPos) { 4695 return rangeLength; 4696 } 4697 range.moveStart('character', -input.value.length); 4698 return range.text.length - rangeLength; 4699 } 4700 }, 4701 value: function() { 4702 var 4703 value = ($input.length > 0) 4704 ? $input.val() 4705 : $module.data(metadata.value), 4706 isEmptyMultiselect = (Array.isArray(value) && value.length === 1 && value[0] === '') 4707 ; 4708 // prevents placeholder element from being selected when multiple 4709 return (value === undefined || isEmptyMultiselect) 4710 ? '' 4711 : value 4712 ; 4713 }, 4714 values: function() { 4715 var 4716 value = module.get.value() 4717 ; 4718 if(value === '') { 4719 return ''; 4720 } 4721 return ( !module.has.selectInput() && module.is.multiple() ) 4722 ? (typeof value == 'string') // delimited string 4723 ? module.escape.htmlEntities(value).split(settings.delimiter) 4724 : '' 4725 : value 4726 ; 4727 }, 4728 remoteValues: function() { 4729 var 4730 values = module.get.values(), 4731 remoteValues = false 4732 ; 4733 if(values) { 4734 if(typeof values == 'string') { 4735 values = [values]; 4736 } 4737 $.each(values, function(index, value) { 4738 var 4739 name = module.read.remoteData(value) 4740 ; 4741 module.verbose('Restoring value from session data', name, value); 4742 if(name) { 4743 if(!remoteValues) { 4744 remoteValues = {}; 4745 } 4746 remoteValues[value] = name; 4747 } 4748 }); 4749 } 4750 return remoteValues; 4751 }, 4752 choiceText: function($choice, preserveHTML) { 4753 preserveHTML = (preserveHTML !== undefined) 4754 ? preserveHTML 4755 : settings.preserveHTML 4756 ; 4757 if($choice) { 4758 if($choice.find(selector.menu).length > 0) { 4759 module.verbose('Retrieving text of element with sub-menu'); 4760 $choice = $choice.clone(); 4761 $choice.find(selector.menu).remove(); 4762 $choice.find(selector.menuIcon).remove(); 4763 } 4764 return ($choice.data(metadata.text) !== undefined) 4765 ? $choice.data(metadata.text) 4766 : (preserveHTML) 4767 ? $choice.html().trim() 4768 : $choice.text().trim() 4769 ; 4770 } 4771 }, 4772 choiceValue: function($choice, choiceText) { 4773 choiceText = choiceText || module.get.choiceText($choice); 4774 if(!$choice) { 4775 return false; 4776 } 4777 return ($choice.data(metadata.value) !== undefined) 4778 ? String( $choice.data(metadata.value) ) 4779 : (typeof choiceText === 'string') 4780 ? String( 4781 settings.ignoreSearchCase 4782 ? choiceText.toLowerCase() 4783 : choiceText 4784 ).trim() 4785 : String(choiceText) 4786 ; 4787 }, 4788 inputEvent: function() { 4789 var 4790 input = $search[0] 4791 ; 4792 if(input) { 4793 return (input.oninput !== undefined) 4794 ? 'input' 4795 : (input.onpropertychange !== undefined) 4796 ? 'propertychange' 4797 : 'keyup' 4798 ; 4799 } 4800 return false; 4801 }, 4802 selectValues: function() { 4803 var 4804 select = {}, 4805 oldGroup = [], 4806 values = [] 4807 ; 4808 $module 4809 .find('option') 4810 .each(function() { 4811 var 4812 $option = $(this), 4813 name = $option.html(), 4814 disabled = $option.attr('disabled'), 4815 value = ( $option.attr('value') !== undefined ) 4816 ? $option.attr('value') 4817 : name, 4818 text = ( $option.data(metadata.text) !== undefined ) 4819 ? $option.data(metadata.text) 4820 : name, 4821 group = $option.parent('optgroup') 4822 ; 4823 if(settings.placeholder === 'auto' && value === '') { 4824 select.placeholder = name; 4825 } 4826 else { 4827 if(group.length !== oldGroup.length || group[0] !== oldGroup[0]) { 4828 values.push({ 4829 type: 'header', 4830 divider: settings.headerDivider, 4831 name: group.attr('label') || '' 4832 }); 4833 oldGroup = group; 4834 } 4835 values.push({ 4836 name : name, 4837 value : value, 4838 text : text, 4839 disabled : disabled 4840 }); 4841 } 4842 }) 4843 ; 4844 if(settings.placeholder && settings.placeholder !== 'auto') { 4845 module.debug('Setting placeholder value to', settings.placeholder); 4846 select.placeholder = settings.placeholder; 4847 } 4848 if(settings.sortSelect) { 4849 if(settings.sortSelect === true) { 4850 values.sort(function(a, b) { 4851 return a.name.localeCompare(b.name); 4852 }); 4853 } else if(settings.sortSelect === 'natural') { 4854 values.sort(function(a, b) { 4855 return (a.name.toLowerCase().localeCompare(b.name.toLowerCase())); 4856 }); 4857 } else if($.isFunction(settings.sortSelect)) { 4858 values.sort(settings.sortSelect); 4859 } 4860 select[fields.values] = values; 4861 module.debug('Retrieved and sorted values from select', select); 4862 } 4863 else { 4864 select[fields.values] = values; 4865 module.debug('Retrieved values from select', select); 4866 } 4867 return select; 4868 }, 4869 activeItem: function() { 4870 return $item.filter('.' + className.active); 4871 }, 4872 selectedItem: function() { 4873 var 4874 $selectedItem = $item.not(selector.unselectable).filter('.' + className.selected) 4875 ; 4876 return ($selectedItem.length > 0) 4877 ? $selectedItem 4878 : $item.eq(0) 4879 ; 4880 }, 4881 itemWithAdditions: function(value) { 4882 var 4883 $items = module.get.item(value), 4884 $userItems = module.create.userChoice(value), 4885 hasUserItems = ($userItems && $userItems.length > 0) 4886 ; 4887 if(hasUserItems) { 4888 $items = ($items.length > 0) 4889 ? $items.add($userItems) 4890 : $userItems 4891 ; 4892 } 4893 return $items; 4894 }, 4895 item: function(value, strict) { 4896 var 4897 $selectedItem = false, 4898 shouldSearch, 4899 isMultiple 4900 ; 4901 value = (value !== undefined) 4902 ? value 4903 : ( module.get.values() !== undefined) 4904 ? module.get.values() 4905 : module.get.text() 4906 ; 4907 isMultiple = (module.is.multiple() && Array.isArray(value)); 4908 shouldSearch = (isMultiple) 4909 ? (value.length > 0) 4910 : (value !== undefined && value !== null) 4911 ; 4912 strict = (value === '' || value === false || value === true) 4913 ? true 4914 : strict || false 4915 ; 4916 if(shouldSearch) { 4917 $item 4918 .each(function() { 4919 var 4920 $choice = $(this), 4921 optionText = module.get.choiceText($choice), 4922 optionValue = module.get.choiceValue($choice, optionText) 4923 ; 4924 // safe early exit 4925 if(optionValue === null || optionValue === undefined) { 4926 return; 4927 } 4928 if(isMultiple) { 4929 if($.inArray(module.escape.htmlEntities(String(optionValue)), value.map(function(v){return String(v);})) !== -1) { 4930 $selectedItem = ($selectedItem) 4931 ? $selectedItem.add($choice) 4932 : $choice 4933 ; 4934 } 4935 } 4936 else if(strict) { 4937 module.verbose('Ambiguous dropdown value using strict type check', $choice, value); 4938 if( optionValue === value) { 4939 $selectedItem = $choice; 4940 return true; 4941 } 4942 } 4943 else { 4944 if(settings.ignoreCase) { 4945 optionValue = optionValue.toLowerCase(); 4946 value = value.toLowerCase(); 4947 } 4948 if(module.escape.htmlEntities(String(optionValue)) === module.escape.htmlEntities(String(value))) { 4949 module.verbose('Found select item by value', optionValue, value); 4950 $selectedItem = $choice; 4951 return true; 4952 } 4953 } 4954 }) 4955 ; 4956 } 4957 return $selectedItem; 4958 } 4959 }, 4960 4961 check: { 4962 maxSelections: function(selectionCount) { 4963 if(settings.maxSelections) { 4964 selectionCount = (selectionCount !== undefined) 4965 ? selectionCount 4966 : module.get.selectionCount() 4967 ; 4968 if(selectionCount >= settings.maxSelections) { 4969 module.debug('Maximum selection count reached'); 4970 if(settings.useLabels) { 4971 $item.addClass(className.filtered); 4972 module.add.message(message.maxSelections); 4973 } 4974 return true; 4975 } 4976 else { 4977 module.verbose('No longer at maximum selection count'); 4978 module.remove.message(); 4979 module.remove.filteredItem(); 4980 if(module.is.searchSelection()) { 4981 module.filterItems(); 4982 } 4983 return false; 4984 } 4985 } 4986 return true; 4987 }, 4988 disabled: function(){ 4989 $search.attr('tabindex',module.is.disabled() ? -1 : 0); 4990 } 4991 }, 4992 4993 restore: { 4994 defaults: function(preventChangeTrigger) { 4995 module.clear(preventChangeTrigger); 4996 module.restore.defaultText(); 4997 module.restore.defaultValue(); 4998 }, 4999 defaultText: function() { 5000 var 5001 defaultText = module.get.defaultText(), 5002 placeholderText = module.get.placeholderText 5003 ; 5004 if(defaultText === placeholderText) { 5005 module.debug('Restoring default placeholder text', defaultText); 5006 module.set.placeholderText(defaultText); 5007 } 5008 else { 5009 module.debug('Restoring default text', defaultText); 5010 module.set.text(defaultText); 5011 } 5012 }, 5013 placeholderText: function() { 5014 module.set.placeholderText(); 5015 }, 5016 defaultValue: function() { 5017 var 5018 defaultValue = module.get.defaultValue() 5019 ; 5020 if(defaultValue !== undefined) { 5021 module.debug('Restoring default value', defaultValue); 5022 if(defaultValue !== '') { 5023 module.set.value(defaultValue); 5024 module.set.selected(); 5025 } 5026 else { 5027 module.remove.activeItem(); 5028 module.remove.selectedItem(); 5029 } 5030 } 5031 }, 5032 labels: function() { 5033 if(settings.allowAdditions) { 5034 if(!settings.useLabels) { 5035 module.error(error.labels); 5036 settings.useLabels = true; 5037 } 5038 module.debug('Restoring selected values'); 5039 module.create.userLabels(); 5040 } 5041 module.check.maxSelections(); 5042 }, 5043 selected: function() { 5044 module.restore.values(); 5045 if(module.is.multiple()) { 5046 module.debug('Restoring previously selected values and labels'); 5047 module.restore.labels(); 5048 } 5049 else { 5050 module.debug('Restoring previously selected values'); 5051 } 5052 }, 5053 values: function() { 5054 // prevents callbacks from occurring on initial load 5055 module.set.initialLoad(); 5056 if(settings.apiSettings && settings.saveRemoteData && module.get.remoteValues()) { 5057 module.restore.remoteValues(); 5058 } 5059 else { 5060 module.set.selected(); 5061 } 5062 var value = module.get.value(); 5063 if(value && value !== '' && !(Array.isArray(value) && value.length === 0)) { 5064 $input.removeClass(className.noselection); 5065 } else { 5066 $input.addClass(className.noselection); 5067 } 5068 module.remove.initialLoad(); 5069 }, 5070 remoteValues: function() { 5071 var 5072 values = module.get.remoteValues() 5073 ; 5074 module.debug('Recreating selected from session data', values); 5075 if(values) { 5076 if( module.is.single() ) { 5077 $.each(values, function(value, name) { 5078 module.set.text(name); 5079 }); 5080 } 5081 else { 5082 $.each(values, function(value, name) { 5083 module.add.label(value, name); 5084 }); 5085 } 5086 } 5087 } 5088 }, 5089 5090 read: { 5091 remoteData: function(value) { 5092 var 5093 name 5094 ; 5095 if(window.Storage === undefined) { 5096 module.error(error.noStorage); 5097 return; 5098 } 5099 name = sessionStorage.getItem(value); 5100 return (name !== undefined) 5101 ? name 5102 : false 5103 ; 5104 } 5105 }, 5106 5107 save: { 5108 defaults: function() { 5109 module.save.defaultText(); 5110 module.save.placeholderText(); 5111 module.save.defaultValue(); 5112 }, 5113 defaultValue: function() { 5114 var 5115 value = module.get.value() 5116 ; 5117 module.verbose('Saving default value as', value); 5118 $module.data(metadata.defaultValue, value); 5119 }, 5120 defaultText: function() { 5121 var 5122 text = module.get.text() 5123 ; 5124 module.verbose('Saving default text as', text); 5125 $module.data(metadata.defaultText, text); 5126 }, 5127 placeholderText: function() { 5128 var 5129 text 5130 ; 5131 if(settings.placeholder !== false && $text.hasClass(className.placeholder)) { 5132 text = module.get.text(); 5133 module.verbose('Saving placeholder text as', text); 5134 $module.data(metadata.placeholderText, text); 5135 } 5136 }, 5137 remoteData: function(name, value) { 5138 if(window.Storage === undefined) { 5139 module.error(error.noStorage); 5140 return; 5141 } 5142 module.verbose('Saving remote data to session storage', value, name); 5143 sessionStorage.setItem(value, name); 5144 } 5145 }, 5146 5147 clear: function(preventChangeTrigger) { 5148 if(module.is.multiple() && settings.useLabels) { 5149 module.remove.labels(); 5150 } 5151 else { 5152 module.remove.activeItem(); 5153 module.remove.selectedItem(); 5154 module.remove.filteredItem(); 5155 } 5156 module.set.placeholderText(); 5157 module.clearValue(preventChangeTrigger); 5158 }, 5159 5160 clearValue: function(preventChangeTrigger) { 5161 module.set.value('', null, null, preventChangeTrigger); 5162 }, 5163 5164 scrollPage: function(direction, $selectedItem) { 5165 var 5166 $currentItem = $selectedItem || module.get.selectedItem(), 5167 $menu = $currentItem.closest(selector.menu), 5168 menuHeight = $menu.outerHeight(), 5169 currentScroll = $menu.scrollTop(), 5170 itemHeight = $item.eq(0).outerHeight(), 5171 itemsPerPage = Math.floor(menuHeight / itemHeight), 5172 maxScroll = $menu.prop('scrollHeight'), 5173 newScroll = (direction == 'up') 5174 ? currentScroll - (itemHeight * itemsPerPage) 5175 : currentScroll + (itemHeight * itemsPerPage), 5176 $selectableItem = $item.not(selector.unselectable), 5177 isWithinRange, 5178 $nextSelectedItem, 5179 elementIndex 5180 ; 5181 elementIndex = (direction == 'up') 5182 ? $selectableItem.index($currentItem) - itemsPerPage 5183 : $selectableItem.index($currentItem) + itemsPerPage 5184 ; 5185 isWithinRange = (direction == 'up') 5186 ? (elementIndex >= 0) 5187 : (elementIndex < $selectableItem.length) 5188 ; 5189 $nextSelectedItem = (isWithinRange) 5190 ? $selectableItem.eq(elementIndex) 5191 : (direction == 'up') 5192 ? $selectableItem.first() 5193 : $selectableItem.last() 5194 ; 5195 if($nextSelectedItem.length > 0) { 5196 module.debug('Scrolling page', direction, $nextSelectedItem); 5197 $currentItem 5198 .removeClass(className.selected) 5199 ; 5200 $nextSelectedItem 5201 .addClass(className.selected) 5202 ; 5203 if(settings.selectOnKeydown && module.is.single()) { 5204 module.set.selectedItem($nextSelectedItem); 5205 } 5206 $menu 5207 .scrollTop(newScroll) 5208 ; 5209 } 5210 }, 5211 5212 set: { 5213 filtered: function() { 5214 var 5215 isMultiple = module.is.multiple(), 5216 isSearch = module.is.searchSelection(), 5217 isSearchMultiple = (isMultiple && isSearch), 5218 searchValue = (isSearch) 5219 ? module.get.query() 5220 : '', 5221 hasSearchValue = (typeof searchValue === 'string' && searchValue.length > 0), 5222 searchWidth = module.get.searchWidth(), 5223 valueIsSet = searchValue !== '' 5224 ; 5225 if(isMultiple && hasSearchValue) { 5226 module.verbose('Adjusting input width', searchWidth, settings.glyphWidth); 5227 $search.css('width', searchWidth); 5228 } 5229 if(hasSearchValue || (isSearchMultiple && valueIsSet)) { 5230 module.verbose('Hiding placeholder text'); 5231 $text.addClass(className.filtered); 5232 } 5233 else if(!isMultiple || (isSearchMultiple && !valueIsSet)) { 5234 module.verbose('Showing placeholder text'); 5235 $text.removeClass(className.filtered); 5236 } 5237 }, 5238 empty: function() { 5239 $module.addClass(className.empty); 5240 }, 5241 loading: function() { 5242 $module.addClass(className.loading); 5243 }, 5244 placeholderText: function(text) { 5245 text = text || module.get.placeholderText(); 5246 module.debug('Setting placeholder text', text); 5247 module.set.text(text); 5248 $text.addClass(className.placeholder); 5249 }, 5250 tabbable: function() { 5251 if( module.is.searchSelection() ) { 5252 module.debug('Added tabindex to searchable dropdown'); 5253 $search 5254 .val('') 5255 ; 5256 module.check.disabled(); 5257 $menu 5258 .attr('tabindex', -1) 5259 ; 5260 } 5261 else { 5262 module.debug('Added tabindex to dropdown'); 5263 if( $module.attr('tabindex') === undefined) { 5264 $module 5265 .attr('tabindex', 0) 5266 ; 5267 $menu 5268 .attr('tabindex', -1) 5269 ; 5270 } 5271 } 5272 }, 5273 initialLoad: function() { 5274 module.verbose('Setting initial load'); 5275 initialLoad = true; 5276 }, 5277 activeItem: function($item) { 5278 if( settings.allowAdditions && $item.filter(selector.addition).length > 0 ) { 5279 $item.addClass(className.filtered); 5280 } 5281 else { 5282 $item.addClass(className.active); 5283 } 5284 }, 5285 partialSearch: function(text) { 5286 var 5287 length = module.get.query().length 5288 ; 5289 $search.val( text.substr(0, length)); 5290 }, 5291 scrollPosition: function($item, forceScroll) { 5292 var 5293 edgeTolerance = 5, 5294 $menu, 5295 hasActive, 5296 offset, 5297 itemHeight, 5298 itemOffset, 5299 menuOffset, 5300 menuScroll, 5301 menuHeight, 5302 abovePage, 5303 belowPage 5304 ; 5305 5306 $item = $item || module.get.selectedItem(); 5307 $menu = $item.closest(selector.menu); 5308 hasActive = ($item && $item.length > 0); 5309 forceScroll = (forceScroll !== undefined) 5310 ? forceScroll 5311 : false 5312 ; 5313 if(module.get.activeItem().length === 0){ 5314 forceScroll = false; 5315 } 5316 if($item && $menu.length > 0 && hasActive) { 5317 itemOffset = $item.position().top; 5318 5319 $menu.addClass(className.loading); 5320 menuScroll = $menu.scrollTop(); 5321 menuOffset = $menu.offset().top; 5322 itemOffset = $item.offset().top; 5323 offset = menuScroll - menuOffset + itemOffset; 5324 if(!forceScroll) { 5325 menuHeight = $menu.height(); 5326 belowPage = menuScroll + menuHeight < (offset + edgeTolerance); 5327 abovePage = ((offset - edgeTolerance) < menuScroll); 5328 } 5329 module.debug('Scrolling to active item', offset); 5330 if(forceScroll || abovePage || belowPage) { 5331 $menu.scrollTop(offset); 5332 } 5333 $menu.removeClass(className.loading); 5334 } 5335 }, 5336 text: function(text) { 5337 if(settings.action === 'combo') { 5338 module.debug('Changing combo button text', text, $combo); 5339 if(settings.preserveHTML) { 5340 $combo.html(text); 5341 } 5342 else { 5343 $combo.text(text); 5344 } 5345 } 5346 else if(settings.action === 'activate') { 5347 if(text !== module.get.placeholderText()) { 5348 $text.removeClass(className.placeholder); 5349 } 5350 module.debug('Changing text', text, $text); 5351 $text 5352 .removeClass(className.filtered) 5353 ; 5354 if(settings.preserveHTML) { 5355 $text.html(text); 5356 } 5357 else { 5358 $text.text(text); 5359 } 5360 } 5361 }, 5362 selectedItem: function($item) { 5363 var 5364 value = module.get.choiceValue($item), 5365 searchText = module.get.choiceText($item, false), 5366 text = module.get.choiceText($item, true) 5367 ; 5368 module.debug('Setting user selection to item', $item); 5369 module.remove.activeItem(); 5370 module.set.partialSearch(searchText); 5371 module.set.activeItem($item); 5372 module.set.selected(value, $item); 5373 module.set.text(text); 5374 }, 5375 selectedLetter: function(letter) { 5376 var 5377 $selectedItem = $item.filter('.' + className.selected), 5378 alreadySelectedLetter = $selectedItem.length > 0 && module.has.firstLetter($selectedItem, letter), 5379 $nextValue = false, 5380 $nextItem 5381 ; 5382 // check next of same letter 5383 if(alreadySelectedLetter) { 5384 $nextItem = $selectedItem.nextAll($item).eq(0); 5385 if( module.has.firstLetter($nextItem, letter) ) { 5386 $nextValue = $nextItem; 5387 } 5388 } 5389 // check all values 5390 if(!$nextValue) { 5391 $item 5392 .each(function(){ 5393 if(module.has.firstLetter($(this), letter)) { 5394 $nextValue = $(this); 5395 return false; 5396 } 5397 }) 5398 ; 5399 } 5400 // set next value 5401 if($nextValue) { 5402 module.verbose('Scrolling to next value with letter', letter); 5403 module.set.scrollPosition($nextValue); 5404 $selectedItem.removeClass(className.selected); 5405 $nextValue.addClass(className.selected); 5406 if(settings.selectOnKeydown && module.is.single()) { 5407 module.set.selectedItem($nextValue); 5408 } 5409 } 5410 }, 5411 direction: function($menu) { 5412 if(settings.direction == 'auto') { 5413 // reset position, remove upward if it's base menu 5414 if (!$menu) { 5415 module.remove.upward(); 5416 } else if (module.is.upward($menu)) { 5417 //we need make sure when make assertion openDownward for $menu, $menu does not have upward class 5418 module.remove.upward($menu); 5419 } 5420 5421 if(module.can.openDownward($menu)) { 5422 module.remove.upward($menu); 5423 } 5424 else { 5425 module.set.upward($menu); 5426 } 5427 if(!module.is.leftward($menu) && !module.can.openRightward($menu)) { 5428 module.set.leftward($menu); 5429 } 5430 } 5431 else if(settings.direction == 'upward') { 5432 module.set.upward($menu); 5433 } 5434 }, 5435 upward: function($currentMenu) { 5436 var $element = $currentMenu || $module; 5437 $element.addClass(className.upward); 5438 }, 5439 leftward: function($currentMenu) { 5440 var $element = $currentMenu || $menu; 5441 $element.addClass(className.leftward); 5442 }, 5443 value: function(value, text, $selected, preventChangeTrigger) { 5444 if(value !== undefined && value !== '' && !(Array.isArray(value) && value.length === 0)) { 5445 $input.removeClass(className.noselection); 5446 } else { 5447 $input.addClass(className.noselection); 5448 } 5449 var 5450 escapedValue = module.escape.value(value), 5451 hasInput = ($input.length > 0), 5452 currentValue = module.get.values(), 5453 stringValue = (value !== undefined) 5454 ? String(value) 5455 : value, 5456 newValue 5457 ; 5458 if(hasInput) { 5459 if(!settings.allowReselection && stringValue == currentValue) { 5460 module.verbose('Skipping value update already same value', value, currentValue); 5461 if(!module.is.initialLoad()) { 5462 return; 5463 } 5464 } 5465 5466 if( module.is.single() && module.has.selectInput() && module.can.extendSelect() ) { 5467 module.debug('Adding user option', value); 5468 module.add.optionValue(value); 5469 } 5470 module.debug('Updating input value', escapedValue, currentValue); 5471 internalChange = true; 5472 $input 5473 .val(escapedValue) 5474 ; 5475 if(settings.fireOnInit === false && module.is.initialLoad()) { 5476 module.debug('Input native change event ignored on initial load'); 5477 } 5478 else if(preventChangeTrigger !== true) { 5479 module.trigger.change(); 5480 } 5481 internalChange = false; 5482 } 5483 else { 5484 module.verbose('Storing value in metadata', escapedValue, $input); 5485 if(escapedValue !== currentValue) { 5486 $module.data(metadata.value, stringValue); 5487 } 5488 } 5489 if(settings.fireOnInit === false && module.is.initialLoad()) { 5490 module.verbose('No callback on initial load', settings.onChange); 5491 } 5492 else if(preventChangeTrigger !== true) { 5493 settings.onChange.call(element, value, text, $selected); 5494 } 5495 }, 5496 active: function() { 5497 $module 5498 .addClass(className.active) 5499 ; 5500 }, 5501 multiple: function() { 5502 $module.addClass(className.multiple); 5503 }, 5504 visible: function() { 5505 $module.addClass(className.visible); 5506 }, 5507 exactly: function(value, $selectedItem) { 5508 module.debug('Setting selected to exact values'); 5509 module.clear(); 5510 module.set.selected(value, $selectedItem); 5511 }, 5512 selected: function(value, $selectedItem) { 5513 var 5514 isMultiple = module.is.multiple() 5515 ; 5516 $selectedItem = (settings.allowAdditions) 5517 ? $selectedItem || module.get.itemWithAdditions(value) 5518 : $selectedItem || module.get.item(value) 5519 ; 5520 if(!$selectedItem) { 5521 return; 5522 } 5523 module.debug('Setting selected menu item to', $selectedItem); 5524 if(module.is.multiple()) { 5525 module.remove.searchWidth(); 5526 } 5527 if(module.is.single()) { 5528 module.remove.activeItem(); 5529 module.remove.selectedItem(); 5530 } 5531 else if(settings.useLabels) { 5532 module.remove.selectedItem(); 5533 } 5534 // select each item 5535 $selectedItem 5536 .each(function() { 5537 var 5538 $selected = $(this), 5539 selectedText = module.get.choiceText($selected), 5540 selectedValue = module.get.choiceValue($selected, selectedText), 5541 5542 isFiltered = $selected.hasClass(className.filtered), 5543 isActive = $selected.hasClass(className.active), 5544 isUserValue = $selected.hasClass(className.addition), 5545 shouldAnimate = (isMultiple && $selectedItem.length == 1) 5546 ; 5547 if(isMultiple) { 5548 if(!isActive || isUserValue) { 5549 if(settings.apiSettings && settings.saveRemoteData) { 5550 module.save.remoteData(selectedText, selectedValue); 5551 } 5552 if(settings.useLabels) { 5553 module.add.label(selectedValue, selectedText, shouldAnimate); 5554 module.add.value(selectedValue, selectedText, $selected); 5555 module.set.activeItem($selected); 5556 module.filterActive(); 5557 module.select.nextAvailable($selectedItem); 5558 } 5559 else { 5560 module.add.value(selectedValue, selectedText, $selected); 5561 module.set.text(module.add.variables(message.count)); 5562 module.set.activeItem($selected); 5563 } 5564 } 5565 else if(!isFiltered && (settings.useLabels || selectActionActive)) { 5566 module.debug('Selected active value, removing label'); 5567 module.remove.selected(selectedValue); 5568 } 5569 } 5570 else { 5571 if(settings.apiSettings && settings.saveRemoteData) { 5572 module.save.remoteData(selectedText, selectedValue); 5573 } 5574 module.set.text(selectedText); 5575 module.set.value(selectedValue, selectedText, $selected); 5576 $selected 5577 .addClass(className.active) 5578 .addClass(className.selected) 5579 ; 5580 } 5581 }) 5582 ; 5583 module.remove.searchTerm(); 5584 } 5585 }, 5586 5587 add: { 5588 label: function(value, text, shouldAnimate) { 5589 var 5590 $next = module.is.searchSelection() 5591 ? $search 5592 : $text, 5593 escapedValue = module.escape.value(value), 5594 $label 5595 ; 5596 if(settings.ignoreCase) { 5597 escapedValue = escapedValue.toLowerCase(); 5598 } 5599 $label = $('<a />') 5600 .addClass(className.label) 5601 .attr('data-' + metadata.value, escapedValue) 5602 .html(templates.label(escapedValue, text, settings.preserveHTML, settings.className)) 5603 ; 5604 $label = settings.onLabelCreate.call($label, escapedValue, text); 5605 5606 if(module.has.label(value)) { 5607 module.debug('User selection already exists, skipping', escapedValue); 5608 return; 5609 } 5610 if(settings.label.variation) { 5611 $label.addClass(settings.label.variation); 5612 } 5613 if(shouldAnimate === true) { 5614 module.debug('Animating in label', $label); 5615 $label 5616 .addClass(className.hidden) 5617 .insertBefore($next) 5618 .transition({ 5619 animation : settings.label.transition, 5620 debug : settings.debug, 5621 verbose : settings.verbose, 5622 duration : settings.label.duration 5623 }) 5624 ; 5625 } 5626 else { 5627 module.debug('Adding selection label', $label); 5628 $label 5629 .insertBefore($next) 5630 ; 5631 } 5632 }, 5633 message: function(message) { 5634 var 5635 $message = $menu.children(selector.message), 5636 html = settings.templates.message(module.add.variables(message)) 5637 ; 5638 if($message.length > 0) { 5639 $message 5640 .html(html) 5641 ; 5642 } 5643 else { 5644 $message = $('<div/>') 5645 .html(html) 5646 .addClass(className.message) 5647 .appendTo($menu) 5648 ; 5649 } 5650 }, 5651 optionValue: function(value) { 5652 var 5653 escapedValue = module.escape.value(value), 5654 $option = $input.find('option[value="' + module.escape.string(escapedValue) + '"]'), 5655 hasOption = ($option.length > 0) 5656 ; 5657 if(hasOption) { 5658 return; 5659 } 5660 // temporarily disconnect observer 5661 module.disconnect.selectObserver(); 5662 if( module.is.single() ) { 5663 module.verbose('Removing previous user addition'); 5664 $input.find('option.' + className.addition).remove(); 5665 } 5666 $('<option/>') 5667 .prop('value', escapedValue) 5668 .addClass(className.addition) 5669 .html(value) 5670 .appendTo($input) 5671 ; 5672 module.verbose('Adding user addition as an <option>', value); 5673 module.observe.select(); 5674 }, 5675 userSuggestion: function(value) { 5676 var 5677 $addition = $menu.children(selector.addition), 5678 $existingItem = module.get.item(value), 5679 alreadyHasValue = $existingItem && $existingItem.not(selector.addition).length, 5680 hasUserSuggestion = $addition.length > 0, 5681 html 5682 ; 5683 if(settings.useLabels && module.has.maxSelections()) { 5684 return; 5685 } 5686 if(value === '' || alreadyHasValue) { 5687 $addition.remove(); 5688 return; 5689 } 5690 if(hasUserSuggestion) { 5691 $addition 5692 .data(metadata.value, value) 5693 .data(metadata.text, value) 5694 .attr('data-' + metadata.value, value) 5695 .attr('data-' + metadata.text, value) 5696 .removeClass(className.filtered) 5697 ; 5698 if(!settings.hideAdditions) { 5699 html = settings.templates.addition( module.add.variables(message.addResult, value) ); 5700 $addition 5701 .html(html) 5702 ; 5703 } 5704 module.verbose('Replacing user suggestion with new value', $addition); 5705 } 5706 else { 5707 $addition = module.create.userChoice(value); 5708 $addition 5709 .prependTo($menu) 5710 ; 5711 module.verbose('Adding item choice to menu corresponding with user choice addition', $addition); 5712 } 5713 if(!settings.hideAdditions || module.is.allFiltered()) { 5714 $addition 5715 .addClass(className.selected) 5716 .siblings() 5717 .removeClass(className.selected) 5718 ; 5719 } 5720 module.refreshItems(); 5721 }, 5722 variables: function(message, term) { 5723 var 5724 hasCount = (message.search('{count}') !== -1), 5725 hasMaxCount = (message.search('{maxCount}') !== -1), 5726 hasTerm = (message.search('{term}') !== -1), 5727 count, 5728 query 5729 ; 5730 module.verbose('Adding templated variables to message', message); 5731 if(hasCount) { 5732 count = module.get.selectionCount(); 5733 message = message.replace('{count}', count); 5734 } 5735 if(hasMaxCount) { 5736 count = module.get.selectionCount(); 5737 message = message.replace('{maxCount}', settings.maxSelections); 5738 } 5739 if(hasTerm) { 5740 query = term || module.get.query(); 5741 message = message.replace('{term}', query); 5742 } 5743 return message; 5744 }, 5745 value: function(addedValue, addedText, $selectedItem) { 5746 var 5747 currentValue = module.get.values(), 5748 newValue 5749 ; 5750 if(module.has.value(addedValue)) { 5751 module.debug('Value already selected'); 5752 return; 5753 } 5754 if(addedValue === '') { 5755 module.debug('Cannot select blank values from multiselect'); 5756 return; 5757 } 5758 // extend current array 5759 if(Array.isArray(currentValue)) { 5760 newValue = currentValue.concat([addedValue]); 5761 newValue = module.get.uniqueArray(newValue); 5762 } 5763 else { 5764 newValue = [addedValue]; 5765 } 5766 // add values 5767 if( module.has.selectInput() ) { 5768 if(module.can.extendSelect()) { 5769 module.debug('Adding value to select', addedValue, newValue, $input); 5770 module.add.optionValue(addedValue); 5771 } 5772 } 5773 else { 5774 newValue = newValue.join(settings.delimiter); 5775 module.debug('Setting hidden input to delimited value', newValue, $input); 5776 } 5777 5778 if(settings.fireOnInit === false && module.is.initialLoad()) { 5779 module.verbose('Skipping onadd callback on initial load', settings.onAdd); 5780 } 5781 else { 5782 settings.onAdd.call(element, addedValue, addedText, $selectedItem); 5783 } 5784 module.set.value(newValue, addedText, $selectedItem); 5785 module.check.maxSelections(); 5786 }, 5787 }, 5788 5789 remove: { 5790 active: function() { 5791 $module.removeClass(className.active); 5792 }, 5793 activeLabel: function() { 5794 $module.find(selector.label).removeClass(className.active); 5795 }, 5796 empty: function() { 5797 $module.removeClass(className.empty); 5798 }, 5799 loading: function() { 5800 $module.removeClass(className.loading); 5801 }, 5802 initialLoad: function() { 5803 initialLoad = false; 5804 }, 5805 upward: function($currentMenu) { 5806 var $element = $currentMenu || $module; 5807 $element.removeClass(className.upward); 5808 }, 5809 leftward: function($currentMenu) { 5810 var $element = $currentMenu || $menu; 5811 $element.removeClass(className.leftward); 5812 }, 5813 visible: function() { 5814 $module.removeClass(className.visible); 5815 }, 5816 activeItem: function() { 5817 $item.removeClass(className.active); 5818 }, 5819 filteredItem: function() { 5820 if(settings.useLabels && module.has.maxSelections() ) { 5821 return; 5822 } 5823 if(settings.useLabels && module.is.multiple()) { 5824 $item.not('.' + className.active).removeClass(className.filtered); 5825 } 5826 else { 5827 $item.removeClass(className.filtered); 5828 } 5829 if(settings.hideDividers) { 5830 $divider.removeClass(className.hidden); 5831 } 5832 module.remove.empty(); 5833 }, 5834 optionValue: function(value) { 5835 var 5836 escapedValue = module.escape.value(value), 5837 $option = $input.find('option[value="' + module.escape.string(escapedValue) + '"]'), 5838 hasOption = ($option.length > 0) 5839 ; 5840 if(!hasOption || !$option.hasClass(className.addition)) { 5841 return; 5842 } 5843 // temporarily disconnect observer 5844 if(selectObserver) { 5845 selectObserver.disconnect(); 5846 module.verbose('Temporarily disconnecting mutation observer'); 5847 } 5848 $option.remove(); 5849 module.verbose('Removing user addition as an <option>', escapedValue); 5850 if(selectObserver) { 5851 selectObserver.observe($input[0], { 5852 childList : true, 5853 subtree : true 5854 }); 5855 } 5856 }, 5857 message: function() { 5858 $menu.children(selector.message).remove(); 5859 }, 5860 searchWidth: function() { 5861 $search.css('width', ''); 5862 }, 5863 searchTerm: function() { 5864 module.verbose('Cleared search term'); 5865 $search.val(''); 5866 module.set.filtered(); 5867 }, 5868 userAddition: function() { 5869 $item.filter(selector.addition).remove(); 5870 }, 5871 selected: function(value, $selectedItem) { 5872 $selectedItem = (settings.allowAdditions) 5873 ? $selectedItem || module.get.itemWithAdditions(value) 5874 : $selectedItem || module.get.item(value) 5875 ; 5876 5877 if(!$selectedItem) { 5878 return false; 5879 } 5880 5881 $selectedItem 5882 .each(function() { 5883 var 5884 $selected = $(this), 5885 selectedText = module.get.choiceText($selected), 5886 selectedValue = module.get.choiceValue($selected, selectedText) 5887 ; 5888 if(module.is.multiple()) { 5889 if(settings.useLabels) { 5890 module.remove.value(selectedValue, selectedText, $selected); 5891 module.remove.label(selectedValue); 5892 } 5893 else { 5894 module.remove.value(selectedValue, selectedText, $selected); 5895 if(module.get.selectionCount() === 0) { 5896 module.set.placeholderText(); 5897 } 5898 else { 5899 module.set.text(module.add.variables(message.count)); 5900 } 5901 } 5902 } 5903 else { 5904 module.remove.value(selectedValue, selectedText, $selected); 5905 } 5906 $selected 5907 .removeClass(className.filtered) 5908 .removeClass(className.active) 5909 ; 5910 if(settings.useLabels) { 5911 $selected.removeClass(className.selected); 5912 } 5913 }) 5914 ; 5915 }, 5916 selectedItem: function() { 5917 $item.removeClass(className.selected); 5918 }, 5919 value: function(removedValue, removedText, $removedItem) { 5920 var 5921 values = module.get.values(), 5922 newValue 5923 ; 5924 removedValue = module.escape.htmlEntities(removedValue); 5925 if( module.has.selectInput() ) { 5926 module.verbose('Input is <select> removing selected option', removedValue); 5927 newValue = module.remove.arrayValue(removedValue, values); 5928 module.remove.optionValue(removedValue); 5929 } 5930 else { 5931 module.verbose('Removing from delimited values', removedValue); 5932 newValue = module.remove.arrayValue(removedValue, values); 5933 newValue = newValue.join(settings.delimiter); 5934 } 5935 if(settings.fireOnInit === false && module.is.initialLoad()) { 5936 module.verbose('No callback on initial load', settings.onRemove); 5937 } 5938 else { 5939 settings.onRemove.call(element, removedValue, removedText, $removedItem); 5940 } 5941 module.set.value(newValue, removedText, $removedItem); 5942 module.check.maxSelections(); 5943 }, 5944 arrayValue: function(removedValue, values) { 5945 if( !Array.isArray(values) ) { 5946 values = [values]; 5947 } 5948 values = $.grep(values, function(value){ 5949 return (removedValue != value); 5950 }); 5951 module.verbose('Removed value from delimited string', removedValue, values); 5952 return values; 5953 }, 5954 label: function(value, shouldAnimate) { 5955 var 5956 $labels = $module.find(selector.label), 5957 $removedLabel = $labels.filter('[data-' + metadata.value + '="' + module.escape.string(settings.ignoreCase ? value.toLowerCase() : value) +'"]') 5958 ; 5959 module.verbose('Removing label', $removedLabel); 5960 $removedLabel.remove(); 5961 }, 5962 activeLabels: function($activeLabels) { 5963 $activeLabels = $activeLabels || $module.find(selector.label).filter('.' + className.active); 5964 module.verbose('Removing active label selections', $activeLabels); 5965 module.remove.labels($activeLabels); 5966 }, 5967 labels: function($labels) { 5968 $labels = $labels || $module.find(selector.label); 5969 module.verbose('Removing labels', $labels); 5970 $labels 5971 .each(function(){ 5972 var 5973 $label = $(this), 5974 value = $label.data(metadata.value), 5975 stringValue = (value !== undefined) 5976 ? String(value) 5977 : value, 5978 isUserValue = module.is.userValue(stringValue) 5979 ; 5980 if(settings.onLabelRemove.call($label, value) === false) { 5981 module.debug('Label remove callback cancelled removal'); 5982 return; 5983 } 5984 module.remove.message(); 5985 if(isUserValue) { 5986 module.remove.value(stringValue); 5987 module.remove.label(stringValue); 5988 } 5989 else { 5990 // selected will also remove label 5991 module.remove.selected(stringValue); 5992 } 5993 }) 5994 ; 5995 }, 5996 tabbable: function() { 5997 if( module.is.searchSelection() ) { 5998 module.debug('Searchable dropdown initialized'); 5999 $search 6000 .removeAttr('tabindex') 6001 ; 6002 $menu 6003 .removeAttr('tabindex') 6004 ; 6005 } 6006 else { 6007 module.debug('Simple selection dropdown initialized'); 6008 $module 6009 .removeAttr('tabindex') 6010 ; 6011 $menu 6012 .removeAttr('tabindex') 6013 ; 6014 } 6015 }, 6016 diacritics: function(text) { 6017 return settings.ignoreDiacritics ? text.normalize('NFD').replace(/[\u0300-\u036f]/g, '') : text; 6018 } 6019 }, 6020 6021 has: { 6022 menuSearch: function() { 6023 return (module.has.search() && $search.closest($menu).length > 0); 6024 }, 6025 clearItem: function() { 6026 return ($clear.length > 0); 6027 }, 6028 search: function() { 6029 return ($search.length > 0); 6030 }, 6031 sizer: function() { 6032 return ($sizer.length > 0); 6033 }, 6034 selectInput: function() { 6035 return ( $input.is('select') ); 6036 }, 6037 minCharacters: function(searchTerm) { 6038 if(settings.minCharacters && !iconClicked) { 6039 searchTerm = (searchTerm !== undefined) 6040 ? String(searchTerm) 6041 : String(module.get.query()) 6042 ; 6043 return (searchTerm.length >= settings.minCharacters); 6044 } 6045 iconClicked=false; 6046 return true; 6047 }, 6048 firstLetter: function($item, letter) { 6049 var 6050 text, 6051 firstLetter 6052 ; 6053 if(!$item || $item.length === 0 || typeof letter !== 'string') { 6054 return false; 6055 } 6056 text = module.get.choiceText($item, false); 6057 letter = letter.toLowerCase(); 6058 firstLetter = String(text).charAt(0).toLowerCase(); 6059 return (letter == firstLetter); 6060 }, 6061 input: function() { 6062 return ($input.length > 0); 6063 }, 6064 items: function() { 6065 return ($item.length > 0); 6066 }, 6067 menu: function() { 6068 return ($menu.length > 0); 6069 }, 6070 message: function() { 6071 return ($menu.children(selector.message).length !== 0); 6072 }, 6073 label: function(value) { 6074 var 6075 escapedValue = module.escape.value(value), 6076 $labels = $module.find(selector.label) 6077 ; 6078 if(settings.ignoreCase) { 6079 escapedValue = escapedValue.toLowerCase(); 6080 } 6081 return ($labels.filter('[data-' + metadata.value + '="' + module.escape.string(escapedValue) +'"]').length > 0); 6082 }, 6083 maxSelections: function() { 6084 return (settings.maxSelections && module.get.selectionCount() >= settings.maxSelections); 6085 }, 6086 allResultsFiltered: function() { 6087 var 6088 $normalResults = $item.not(selector.addition) 6089 ; 6090 return ($normalResults.filter(selector.unselectable).length === $normalResults.length); 6091 }, 6092 userSuggestion: function() { 6093 return ($menu.children(selector.addition).length > 0); 6094 }, 6095 query: function() { 6096 return (module.get.query() !== ''); 6097 }, 6098 value: function(value) { 6099 return (settings.ignoreCase) 6100 ? module.has.valueIgnoringCase(value) 6101 : module.has.valueMatchingCase(value) 6102 ; 6103 }, 6104 valueMatchingCase: function(value) { 6105 var 6106 values = module.get.values(), 6107 hasValue = Array.isArray(values) 6108 ? values && ($.inArray(value, values) !== -1) 6109 : (values == value) 6110 ; 6111 return (hasValue) 6112 ? true 6113 : false 6114 ; 6115 }, 6116 valueIgnoringCase: function(value) { 6117 var 6118 values = module.get.values(), 6119 hasValue = false 6120 ; 6121 if(!Array.isArray(values)) { 6122 values = [values]; 6123 } 6124 $.each(values, function(index, existingValue) { 6125 if(String(value).toLowerCase() == String(existingValue).toLowerCase()) { 6126 hasValue = true; 6127 return false; 6128 } 6129 }); 6130 return hasValue; 6131 } 6132 }, 6133 6134 is: { 6135 active: function() { 6136 return $module.hasClass(className.active); 6137 }, 6138 animatingInward: function() { 6139 return $menu.transition('is inward'); 6140 }, 6141 animatingOutward: function() { 6142 return $menu.transition('is outward'); 6143 }, 6144 bubbledLabelClick: function(event) { 6145 return $(event.target).is('select, input') && $module.closest('label').length > 0; 6146 }, 6147 bubbledIconClick: function(event) { 6148 return $(event.target).closest($icon).length > 0; 6149 }, 6150 alreadySetup: function() { 6151 return ($module.is('select') && $module.parent(selector.dropdown).data(moduleNamespace) !== undefined && $module.prev().length === 0); 6152 }, 6153 animating: function($subMenu) { 6154 return ($subMenu) 6155 ? $subMenu.transition && $subMenu.transition('is animating') 6156 : $menu.transition && $menu.transition('is animating') 6157 ; 6158 }, 6159 leftward: function($subMenu) { 6160 var $selectedMenu = $subMenu || $menu; 6161 return $selectedMenu.hasClass(className.leftward); 6162 }, 6163 clearable: function() { 6164 return ($module.hasClass(className.clearable) || settings.clearable); 6165 }, 6166 disabled: function() { 6167 return $module.hasClass(className.disabled); 6168 }, 6169 focused: function() { 6170 return (document.activeElement === $module[0]); 6171 }, 6172 focusedOnSearch: function() { 6173 return (document.activeElement === $search[0]); 6174 }, 6175 allFiltered: function() { 6176 return( (module.is.multiple() || module.has.search()) && !(settings.hideAdditions == false && module.has.userSuggestion()) && !module.has.message() && module.has.allResultsFiltered() ); 6177 }, 6178 hidden: function($subMenu) { 6179 return !module.is.visible($subMenu); 6180 }, 6181 initialLoad: function() { 6182 return initialLoad; 6183 }, 6184 inObject: function(needle, object) { 6185 var 6186 found = false 6187 ; 6188 $.each(object, function(index, property) { 6189 if(property == needle) { 6190 found = true; 6191 return true; 6192 } 6193 }); 6194 return found; 6195 }, 6196 multiple: function() { 6197 return $module.hasClass(className.multiple); 6198 }, 6199 remote: function() { 6200 return settings.apiSettings && module.can.useAPI(); 6201 }, 6202 single: function() { 6203 return !module.is.multiple(); 6204 }, 6205 selectMutation: function(mutations) { 6206 var 6207 selectChanged = false 6208 ; 6209 $.each(mutations, function(index, mutation) { 6210 if($(mutation.target).is('select') || $(mutation.addedNodes).is('select')) { 6211 selectChanged = true; 6212 return false; 6213 } 6214 }); 6215 return selectChanged; 6216 }, 6217 search: function() { 6218 return $module.hasClass(className.search); 6219 }, 6220 searchSelection: function() { 6221 return ( module.has.search() && $search.parent(selector.dropdown).length === 1 ); 6222 }, 6223 selection: function() { 6224 return $module.hasClass(className.selection); 6225 }, 6226 userValue: function(value) { 6227 return ($.inArray(value, module.get.userValues()) !== -1); 6228 }, 6229 upward: function($menu) { 6230 var $element = $menu || $module; 6231 return $element.hasClass(className.upward); 6232 }, 6233 visible: function($subMenu) { 6234 return ($subMenu) 6235 ? $subMenu.hasClass(className.visible) 6236 : $menu.hasClass(className.visible) 6237 ; 6238 }, 6239 verticallyScrollableContext: function() { 6240 var 6241 overflowY = ($context.get(0) !== window) 6242 ? $context.css('overflow-y') 6243 : false 6244 ; 6245 return (overflowY == 'auto' || overflowY == 'scroll'); 6246 }, 6247 horizontallyScrollableContext: function() { 6248 var 6249 overflowX = ($context.get(0) !== window) 6250 ? $context.css('overflow-X') 6251 : false 6252 ; 6253 return (overflowX == 'auto' || overflowX == 'scroll'); 6254 } 6255 }, 6256 6257 can: { 6258 activate: function($item) { 6259 if(settings.useLabels) { 6260 return true; 6261 } 6262 if(!module.has.maxSelections()) { 6263 return true; 6264 } 6265 if(module.has.maxSelections() && $item.hasClass(className.active)) { 6266 return true; 6267 } 6268 return false; 6269 }, 6270 openDownward: function($subMenu) { 6271 var 6272 $currentMenu = $subMenu || $menu, 6273 canOpenDownward = true, 6274 onScreen = {}, 6275 calculations 6276 ; 6277 $currentMenu 6278 .addClass(className.loading) 6279 ; 6280 calculations = { 6281 context: { 6282 offset : ($context.get(0) === window) 6283 ? { top: 0, left: 0} 6284 : $context.offset(), 6285 scrollTop : $context.scrollTop(), 6286 height : $context.outerHeight() 6287 }, 6288 menu : { 6289 offset: $currentMenu.offset(), 6290 height: $currentMenu.outerHeight() 6291 } 6292 }; 6293 if(module.is.verticallyScrollableContext()) { 6294 calculations.menu.offset.top += calculations.context.scrollTop; 6295 } 6296 onScreen = { 6297 above : (calculations.context.scrollTop) <= calculations.menu.offset.top - calculations.context.offset.top - calculations.menu.height, 6298 below : (calculations.context.scrollTop + calculations.context.height) >= calculations.menu.offset.top - calculations.context.offset.top + calculations.menu.height 6299 }; 6300 if(onScreen.below) { 6301 module.verbose('Dropdown can fit in context downward', onScreen); 6302 canOpenDownward = true; 6303 } 6304 else if(!onScreen.below && !onScreen.above) { 6305 module.verbose('Dropdown cannot fit in either direction, favoring downward', onScreen); 6306 canOpenDownward = true; 6307 } 6308 else { 6309 module.verbose('Dropdown cannot fit below, opening upward', onScreen); 6310 canOpenDownward = false; 6311 } 6312 $currentMenu.removeClass(className.loading); 6313 return canOpenDownward; 6314 }, 6315 openRightward: function($subMenu) { 6316 var 6317 $currentMenu = $subMenu || $menu, 6318 canOpenRightward = true, 6319 isOffscreenRight = false, 6320 calculations 6321 ; 6322 $currentMenu 6323 .addClass(className.loading) 6324 ; 6325 calculations = { 6326 context: { 6327 offset : ($context.get(0) === window) 6328 ? { top: 0, left: 0} 6329 : $context.offset(), 6330 scrollLeft : $context.scrollLeft(), 6331 width : $context.outerWidth() 6332 }, 6333 menu: { 6334 offset : $currentMenu.offset(), 6335 width : $currentMenu.outerWidth() 6336 } 6337 }; 6338 if(module.is.horizontallyScrollableContext()) { 6339 calculations.menu.offset.left += calculations.context.scrollLeft; 6340 } 6341 isOffscreenRight = (calculations.menu.offset.left - calculations.context.offset.left + calculations.menu.width >= calculations.context.scrollLeft + calculations.context.width); 6342 if(isOffscreenRight) { 6343 module.verbose('Dropdown cannot fit in context rightward', isOffscreenRight); 6344 canOpenRightward = false; 6345 } 6346 $currentMenu.removeClass(className.loading); 6347 return canOpenRightward; 6348 }, 6349 click: function() { 6350 return (hasTouch || settings.on == 'click'); 6351 }, 6352 extendSelect: function() { 6353 return settings.allowAdditions || settings.apiSettings; 6354 }, 6355 show: function() { 6356 return !module.is.disabled() && (module.has.items() || module.has.message()); 6357 }, 6358 useAPI: function() { 6359 return $.fn.api !== undefined; 6360 } 6361 }, 6362 6363 animate: { 6364 show: function(callback, $subMenu) { 6365 var 6366 $currentMenu = $subMenu || $menu, 6367 start = ($subMenu) 6368 ? function() {} 6369 : function() { 6370 module.hideSubMenus(); 6371 module.hideOthers(); 6372 module.set.active(); 6373 }, 6374 transition 6375 ; 6376 callback = $.isFunction(callback) 6377 ? callback 6378 : function(){} 6379 ; 6380 module.verbose('Doing menu show animation', $currentMenu); 6381 module.set.direction($subMenu); 6382 transition = module.get.transition($subMenu); 6383 if( module.is.selection() ) { 6384 module.set.scrollPosition(module.get.selectedItem(), true); 6385 } 6386 if( module.is.hidden($currentMenu) || module.is.animating($currentMenu) ) { 6387 var displayType = $module.hasClass('column') ? 'flex' : false; 6388 if(transition == 'none') { 6389 start(); 6390 $currentMenu.transition({ 6391 displayType: displayType 6392 }).transition('show'); 6393 callback.call(element); 6394 } 6395 else if($.fn.transition !== undefined && $module.transition('is supported')) { 6396 $currentMenu 6397 .transition({ 6398 animation : transition + ' in', 6399 debug : settings.debug, 6400 verbose : settings.verbose, 6401 duration : settings.duration, 6402 queue : true, 6403 onStart : start, 6404 displayType: displayType, 6405 onComplete : function() { 6406 callback.call(element); 6407 } 6408 }) 6409 ; 6410 } 6411 else { 6412 module.error(error.noTransition, transition); 6413 } 6414 } 6415 }, 6416 hide: function(callback, $subMenu) { 6417 var 6418 $currentMenu = $subMenu || $menu, 6419 start = ($subMenu) 6420 ? function() {} 6421 : function() { 6422 if( module.can.click() ) { 6423 module.unbind.intent(); 6424 } 6425 module.remove.active(); 6426 }, 6427 transition = module.get.transition($subMenu) 6428 ; 6429 callback = $.isFunction(callback) 6430 ? callback 6431 : function(){} 6432 ; 6433 if( module.is.visible($currentMenu) || module.is.animating($currentMenu) ) { 6434 module.verbose('Doing menu hide animation', $currentMenu); 6435 6436 if(transition == 'none') { 6437 start(); 6438 $currentMenu.transition('hide'); 6439 callback.call(element); 6440 } 6441 else if($.fn.transition !== undefined && $module.transition('is supported')) { 6442 $currentMenu 6443 .transition({ 6444 animation : transition + ' out', 6445 duration : settings.duration, 6446 debug : settings.debug, 6447 verbose : settings.verbose, 6448 queue : false, 6449 onStart : start, 6450 onComplete : function() { 6451 callback.call(element); 6452 } 6453 }) 6454 ; 6455 } 6456 else { 6457 module.error(error.transition); 6458 } 6459 } 6460 } 6461 }, 6462 6463 hideAndClear: function() { 6464 module.remove.searchTerm(); 6465 if( module.has.maxSelections() ) { 6466 return; 6467 } 6468 if(module.has.search()) { 6469 module.hide(function() { 6470 module.remove.filteredItem(); 6471 }); 6472 } 6473 else { 6474 module.hide(); 6475 } 6476 }, 6477 6478 delay: { 6479 show: function() { 6480 module.verbose('Delaying show event to ensure user intent'); 6481 clearTimeout(module.timer); 6482 module.timer = setTimeout(module.show, settings.delay.show); 6483 }, 6484 hide: function() { 6485 module.verbose('Delaying hide event to ensure user intent'); 6486 clearTimeout(module.timer); 6487 module.timer = setTimeout(module.hide, settings.delay.hide); 6488 } 6489 }, 6490 6491 escape: { 6492 value: function(value) { 6493 var 6494 multipleValues = Array.isArray(value), 6495 stringValue = (typeof value === 'string'), 6496 isUnparsable = (!stringValue && !multipleValues), 6497 hasQuotes = (stringValue && value.search(regExp.quote) !== -1), 6498 values = [] 6499 ; 6500 if(isUnparsable || !hasQuotes) { 6501 return value; 6502 } 6503 module.debug('Encoding quote values for use in select', value); 6504 if(multipleValues) { 6505 $.each(value, function(index, value){ 6506 values.push(value.replace(regExp.quote, '"')); 6507 }); 6508 return values; 6509 } 6510 return value.replace(regExp.quote, '"'); 6511 }, 6512 string: function(text) { 6513 text = String(text); 6514 return text.replace(regExp.escape, '\\$&'); 6515 }, 6516 htmlEntities: function(string) { 6517 var 6518 badChars = /[<>"'`]/g, 6519 shouldEscape = /[&<>"'`]/, 6520 escape = { 6521 "<": "<", 6522 ">": ">", 6523 '"': """, 6524 "'": "'", 6525 "`": "`" 6526 }, 6527 escapedChar = function(chr) { 6528 return escape[chr]; 6529 } 6530 ; 6531 if(shouldEscape.test(string)) { 6532 string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&"); 6533 return string.replace(badChars, escapedChar); 6534 } 6535 return string; 6536 } 6537 }, 6538 6539 setting: function(name, value) { 6540 module.debug('Changing setting', name, value); 6541 if( $.isPlainObject(name) ) { 6542 $.extend(true, settings, name); 6543 } 6544 else if(value !== undefined) { 6545 if($.isPlainObject(settings[name])) { 6546 $.extend(true, settings[name], value); 6547 } 6548 else { 6549 settings[name] = value; 6550 } 6551 } 6552 else { 6553 return settings[name]; 6554 } 6555 }, 6556 internal: function(name, value) { 6557 if( $.isPlainObject(name) ) { 6558 $.extend(true, module, name); 6559 } 6560 else if(value !== undefined) { 6561 module[name] = value; 6562 } 6563 else { 6564 return module[name]; 6565 } 6566 }, 6567 debug: function() { 6568 if(!settings.silent && settings.debug) { 6569 if(settings.performance) { 6570 module.performance.log(arguments); 6571 } 6572 else { 6573 module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); 6574 module.debug.apply(console, arguments); 6575 } 6576 } 6577 }, 6578 verbose: function() { 6579 if(!settings.silent && settings.verbose && settings.debug) { 6580 if(settings.performance) { 6581 module.performance.log(arguments); 6582 } 6583 else { 6584 module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); 6585 module.verbose.apply(console, arguments); 6586 } 6587 } 6588 }, 6589 error: function() { 6590 if(!settings.silent) { 6591 module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); 6592 module.error.apply(console, arguments); 6593 } 6594 }, 6595 performance: { 6596 log: function(message) { 6597 var 6598 currentTime, 6599 executionTime, 6600 previousTime 6601 ; 6602 if(settings.performance) { 6603 currentTime = new Date().getTime(); 6604 previousTime = time || currentTime; 6605 executionTime = currentTime - previousTime; 6606 time = currentTime; 6607 performance.push({ 6608 'Name' : message[0], 6609 'Arguments' : [].slice.call(message, 1) || '', 6610 'Element' : element, 6611 'Execution Time' : executionTime 6612 }); 6613 } 6614 clearTimeout(module.performance.timer); 6615 module.performance.timer = setTimeout(module.performance.display, 500); 6616 }, 6617 display: function() { 6618 var 6619 title = settings.name + ':', 6620 totalTime = 0 6621 ; 6622 time = false; 6623 clearTimeout(module.performance.timer); 6624 $.each(performance, function(index, data) { 6625 totalTime += data['Execution Time']; 6626 }); 6627 title += ' ' + totalTime + 'ms'; 6628 if(moduleSelector) { 6629 title += ' \'' + moduleSelector + '\''; 6630 } 6631 if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { 6632 console.groupCollapsed(title); 6633 if(console.table) { 6634 console.table(performance); 6635 } 6636 else { 6637 $.each(performance, function(index, data) { 6638 console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); 6639 }); 6640 } 6641 console.groupEnd(); 6642 } 6643 performance = []; 6644 } 6645 }, 6646 invoke: function(query, passedArguments, context) { 6647 var 6648 object = instance, 6649 maxDepth, 6650 found, 6651 response 6652 ; 6653 passedArguments = passedArguments || queryArguments; 6654 context = element || context; 6655 if(typeof query == 'string' && object !== undefined) { 6656 query = query.split(/[\. ]/); 6657 maxDepth = query.length - 1; 6658 $.each(query, function(depth, value) { 6659 var camelCaseValue = (depth != maxDepth) 6660 ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) 6661 : query 6662 ; 6663 if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { 6664 object = object[camelCaseValue]; 6665 } 6666 else if( object[camelCaseValue] !== undefined ) { 6667 found = object[camelCaseValue]; 6668 return false; 6669 } 6670 else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { 6671 object = object[value]; 6672 } 6673 else if( object[value] !== undefined ) { 6674 found = object[value]; 6675 return false; 6676 } 6677 else { 6678 module.error(error.method, query); 6679 return false; 6680 } 6681 }); 6682 } 6683 if ( $.isFunction( found ) ) { 6684 response = found.apply(context, passedArguments); 6685 } 6686 else if(found !== undefined) { 6687 response = found; 6688 } 6689 if(Array.isArray(returnedValue)) { 6690 returnedValue.push(response); 6691 } 6692 else if(returnedValue !== undefined) { 6693 returnedValue = [returnedValue, response]; 6694 } 6695 else if(response !== undefined) { 6696 returnedValue = response; 6697 } 6698 return found; 6699 } 6700 }; 6701 6702 if(methodInvoked) { 6703 if(instance === undefined) { 6704 module.initialize(); 6705 } 6706 module.invoke(query); 6707 } 6708 else { 6709 if(instance !== undefined) { 6710 instance.invoke('destroy'); 6711 } 6712 module.initialize(); 6713 } 6714 }) 6715 ; 6716 return (returnedValue !== undefined) 6717 ? returnedValue 6718 : $allModules 6719 ; 6720 }; 6721 6722 $.fn.dropdown.settings = { 6723 6724 silent : false, 6725 debug : false, 6726 verbose : false, 6727 performance : true, 6728 6729 on : 'click', // what event should show menu action on item selection 6730 action : 'activate', // action on item selection (nothing, activate, select, combo, hide, function(){}) 6731 6732 values : false, // specify values to use for dropdown 6733 6734 clearable : false, // whether the value of the dropdown can be cleared 6735 6736 apiSettings : false, 6737 selectOnKeydown : true, // Whether selection should occur automatically when keyboard shortcuts used 6738 minCharacters : 0, // Minimum characters required to trigger API call 6739 6740 filterRemoteData : false, // Whether API results should be filtered after being returned for query term 6741 saveRemoteData : true, // Whether remote name/value pairs should be stored in sessionStorage to allow remote data to be restored on page refresh 6742 6743 throttle : 200, // How long to wait after last user input to search remotely 6744 6745 context : window, // Context to use when determining if on screen 6746 direction : 'auto', // Whether dropdown should always open in one direction 6747 keepOnScreen : true, // Whether dropdown should check whether it is on screen before showing 6748 6749 match : 'both', // what to match against with search selection (both, text, or label) 6750 fullTextSearch : false, // search anywhere in value (set to 'exact' to require exact matches) 6751 ignoreDiacritics : false, // match results also if they contain diacritics of the same base character (for example searching for "a" will also match "á" or "â" or "à ", etc...) 6752 hideDividers : false, // Whether to hide any divider elements (specified in selector.divider) that are sibling to any items when searched (set to true will hide all dividers, set to 'empty' will hide them when they are not followed by a visible item) 6753 6754 placeholder : 'auto', // whether to convert blank <select> values to placeholder text 6755 preserveHTML : true, // preserve html when selecting value 6756 sortSelect : false, // sort selection on init 6757 6758 forceSelection : true, // force a choice on blur with search selection 6759 6760 allowAdditions : false, // whether multiple select should allow user added values 6761 ignoreCase : false, // whether to consider case sensitivity when creating labels 6762 ignoreSearchCase : true, // whether to consider case sensitivity when filtering items 6763 hideAdditions : true, // whether or not to hide special message prompting a user they can enter a value 6764 6765 maxSelections : false, // When set to a number limits the number of selections to this count 6766 useLabels : true, // whether multiple select should filter currently active selections from choices 6767 delimiter : ',', // when multiselect uses normal <input> the values will be delimited with this character 6768 6769 showOnFocus : true, // show menu on focus 6770 allowReselection : false, // whether current value should trigger callbacks when reselected 6771 allowTab : true, // add tabindex to element 6772 allowCategorySelection : false, // allow elements with sub-menus to be selected 6773 6774 fireOnInit : false, // Whether callbacks should fire when initializing dropdown values 6775 6776 transition : 'auto', // auto transition will slide down or up based on direction 6777 duration : 200, // duration of transition 6778 6779 glyphWidth : 1.037, // widest glyph width in em (W is 1.037 em) used to calculate multiselect input width 6780 6781 headerDivider : true, // whether option headers should have an additional divider line underneath when converted from <select> <optgroup> 6782 6783 // label settings on multi-select 6784 label: { 6785 transition : 'scale', 6786 duration : 200, 6787 variation : false 6788 }, 6789 6790 // delay before event 6791 delay : { 6792 hide : 300, 6793 show : 200, 6794 search : 20, 6795 touch : 50 6796 }, 6797 6798 /* Callbacks */ 6799 onChange : function(value, text, $selected){}, 6800 onAdd : function(value, text, $selected){}, 6801 onRemove : function(value, text, $selected){}, 6802 6803 onLabelSelect : function($selectedLabels){}, 6804 onLabelCreate : function(value, text) { return $(this); }, 6805 onLabelRemove : function(value) { return true; }, 6806 onNoResults : function(searchTerm) { return true; }, 6807 onShow : function(){}, 6808 onHide : function(){}, 6809 6810 /* Component */ 6811 name : 'Dropdown', 6812 namespace : 'dropdown', 6813 6814 message: { 6815 addResult : 'Add <b>{term}</b>', 6816 count : '{count} selected', 6817 maxSelections : 'Max {maxCount} selections', 6818 noResults : 'No results found.', 6819 serverError : 'There was an error contacting the server' 6820 }, 6821 6822 error : { 6823 action : 'You called a dropdown action that was not defined', 6824 alreadySetup : 'Once a select has been initialized behaviors must be called on the created ui dropdown', 6825 labels : 'Allowing user additions currently requires the use of labels.', 6826 missingMultiple : '<select> requires multiple property to be set to correctly preserve multiple values', 6827 method : 'The method you called is not defined.', 6828 noAPI : 'The API module is required to load resources remotely', 6829 noStorage : 'Saving remote data requires session storage', 6830 noTransition : 'This module requires ui transitions <https://github.com/Semantic-Org/UI-Transition>', 6831 noNormalize : '"ignoreDiacritics" setting will be ignored. Browser does not support String().normalize(). You may consider including <https://cdn.jsdelivr.net/npm/unorm@1.4.1/lib/unorm.min.js> as a polyfill.' 6832 }, 6833 6834 regExp : { 6835 escape : /[-[\]{}()*+?.,\\^$|#\s:=@]/g, 6836 quote : /"/g 6837 }, 6838 6839 metadata : { 6840 defaultText : 'defaultText', 6841 defaultValue : 'defaultValue', 6842 placeholderText : 'placeholder', 6843 text : 'text', 6844 value : 'value' 6845 }, 6846 6847 // property names for remote query 6848 fields: { 6849 remoteValues : 'results', // grouping for api results 6850 values : 'values', // grouping for all dropdown values 6851 disabled : 'disabled', // whether value should be disabled 6852 name : 'name', // displayed dropdown text 6853 value : 'value', // actual dropdown value 6854 text : 'text', // displayed text when selected 6855 type : 'type', // type of dropdown element 6856 image : 'image', // optional image path 6857 imageClass : 'imageClass', // optional individual class for image 6858 icon : 'icon', // optional icon name 6859 iconClass : 'iconClass', // optional individual class for icon (for example to use flag instead) 6860 class : 'class', // optional individual class for item/header 6861 divider : 'divider' // optional divider append for group headers 6862 }, 6863 6864 keys : { 6865 backspace : 8, 6866 delimiter : 188, // comma 6867 deleteKey : 46, 6868 enter : 13, 6869 escape : 27, 6870 pageUp : 33, 6871 pageDown : 34, 6872 leftArrow : 37, 6873 upArrow : 38, 6874 rightArrow : 39, 6875 downArrow : 40 6876 }, 6877 6878 selector : { 6879 addition : '.addition', 6880 divider : '.divider, .header', 6881 dropdown : '.ui.dropdown', 6882 hidden : '.hidden', 6883 icon : '> .dropdown.icon', 6884 input : '> input[type="hidden"], > select', 6885 item : '.item', 6886 label : '> .label', 6887 remove : '> .label > .delete.icon', 6888 siblingLabel : '.label', 6889 menu : '.menu', 6890 message : '.message', 6891 menuIcon : '.dropdown.icon', 6892 search : 'input.search, .menu > .search > input, .menu input.search', 6893 sizer : '> span.sizer', 6894 text : '> .text:not(.icon)', 6895 unselectable : '.disabled, .filtered', 6896 clearIcon : '> .remove.icon' 6897 }, 6898 6899 className : { 6900 active : 'active', 6901 addition : 'addition', 6902 animating : 'animating', 6903 disabled : 'disabled', 6904 empty : 'empty', 6905 dropdown : 'ui dropdown', 6906 filtered : 'filtered', 6907 hidden : 'hidden transition', 6908 icon : 'icon', 6909 image : 'image', 6910 item : 'item', 6911 label : 'ui label', 6912 loading : 'loading', 6913 menu : 'menu', 6914 message : 'message', 6915 multiple : 'multiple', 6916 placeholder : 'default', 6917 sizer : 'sizer', 6918 search : 'search', 6919 selected : 'selected', 6920 selection : 'selection', 6921 upward : 'upward', 6922 leftward : 'left', 6923 visible : 'visible', 6924 clearable : 'clearable', 6925 noselection : 'noselection', 6926 delete : 'delete', 6927 header : 'header', 6928 divider : 'divider', 6929 groupIcon : '', 6930 unfilterable : 'unfilterable' 6931 } 6932 6933 }; 6934 6935 /* Templates */ 6936 $.fn.dropdown.settings.templates = { 6937 deQuote: function(string) { 6938 return String(string).replace(/"/g,""); 6939 }, 6940 escape: function(string, preserveHTML) { 6941 if (preserveHTML){ 6942 return string; 6943 } 6944 var 6945 badChars = /[<>"'`]/g, 6946 shouldEscape = /[&<>"'`]/, 6947 escape = { 6948 "<": "<", 6949 ">": ">", 6950 '"': """, 6951 "'": "'", 6952 "`": "`" 6953 }, 6954 escapedChar = function(chr) { 6955 return escape[chr]; 6956 } 6957 ; 6958 if(shouldEscape.test(string)) { 6959 string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&"); 6960 return string.replace(badChars, escapedChar); 6961 } 6962 return string; 6963 }, 6964 // generates dropdown from select values 6965 dropdown: function(select, fields, preserveHTML, className) { 6966 var 6967 placeholder = select.placeholder || false, 6968 html = '', 6969 escape = $.fn.dropdown.settings.templates.escape 6970 ; 6971 html += '<i class="dropdown icon"></i>'; 6972 if(placeholder) { 6973 html += '<div class="default text">' + escape(placeholder,preserveHTML) + '</div>'; 6974 } 6975 else { 6976 html += '<div class="text"></div>'; 6977 } 6978 html += '<div class="'+className.menu+'">'; 6979 html += $.fn.dropdown.settings.templates.menu(select, fields, preserveHTML,className); 6980 html += '</div>'; 6981 return html; 6982 }, 6983 6984 // generates just menu from select 6985 menu: function(response, fields, preserveHTML, className) { 6986 var 6987 values = response[fields.values] || [], 6988 html = '', 6989 escape = $.fn.dropdown.settings.templates.escape, 6990 deQuote = $.fn.dropdown.settings.templates.deQuote 6991 ; 6992 $.each(values, function(index, option) { 6993 var 6994 itemType = (option[fields.type]) 6995 ? option[fields.type] 6996 : 'item' 6997 ; 6998 6999 if( itemType === 'item' ) { 7000 var 7001 maybeText = (option[fields.text]) 7002 ? ' data-text="' + deQuote(option[fields.text]) + '"' 7003 : '', 7004 maybeDisabled = (option[fields.disabled]) 7005 ? className.disabled+' ' 7006 : '' 7007 ; 7008 html += '<div class="'+ maybeDisabled + (option[fields.class] ? deQuote(option[fields.class]) : className.item)+'" data-value="' + deQuote(option[fields.value]) + '"' + maybeText + '>'; 7009 if(option[fields.image]) { 7010 html += '<img class="'+(option[fields.imageClass] ? deQuote(option[fields.imageClass]) : className.image)+'" src="' + deQuote(option[fields.image]) + '">'; 7011 } 7012 if(option[fields.icon]) { 7013 html += '<i class="'+deQuote(option[fields.icon])+' '+(option[fields.iconClass] ? deQuote(option[fields.iconClass]) : className.icon)+'"></i>'; 7014 } 7015 html += escape(option[fields.name] || '', preserveHTML); 7016 html += '</div>'; 7017 } else if (itemType === 'header') { 7018 var groupName = escape(option[fields.name] || '', preserveHTML), 7019 groupIcon = option[fields.icon] ? deQuote(option[fields.icon]) : className.groupIcon 7020 ; 7021 if(groupName !== '' || groupIcon !== '') { 7022 html += '<div class="' + (option[fields.class] ? deQuote(option[fields.class]) : className.header) + '">'; 7023 if (groupIcon !== '') { 7024 html += '<i class="' + groupIcon + ' ' + (option[fields.iconClass] ? deQuote(option[fields.iconClass]) : className.icon) + '"></i>'; 7025 } 7026 html += groupName; 7027 html += '</div>'; 7028 } 7029 if(option[fields.divider]){ 7030 html += '<div class="'+className.divider+'"></div>'; 7031 } 7032 } 7033 }); 7034 return html; 7035 }, 7036 7037 // generates label for multiselect 7038 label: function(value, text, preserveHTML, className) { 7039 var 7040 escape = $.fn.dropdown.settings.templates.escape; 7041 return escape(text,preserveHTML) + '<i class="'+className.delete+' icon"></i>'; 7042 }, 7043 7044 7045 // generates messages like "No results" 7046 message: function(message) { 7047 return message; 7048 }, 7049 7050 // generates user addition to selection menu 7051 addition: function(choice) { 7052 return choice; 7053 } 7054 7055 }; 7056 7057 })( jQuery, window, document ); 7058 7059 /*! 7060 * # Fomantic-UI - Form Validation 7061 * http://github.com/fomantic/Fomantic-UI/ 7062 * 7063 * 7064 * Released under the MIT license 7065 * http://opensource.org/licenses/MIT 7066 * 7067 */ 7068 7069 ;(function ($, window, document, undefined) { 7070 7071 'use strict'; 7072 7073 $.isFunction = $.isFunction || function(obj) { 7074 return typeof obj === "function" && typeof obj.nodeType !== "number"; 7075 }; 7076 7077 window = (typeof window != 'undefined' && window.Math == Math) 7078 ? window 7079 : (typeof self != 'undefined' && self.Math == Math) 7080 ? self 7081 : Function('return this')() 7082 ; 7083 7084 $.fn.form = function(parameters) { 7085 var 7086 $allModules = $(this), 7087 moduleSelector = $allModules.selector || '', 7088 7089 time = new Date().getTime(), 7090 performance = [], 7091 7092 query = arguments[0], 7093 legacyParameters = arguments[1], 7094 methodInvoked = (typeof query == 'string'), 7095 queryArguments = [].slice.call(arguments, 1), 7096 returnedValue 7097 ; 7098 $allModules 7099 .each(function() { 7100 var 7101 $module = $(this), 7102 element = this, 7103 7104 formErrors = [], 7105 keyHeldDown = false, 7106 7107 // set at run-time 7108 $field, 7109 $group, 7110 $message, 7111 $prompt, 7112 $submit, 7113 $clear, 7114 $reset, 7115 7116 settings, 7117 validation, 7118 7119 metadata, 7120 selector, 7121 className, 7122 regExp, 7123 error, 7124 7125 namespace, 7126 moduleNamespace, 7127 eventNamespace, 7128 7129 submitting = false, 7130 dirty = false, 7131 history = ['clean', 'clean'], 7132 7133 instance, 7134 module 7135 ; 7136 7137 module = { 7138 7139 initialize: function() { 7140 7141 // settings grabbed at run time 7142 module.get.settings(); 7143 if(methodInvoked) { 7144 if(instance === undefined) { 7145 module.instantiate(); 7146 } 7147 module.invoke(query); 7148 } 7149 else { 7150 if(instance !== undefined) { 7151 instance.invoke('destroy'); 7152 } 7153 module.verbose('Initializing form validation', $module, settings); 7154 module.bindEvents(); 7155 module.set.defaults(); 7156 if (settings.autoCheckRequired) { 7157 module.set.autoCheck(); 7158 } 7159 module.instantiate(); 7160 } 7161 }, 7162 7163 instantiate: function() { 7164 module.verbose('Storing instance of module', module); 7165 instance = module; 7166 $module 7167 .data(moduleNamespace, module) 7168 ; 7169 }, 7170 7171 destroy: function() { 7172 module.verbose('Destroying previous module', instance); 7173 module.removeEvents(); 7174 $module 7175 .removeData(moduleNamespace) 7176 ; 7177 }, 7178 7179 refresh: function() { 7180 module.verbose('Refreshing selector cache'); 7181 $field = $module.find(selector.field); 7182 $group = $module.find(selector.group); 7183 $message = $module.find(selector.message); 7184 $prompt = $module.find(selector.prompt); 7185 7186 $submit = $module.find(selector.submit); 7187 $clear = $module.find(selector.clear); 7188 $reset = $module.find(selector.reset); 7189 }, 7190 7191 submit: function() { 7192 module.verbose('Submitting form', $module); 7193 submitting = true; 7194 $module.submit(); 7195 }, 7196 7197 attachEvents: function(selector, action) { 7198 action = action || 'submit'; 7199 $(selector).on('click' + eventNamespace, function(event) { 7200 module[action](); 7201 event.preventDefault(); 7202 }); 7203 }, 7204 7205 bindEvents: function() { 7206 module.verbose('Attaching form events'); 7207 $module 7208 .on('submit' + eventNamespace, module.validate.form) 7209 .on('blur' + eventNamespace, selector.field, module.event.field.blur) 7210 .on('click' + eventNamespace, selector.submit, module.submit) 7211 .on('click' + eventNamespace, selector.reset, module.reset) 7212 .on('click' + eventNamespace, selector.clear, module.clear) 7213 ; 7214 if(settings.keyboardShortcuts) { 7215 $module.on('keydown' + eventNamespace, selector.field, module.event.field.keydown); 7216 } 7217 $field.each(function(index, el) { 7218 var 7219 $input = $(el), 7220 type = $input.prop('type'), 7221 inputEvent = module.get.changeEvent(type, $input) 7222 ; 7223 $input.on(inputEvent + eventNamespace, module.event.field.change); 7224 }); 7225 7226 // Dirty events 7227 if (settings.preventLeaving) { 7228 $(window).on('beforeunload' + eventNamespace, module.event.beforeUnload); 7229 } 7230 7231 $field.on('change click keyup keydown blur', function(e) { 7232 $(this).triggerHandler(e.type + ".dirty"); 7233 }); 7234 7235 $field.on('change.dirty click.dirty keyup.dirty keydown.dirty blur.dirty', module.determine.isDirty); 7236 7237 $module.on('dirty' + eventNamespace, function(e) { 7238 settings.onDirty.call(); 7239 }); 7240 7241 $module.on('clean' + eventNamespace, function(e) { 7242 settings.onClean.call(); 7243 }) 7244 }, 7245 7246 clear: function() { 7247 $field.each(function (index, el) { 7248 var 7249 $field = $(el), 7250 $element = $field.parent(), 7251 $fieldGroup = $field.closest($group), 7252 $prompt = $fieldGroup.find(selector.prompt), 7253 $calendar = $field.closest(selector.uiCalendar), 7254 defaultValue = $field.data(metadata.defaultValue) || '', 7255 isCheckbox = $element.is(selector.uiCheckbox), 7256 isDropdown = $element.is(selector.uiDropdown) && module.can.useElement('dropdown'), 7257 isCalendar = ($calendar.length > 0 && module.can.useElement('calendar')), 7258 isErrored = $fieldGroup.hasClass(className.error) 7259 ; 7260 if(isErrored) { 7261 module.verbose('Resetting error on field', $fieldGroup); 7262 $fieldGroup.removeClass(className.error); 7263 $prompt.remove(); 7264 } 7265 if(isDropdown) { 7266 module.verbose('Resetting dropdown value', $element, defaultValue); 7267 $element.dropdown('clear', true); 7268 } 7269 else if(isCheckbox) { 7270 $field.prop('checked', false); 7271 } 7272 else if (isCalendar) { 7273 $calendar.calendar('clear'); 7274 } 7275 else { 7276 module.verbose('Resetting field value', $field, defaultValue); 7277 $field.val(''); 7278 } 7279 }); 7280 module.remove.states(); 7281 }, 7282 7283 reset: function() { 7284 $field.each(function (index, el) { 7285 var 7286 $field = $(el), 7287 $element = $field.parent(), 7288 $fieldGroup = $field.closest($group), 7289 $calendar = $field.closest(selector.uiCalendar), 7290 $prompt = $fieldGroup.find(selector.prompt), 7291 defaultValue = $field.data(metadata.defaultValue), 7292 isCheckbox = $element.is(selector.uiCheckbox), 7293 isDropdown = $element.is(selector.uiDropdown) && module.can.useElement('dropdown'), 7294 isCalendar = ($calendar.length > 0 && module.can.useElement('calendar')), 7295 isErrored = $fieldGroup.hasClass(className.error) 7296 ; 7297 if(defaultValue === undefined) { 7298 return; 7299 } 7300 if(isErrored) { 7301 module.verbose('Resetting error on field', $fieldGroup); 7302 $fieldGroup.removeClass(className.error); 7303 $prompt.remove(); 7304 } 7305 if(isDropdown) { 7306 module.verbose('Resetting dropdown value', $element, defaultValue); 7307 $element.dropdown('restore defaults', true); 7308 } 7309 else if(isCheckbox) { 7310 module.verbose('Resetting checkbox value', $element, defaultValue); 7311 $field.prop('checked', defaultValue); 7312 } 7313 else if (isCalendar) { 7314 $calendar.calendar('set date', defaultValue); 7315 } 7316 else { 7317 module.verbose('Resetting field value', $field, defaultValue); 7318 $field.val(defaultValue); 7319 } 7320 }); 7321 module.remove.states(); 7322 }, 7323 7324 determine: { 7325 isValid: function() { 7326 var 7327 allValid = true 7328 ; 7329 $.each(validation, function(fieldName, field) { 7330 if( !( module.validate.field(field, fieldName, true) ) ) { 7331 allValid = false; 7332 } 7333 }); 7334 return allValid; 7335 }, 7336 isDirty: function(e) { 7337 var formIsDirty = false; 7338 7339 $field.each(function(index, el) { 7340 var 7341 $el = $(el), 7342 isCheckbox = ($el.filter(selector.checkbox).length > 0), 7343 isDirty 7344 ; 7345 7346 if (isCheckbox) { 7347 isDirty = module.is.checkboxDirty($el); 7348 } else { 7349 isDirty = module.is.fieldDirty($el); 7350 } 7351 7352 $el.data(settings.metadata.isDirty, isDirty); 7353 7354 formIsDirty |= isDirty; 7355 }); 7356 7357 if (formIsDirty) { 7358 module.set.dirty(); 7359 } else { 7360 module.set.clean(); 7361 } 7362 7363 if (e && e.namespace === 'dirty') { 7364 e.stopImmediatePropagation(); 7365 e.preventDefault(); 7366 } 7367 } 7368 }, 7369 7370 is: { 7371 bracketedRule: function(rule) { 7372 return (rule.type && rule.type.match(settings.regExp.bracket)); 7373 }, 7374 shorthandFields: function(fields) { 7375 var 7376 fieldKeys = Object.keys(fields), 7377 firstRule = fields[fieldKeys[0]] 7378 ; 7379 return module.is.shorthandRules(firstRule); 7380 }, 7381 // duck type rule test 7382 shorthandRules: function(rules) { 7383 return (typeof rules == 'string' || Array.isArray(rules)); 7384 }, 7385 empty: function($field) { 7386 if(!$field || $field.length === 0) { 7387 return true; 7388 } 7389 else if($field.is(selector.checkbox)) { 7390 return !$field.is(':checked'); 7391 } 7392 else { 7393 return module.is.blank($field); 7394 } 7395 }, 7396 blank: function($field) { 7397 return String($field.val()).trim() === ''; 7398 }, 7399 valid: function(field, showErrors) { 7400 var 7401 allValid = true 7402 ; 7403 if(field) { 7404 module.verbose('Checking if field is valid', field); 7405 return module.validate.field(validation[field], field, !!showErrors); 7406 } 7407 else { 7408 module.verbose('Checking if form is valid'); 7409 $.each(validation, function(fieldName, field) { 7410 if( !module.is.valid(fieldName, showErrors) ) { 7411 allValid = false; 7412 } 7413 }); 7414 return allValid; 7415 } 7416 }, 7417 dirty: function() { 7418 return dirty; 7419 }, 7420 clean: function() { 7421 return !dirty; 7422 }, 7423 fieldDirty: function($el) { 7424 var initialValue = $el.data(metadata.defaultValue); 7425 // Explicitly check for null/undefined here as value may be `false`, so ($el.data(dataInitialValue) || '') would not work 7426 if (initialValue == null) { initialValue = ''; } 7427 else if(Array.isArray(initialValue)) { 7428 initialValue = initialValue.toString(); 7429 } 7430 var currentValue = $el.val(); 7431 if (currentValue == null) { currentValue = ''; } 7432 // multiple select values are returned as arrays which are never equal, so do string conversion first 7433 else if(Array.isArray(currentValue)) { 7434 currentValue = currentValue.toString(); 7435 } 7436 // Boolean values can be encoded as "true/false" or "True/False" depending on underlying frameworks so we need a case insensitive comparison 7437 var boolRegex = /^(true|false)$/i; 7438 var isBoolValue = boolRegex.test(initialValue) && boolRegex.test(currentValue); 7439 if (isBoolValue) { 7440 var regex = new RegExp("^" + initialValue + "$", "i"); 7441 return !regex.test(currentValue); 7442 } 7443 7444 return currentValue !== initialValue; 7445 }, 7446 checkboxDirty: function($el) { 7447 var initialValue = $el.data(metadata.defaultValue); 7448 var currentValue = $el.is(":checked"); 7449 7450 return initialValue !== currentValue; 7451 }, 7452 justDirty: function() { 7453 return (history[0] === 'dirty'); 7454 }, 7455 justClean: function() { 7456 return (history[0] === 'clean'); 7457 } 7458 }, 7459 7460 removeEvents: function() { 7461 $module.off(eventNamespace); 7462 $field.off(eventNamespace); 7463 $submit.off(eventNamespace); 7464 $field.off(eventNamespace); 7465 }, 7466 7467 event: { 7468 field: { 7469 keydown: function(event) { 7470 var 7471 $field = $(this), 7472 key = event.which, 7473 isInput = $field.is(selector.input), 7474 isCheckbox = $field.is(selector.checkbox), 7475 isInDropdown = ($field.closest(selector.uiDropdown).length > 0), 7476 keyCode = { 7477 enter : 13, 7478 escape : 27 7479 } 7480 ; 7481 if( key == keyCode.escape) { 7482 module.verbose('Escape key pressed blurring field'); 7483 $field 7484 .blur() 7485 ; 7486 } 7487 if(!event.ctrlKey && key == keyCode.enter && isInput && !isInDropdown && !isCheckbox) { 7488 if(!keyHeldDown) { 7489 $field.one('keyup' + eventNamespace, module.event.field.keyup); 7490 module.submit(); 7491 module.debug('Enter pressed on input submitting form'); 7492 } 7493 keyHeldDown = true; 7494 } 7495 }, 7496 keyup: function() { 7497 keyHeldDown = false; 7498 }, 7499 blur: function(event) { 7500 var 7501 $field = $(this), 7502 $fieldGroup = $field.closest($group), 7503 validationRules = module.get.validation($field) 7504 ; 7505 if( $fieldGroup.hasClass(className.error) ) { 7506 module.debug('Revalidating field', $field, validationRules); 7507 if(validationRules) { 7508 module.validate.field( validationRules ); 7509 } 7510 } 7511 else if(settings.on == 'blur') { 7512 if(validationRules) { 7513 module.validate.field( validationRules ); 7514 } 7515 } 7516 }, 7517 change: function(event) { 7518 var 7519 $field = $(this), 7520 $fieldGroup = $field.closest($group), 7521 validationRules = module.get.validation($field) 7522 ; 7523 if(validationRules && (settings.on == 'change' || ( $fieldGroup.hasClass(className.error) && settings.revalidate) )) { 7524 clearTimeout(module.timer); 7525 module.timer = setTimeout(function() { 7526 module.debug('Revalidating field', $field, module.get.validation($field)); 7527 module.validate.field( validationRules ); 7528 if(!settings.inline) { 7529 module.validate.form(false,true); 7530 } 7531 }, settings.delay); 7532 } 7533 } 7534 }, 7535 beforeUnload: function(event) { 7536 if (module.is.dirty() && !submitting) { 7537 var event = event || window.event; 7538 7539 // For modern browsers 7540 if (event) { 7541 event.returnValue = settings.text.leavingMessage; 7542 } 7543 7544 // For olders... 7545 return settings.text.leavingMessage; 7546 } 7547 } 7548 7549 }, 7550 7551 get: { 7552 ancillaryValue: function(rule) { 7553 if(!rule.type || (!rule.value && !module.is.bracketedRule(rule))) { 7554 return false; 7555 } 7556 return (rule.value !== undefined) 7557 ? rule.value 7558 : rule.type.match(settings.regExp.bracket)[1] + '' 7559 ; 7560 }, 7561 ruleName: function(rule) { 7562 if( module.is.bracketedRule(rule) ) { 7563 return rule.type.replace(rule.type.match(settings.regExp.bracket)[0], ''); 7564 } 7565 return rule.type; 7566 }, 7567 changeEvent: function(type, $input) { 7568 if(type == 'checkbox' || type == 'radio' || type == 'hidden' || $input.is('select')) { 7569 return 'change'; 7570 } 7571 else { 7572 return module.get.inputEvent(); 7573 } 7574 }, 7575 inputEvent: function() { 7576 return (document.createElement('input').oninput !== undefined) 7577 ? 'input' 7578 : (document.createElement('input').onpropertychange !== undefined) 7579 ? 'propertychange' 7580 : 'keyup' 7581 ; 7582 }, 7583 fieldsFromShorthand: function(fields) { 7584 var 7585 fullFields = {} 7586 ; 7587 $.each(fields, function(name, rules) { 7588 if(typeof rules == 'string') { 7589 rules = [rules]; 7590 } 7591 fullFields[name] = { 7592 rules: [] 7593 }; 7594 $.each(rules, function(index, rule) { 7595 fullFields[name].rules.push({ type: rule }); 7596 }); 7597 }); 7598 return fullFields; 7599 }, 7600 prompt: function(rule, field) { 7601 var 7602 ruleName = module.get.ruleName(rule), 7603 ancillary = module.get.ancillaryValue(rule), 7604 $field = module.get.field(field.identifier), 7605 value = $field.val(), 7606 prompt = $.isFunction(rule.prompt) 7607 ? rule.prompt(value) 7608 : rule.prompt || settings.prompt[ruleName] || settings.text.unspecifiedRule, 7609 requiresValue = (prompt.search('{value}') !== -1), 7610 requiresName = (prompt.search('{name}') !== -1), 7611 $label, 7612 name 7613 ; 7614 if(requiresValue) { 7615 prompt = prompt.replace(/\{value\}/g, $field.val()); 7616 } 7617 if(requiresName) { 7618 $label = $field.closest(selector.group).find('label').eq(0); 7619 name = ($label.length == 1) 7620 ? $label.text() 7621 : $field.prop('placeholder') || settings.text.unspecifiedField 7622 ; 7623 prompt = prompt.replace(/\{name\}/g, name); 7624 } 7625 prompt = prompt.replace(/\{identifier\}/g, field.identifier); 7626 prompt = prompt.replace(/\{ruleValue\}/g, ancillary); 7627 if(!rule.prompt) { 7628 module.verbose('Using default validation prompt for type', prompt, ruleName); 7629 } 7630 return prompt; 7631 }, 7632 settings: function() { 7633 if($.isPlainObject(parameters)) { 7634 var 7635 keys = Object.keys(parameters), 7636 isLegacySettings = (keys.length > 0) 7637 ? (parameters[keys[0]].identifier !== undefined && parameters[keys[0]].rules !== undefined) 7638 : false 7639 ; 7640 if(isLegacySettings) { 7641 // 1.x (ducktyped) 7642 settings = $.extend(true, {}, $.fn.form.settings, legacyParameters); 7643 validation = $.extend({}, $.fn.form.settings.defaults, parameters); 7644 module.error(settings.error.oldSyntax, element); 7645 module.verbose('Extending settings from legacy parameters', validation, settings); 7646 } 7647 else { 7648 // 2.x 7649 if(parameters.fields && module.is.shorthandFields(parameters.fields)) { 7650 parameters.fields = module.get.fieldsFromShorthand(parameters.fields); 7651 } 7652 settings = $.extend(true, {}, $.fn.form.settings, parameters); 7653 validation = $.extend({}, $.fn.form.settings.defaults, settings.fields); 7654 module.verbose('Extending settings', validation, settings); 7655 } 7656 } 7657 else { 7658 settings = $.fn.form.settings; 7659 validation = $.fn.form.settings.defaults; 7660 module.verbose('Using default form validation', validation, settings); 7661 } 7662 7663 // shorthand 7664 namespace = settings.namespace; 7665 metadata = settings.metadata; 7666 selector = settings.selector; 7667 className = settings.className; 7668 regExp = settings.regExp; 7669 error = settings.error; 7670 moduleNamespace = 'module-' + namespace; 7671 eventNamespace = '.' + namespace; 7672 7673 // grab instance 7674 instance = $module.data(moduleNamespace); 7675 7676 // refresh selector cache 7677 module.refresh(); 7678 }, 7679 field: function(identifier) { 7680 module.verbose('Finding field with identifier', identifier); 7681 identifier = module.escape.string(identifier); 7682 var t; 7683 if((t=$field.filter('#' + identifier)).length > 0 ) { 7684 return t; 7685 } 7686 if((t=$field.filter('[name="' + identifier +'"]')).length > 0 ) { 7687 return t; 7688 } 7689 if((t=$field.filter('[name="' + identifier +'[]"]')).length > 0 ) { 7690 return t; 7691 } 7692 if((t=$field.filter('[data-' + metadata.validate + '="'+ identifier +'"]')).length > 0 ) { 7693 return t; 7694 } 7695 return $('<input/>'); 7696 }, 7697 fields: function(fields) { 7698 var 7699 $fields = $() 7700 ; 7701 $.each(fields, function(index, name) { 7702 $fields = $fields.add( module.get.field(name) ); 7703 }); 7704 return $fields; 7705 }, 7706 validation: function($field) { 7707 var 7708 fieldValidation, 7709 identifier 7710 ; 7711 if(!validation) { 7712 return false; 7713 } 7714 $.each(validation, function(fieldName, field) { 7715 identifier = field.identifier || fieldName; 7716 $.each(module.get.field(identifier), function(index, groupField) { 7717 if(groupField == $field[0]) { 7718 field.identifier = identifier; 7719 fieldValidation = field; 7720 return false; 7721 } 7722 }); 7723 }); 7724 return fieldValidation || false; 7725 }, 7726 value: function (field) { 7727 var 7728 fields = [], 7729 results 7730 ; 7731 fields.push(field); 7732 results = module.get.values.call(element, fields); 7733 return results[field]; 7734 }, 7735 values: function (fields) { 7736 var 7737 $fields = Array.isArray(fields) 7738 ? module.get.fields(fields) 7739 : $field, 7740 values = {} 7741 ; 7742 $fields.each(function(index, field) { 7743 var 7744 $field = $(field), 7745 $calendar = $field.closest(selector.uiCalendar), 7746 name = $field.prop('name'), 7747 value = $field.val(), 7748 isCheckbox = $field.is(selector.checkbox), 7749 isRadio = $field.is(selector.radio), 7750 isMultiple = (name.indexOf('[]') !== -1), 7751 isCalendar = ($calendar.length > 0 && module.can.useElement('calendar')), 7752 isChecked = (isCheckbox) 7753 ? $field.is(':checked') 7754 : false 7755 ; 7756 if(name) { 7757 if(isMultiple) { 7758 name = name.replace('[]', ''); 7759 if(!values[name]) { 7760 values[name] = []; 7761 } 7762 if(isCheckbox) { 7763 if(isChecked) { 7764 values[name].push(value || true); 7765 } 7766 else { 7767 values[name].push(false); 7768 } 7769 } 7770 else { 7771 values[name].push(value); 7772 } 7773 } 7774 else { 7775 if(isRadio) { 7776 if(values[name] === undefined || values[name] === false) { 7777 values[name] = (isChecked) 7778 ? value || true 7779 : false 7780 ; 7781 } 7782 } 7783 else if(isCheckbox) { 7784 if(isChecked) { 7785 values[name] = value || true; 7786 } 7787 else { 7788 values[name] = false; 7789 } 7790 } 7791 else if(isCalendar) { 7792 var date = $calendar.calendar('get date'); 7793 7794 if (date !== null) { 7795 if (settings.dateHandling == 'date') { 7796 values[name] = date; 7797 } else if(settings.dateHandling == 'input') { 7798 values[name] = $calendar.calendar('get input date') 7799 } else if (settings.dateHandling == 'formatter') { 7800 var type = $calendar.calendar('setting', 'type'); 7801 7802 switch(type) { 7803 case 'date': 7804 values[name] = settings.formatter.date(date); 7805 break; 7806 7807 case 'datetime': 7808 values[name] = settings.formatter.datetime(date); 7809 break; 7810 7811 case 'time': 7812 values[name] = settings.formatter.time(date); 7813 break; 7814 7815 case 'month': 7816 values[name] = settings.formatter.month(date); 7817 break; 7818 7819 case 'year': 7820 values[name] = settings.formatter.year(date); 7821 break; 7822 7823 default: 7824 module.debug('Wrong calendar mode', $calendar, type); 7825 values[name] = ''; 7826 } 7827 } 7828 } else { 7829 values[name] = ''; 7830 } 7831 } else { 7832 values[name] = value; 7833 } 7834 } 7835 } 7836 }); 7837 return values; 7838 }, 7839 dirtyFields: function() { 7840 return $field.filter(function(index, e) { 7841 return $(e).data(metadata.isDirty); 7842 }); 7843 } 7844 }, 7845 7846 has: { 7847 7848 field: function(identifier) { 7849 module.verbose('Checking for existence of a field with identifier', identifier); 7850 identifier = module.escape.string(identifier); 7851 if(typeof identifier !== 'string') { 7852 module.error(error.identifier, identifier); 7853 } 7854 if($field.filter('#' + identifier).length > 0 ) { 7855 return true; 7856 } 7857 else if( $field.filter('[name="' + identifier +'"]').length > 0 ) { 7858 return true; 7859 } 7860 else if( $field.filter('[data-' + metadata.validate + '="'+ identifier +'"]').length > 0 ) { 7861 return true; 7862 } 7863 return false; 7864 } 7865 7866 }, 7867 7868 can: { 7869 useElement: function(element){ 7870 if ($.fn[element] !== undefined) { 7871 return true; 7872 } 7873 module.error(error.noElement.replace('{element}',element)); 7874 return false; 7875 } 7876 }, 7877 7878 escape: { 7879 string: function(text) { 7880 text = String(text); 7881 return text.replace(regExp.escape, '\\$&'); 7882 } 7883 }, 7884 7885 add: { 7886 // alias 7887 rule: function(name, rules) { 7888 module.add.field(name, rules); 7889 }, 7890 field: function(name, rules) { 7891 // Validation should have at least a standard format 7892 if(validation[name] === undefined || validation[name].rules === undefined) { 7893 validation[name] = { 7894 rules: [] 7895 }; 7896 } 7897 var 7898 newValidation = { 7899 rules: [] 7900 } 7901 ; 7902 if(module.is.shorthandRules(rules)) { 7903 rules = Array.isArray(rules) 7904 ? rules 7905 : [rules] 7906 ; 7907 $.each(rules, function(_index, rule) { 7908 newValidation.rules.push({ type: rule }); 7909 }); 7910 } 7911 else { 7912 newValidation.rules = rules.rules; 7913 } 7914 // For each new rule, check if there's not already one with the same type 7915 $.each(newValidation.rules, function (_index, rule) { 7916 if ($.grep(validation[name].rules, function(item){ return item.type == rule.type; }).length == 0) { 7917 validation[name].rules.push(rule); 7918 } 7919 }); 7920 module.debug('Adding rules', newValidation.rules, validation); 7921 }, 7922 fields: function(fields) { 7923 var 7924 newValidation 7925 ; 7926 if(fields && module.is.shorthandFields(fields)) { 7927 newValidation = module.get.fieldsFromShorthand(fields); 7928 } 7929 else { 7930 newValidation = fields; 7931 } 7932 validation = $.extend({}, validation, newValidation); 7933 }, 7934 prompt: function(identifier, errors, internal) { 7935 var 7936 $field = module.get.field(identifier), 7937 $fieldGroup = $field.closest($group), 7938 $prompt = $fieldGroup.children(selector.prompt), 7939 promptExists = ($prompt.length !== 0) 7940 ; 7941 errors = (typeof errors == 'string') 7942 ? [errors] 7943 : errors 7944 ; 7945 module.verbose('Adding field error state', identifier); 7946 if(!internal) { 7947 $fieldGroup 7948 .addClass(className.error) 7949 ; 7950 } 7951 if(settings.inline) { 7952 if(!promptExists) { 7953 $prompt = settings.templates.prompt(errors, className.label); 7954 $prompt 7955 .appendTo($fieldGroup) 7956 ; 7957 } 7958 $prompt 7959 .html(errors[0]) 7960 ; 7961 if(!promptExists) { 7962 if(settings.transition && module.can.useElement('transition') && $module.transition('is supported')) { 7963 module.verbose('Displaying error with css transition', settings.transition); 7964 $prompt.transition(settings.transition + ' in', settings.duration); 7965 } 7966 else { 7967 module.verbose('Displaying error with fallback javascript animation'); 7968 $prompt 7969 .fadeIn(settings.duration) 7970 ; 7971 } 7972 } 7973 else { 7974 module.verbose('Inline errors are disabled, no inline error added', identifier); 7975 } 7976 } 7977 }, 7978 errors: function(errors) { 7979 module.debug('Adding form error messages', errors); 7980 module.set.error(); 7981 $message 7982 .html( settings.templates.error(errors) ) 7983 ; 7984 } 7985 }, 7986 7987 remove: { 7988 errors: function() { 7989 module.debug('Removing form error messages'); 7990 $message.empty(); 7991 }, 7992 states: function() { 7993 $module.removeClass(className.error).removeClass(className.success); 7994 if(!settings.inline) { 7995 module.remove.errors(); 7996 } 7997 module.determine.isDirty(); 7998 }, 7999 rule: function(field, rule) { 8000 var 8001 rules = Array.isArray(rule) 8002 ? rule 8003 : [rule] 8004 ; 8005 if(validation[field] === undefined || !Array.isArray(validation[field].rules)) { 8006 return; 8007 } 8008 if(rule === undefined) { 8009 module.debug('Removed all rules'); 8010 validation[field].rules = []; 8011 return; 8012 } 8013 $.each(validation[field].rules, function(index, rule) { 8014 if(rule && rules.indexOf(rule.type) !== -1) { 8015 module.debug('Removed rule', rule.type); 8016 validation[field].rules.splice(index, 1); 8017 } 8018 }); 8019 }, 8020 field: function(field) { 8021 var 8022 fields = Array.isArray(field) 8023 ? field 8024 : [field] 8025 ; 8026 $.each(fields, function(index, field) { 8027 module.remove.rule(field); 8028 }); 8029 }, 8030 // alias 8031 rules: function(field, rules) { 8032 if(Array.isArray(field)) { 8033 $.each(field, function(index, field) { 8034 module.remove.rule(field, rules); 8035 }); 8036 } 8037 else { 8038 module.remove.rule(field, rules); 8039 } 8040 }, 8041 fields: function(fields) { 8042 module.remove.field(fields); 8043 }, 8044 prompt: function(identifier) { 8045 var 8046 $field = module.get.field(identifier), 8047 $fieldGroup = $field.closest($group), 8048 $prompt = $fieldGroup.children(selector.prompt) 8049 ; 8050 $fieldGroup 8051 .removeClass(className.error) 8052 ; 8053 if(settings.inline && $prompt.is(':visible')) { 8054 module.verbose('Removing prompt for field', identifier); 8055 if(settings.transition && module.can.useElement('transition') && $module.transition('is supported')) { 8056 $prompt.transition(settings.transition + ' out', settings.duration, function() { 8057 $prompt.remove(); 8058 }); 8059 } 8060 else { 8061 $prompt 8062 .fadeOut(settings.duration, function(){ 8063 $prompt.remove(); 8064 }) 8065 ; 8066 } 8067 } 8068 } 8069 }, 8070 8071 set: { 8072 success: function() { 8073 $module 8074 .removeClass(className.error) 8075 .addClass(className.success) 8076 ; 8077 }, 8078 defaults: function () { 8079 $field.each(function (index, el) { 8080 var 8081 $el = $(el), 8082 $parent = $el.parent(), 8083 isCheckbox = ($el.filter(selector.checkbox).length > 0), 8084 isDropdown = $parent.is(selector.uiDropdown) && module.can.useElement('dropdown'), 8085 $calendar = $el.closest(selector.uiCalendar), 8086 isCalendar = ($calendar.length > 0 && module.can.useElement('calendar')), 8087 value = (isCheckbox) 8088 ? $el.is(':checked') 8089 : $el.val() 8090 ; 8091 if (isDropdown) { 8092 $parent.dropdown('save defaults'); 8093 } 8094 else if (isCalendar) { 8095 $calendar.calendar('refresh'); 8096 } 8097 $el.data(metadata.defaultValue, value); 8098 $el.data(metadata.isDirty, false); 8099 }); 8100 }, 8101 error: function() { 8102 $module 8103 .removeClass(className.success) 8104 .addClass(className.error) 8105 ; 8106 }, 8107 value: function (field, value) { 8108 var 8109 fields = {} 8110 ; 8111 fields[field] = value; 8112 return module.set.values.call(element, fields); 8113 }, 8114 values: function (fields) { 8115 if($.isEmptyObject(fields)) { 8116 return; 8117 } 8118 $.each(fields, function(key, value) { 8119 var 8120 $field = module.get.field(key), 8121 $element = $field.parent(), 8122 $calendar = $field.closest(selector.uiCalendar), 8123 isMultiple = Array.isArray(value), 8124 isCheckbox = $element.is(selector.uiCheckbox) && module.can.useElement('checkbox'), 8125 isDropdown = $element.is(selector.uiDropdown) && module.can.useElement('dropdown'), 8126 isRadio = ($field.is(selector.radio) && isCheckbox), 8127 isCalendar = ($calendar.length > 0 && module.can.useElement('calendar')), 8128 fieldExists = ($field.length > 0), 8129 $multipleField 8130 ; 8131 if(fieldExists) { 8132 if(isMultiple && isCheckbox) { 8133 module.verbose('Selecting multiple', value, $field); 8134 $element.checkbox('uncheck'); 8135 $.each(value, function(index, value) { 8136 $multipleField = $field.filter('[value="' + value + '"]'); 8137 $element = $multipleField.parent(); 8138 if($multipleField.length > 0) { 8139 $element.checkbox('check'); 8140 } 8141 }); 8142 } 8143 else if(isRadio) { 8144 module.verbose('Selecting radio value', value, $field); 8145 $field.filter('[value="' + value + '"]') 8146 .parent(selector.uiCheckbox) 8147 .checkbox('check') 8148 ; 8149 } 8150 else if(isCheckbox) { 8151 module.verbose('Setting checkbox value', value, $element); 8152 if(value === true || value === 1) { 8153 $element.checkbox('check'); 8154 } 8155 else { 8156 $element.checkbox('uncheck'); 8157 } 8158 } 8159 else if(isDropdown) { 8160 module.verbose('Setting dropdown value', value, $element); 8161 $element.dropdown('set selected', value); 8162 } 8163 else if (isCalendar) { 8164 $calendar.calendar('set date',value); 8165 } 8166 else { 8167 module.verbose('Setting field value', value, $field); 8168 $field.val(value); 8169 } 8170 } 8171 }); 8172 }, 8173 dirty: function() { 8174 module.verbose('Setting state dirty'); 8175 dirty = true; 8176 history[0] = history[1]; 8177 history[1] = 'dirty'; 8178 8179 if (module.is.justClean()) { 8180 $module.trigger('dirty'); 8181 } 8182 }, 8183 clean: function() { 8184 module.verbose('Setting state clean'); 8185 dirty = false; 8186 history[0] = history[1]; 8187 history[1] = 'clean'; 8188 8189 if (module.is.justDirty()) { 8190 $module.trigger('clean'); 8191 } 8192 }, 8193 asClean: function() { 8194 module.set.defaults(); 8195 module.set.clean(); 8196 }, 8197 asDirty: function() { 8198 module.set.defaults(); 8199 module.set.dirty(); 8200 }, 8201 autoCheck: function() { 8202 module.debug('Enabling auto check on required fields'); 8203 $field.each(function (_index, el) { 8204 var 8205 $el = $(el), 8206 $elGroup = $(el).closest($group), 8207 isCheckbox = ($el.filter(selector.checkbox).length > 0), 8208 isRequired = $el.prop('required') || $elGroup.hasClass(className.required) || $elGroup.parent().hasClass(className.required), 8209 isDisabled = $el.is(':disabled') || $elGroup.hasClass(className.disabled) || $elGroup.parent().hasClass(className.disabled), 8210 validation = module.get.validation($el), 8211 hasEmptyRule = validation 8212 ? $.grep(validation.rules, function(rule) { return rule.type == "empty" }) !== 0 8213 : false, 8214 identifier = validation.identifier || $el.attr('id') || $el.attr('name') || $el.data(metadata.validate) 8215 ; 8216 if (isRequired && !isDisabled && !hasEmptyRule && identifier !== undefined) { 8217 if (isCheckbox) { 8218 module.verbose("Adding 'checked' rule on field", identifier); 8219 module.add.rule(identifier, "checked"); 8220 } else { 8221 module.verbose("Adding 'empty' rule on field", identifier); 8222 module.add.rule(identifier, "empty"); 8223 } 8224 } 8225 }); 8226 } 8227 }, 8228 8229 validate: { 8230 8231 form: function(event, ignoreCallbacks) { 8232 var values = module.get.values(); 8233 8234 // input keydown event will fire submit repeatedly by browser default 8235 if(keyHeldDown) { 8236 return false; 8237 } 8238 8239 // reset errors 8240 formErrors = []; 8241 if( module.determine.isValid() ) { 8242 module.debug('Form has no validation errors, submitting'); 8243 module.set.success(); 8244 if(!settings.inline) { 8245 module.remove.errors(); 8246 } 8247 if(ignoreCallbacks !== true) { 8248 return settings.onSuccess.call(element, event, values); 8249 } 8250 } 8251 else { 8252 module.debug('Form has errors'); 8253 submitting = false; 8254 module.set.error(); 8255 if(!settings.inline) { 8256 module.add.errors(formErrors); 8257 } 8258 // prevent ajax submit 8259 if(event && $module.data('moduleApi') !== undefined) { 8260 event.stopImmediatePropagation(); 8261 } 8262 if(ignoreCallbacks !== true) { 8263 return settings.onFailure.call(element, formErrors, values); 8264 } 8265 } 8266 }, 8267 8268 // takes a validation object and returns whether field passes validation 8269 field: function(field, fieldName, showErrors) { 8270 showErrors = (showErrors !== undefined) 8271 ? showErrors 8272 : true 8273 ; 8274 if(typeof field == 'string') { 8275 module.verbose('Validating field', field); 8276 fieldName = field; 8277 field = validation[field]; 8278 } 8279 var 8280 identifier = field.identifier || fieldName, 8281 $field = module.get.field(identifier), 8282 $dependsField = (field.depends) 8283 ? module.get.field(field.depends) 8284 : false, 8285 fieldValid = true, 8286 fieldErrors = [] 8287 ; 8288 if(!field.identifier) { 8289 module.debug('Using field name as identifier', identifier); 8290 field.identifier = identifier; 8291 } 8292 var isDisabled = !$field.filter(':not(:disabled)').length; 8293 if(isDisabled) { 8294 module.debug('Field is disabled. Skipping', identifier); 8295 } 8296 else if(field.optional && module.is.blank($field)){ 8297 module.debug('Field is optional and blank. Skipping', identifier); 8298 } 8299 else if(field.depends && module.is.empty($dependsField)) { 8300 module.debug('Field depends on another value that is not present or empty. Skipping', $dependsField); 8301 } 8302 else if(field.rules !== undefined) { 8303 if(showErrors) { 8304 $field.closest($group).removeClass(className.error); 8305 } 8306 $.each(field.rules, function(index, rule) { 8307 if( module.has.field(identifier)) { 8308 var invalidFields = module.validate.rule(field, rule,true) || []; 8309 if (invalidFields.length>0){ 8310 module.debug('Field is invalid', identifier, rule.type); 8311 fieldErrors.push(module.get.prompt(rule, field)); 8312 fieldValid = false; 8313 if(showErrors){ 8314 $(invalidFields).closest($group).addClass(className.error); 8315 } 8316 } 8317 } 8318 }); 8319 } 8320 if(fieldValid) { 8321 if(showErrors) { 8322 module.remove.prompt(identifier, fieldErrors); 8323 settings.onValid.call($field); 8324 } 8325 } 8326 else { 8327 if(showErrors) { 8328 formErrors = formErrors.concat(fieldErrors); 8329 module.add.prompt(identifier, fieldErrors, true); 8330 settings.onInvalid.call($field, fieldErrors); 8331 } 8332 return false; 8333 } 8334 return true; 8335 }, 8336 8337 // takes validation rule and returns whether field passes rule 8338 rule: function(field, rule, internal) { 8339 var 8340 $field = module.get.field(field.identifier), 8341 ancillary = module.get.ancillaryValue(rule), 8342 ruleName = module.get.ruleName(rule), 8343 ruleFunction = settings.rules[ruleName], 8344 invalidFields = [], 8345 isCheckbox = $field.is(selector.checkbox), 8346 isValid = function(field){ 8347 var value = (isCheckbox ? $(field).filter(':checked').val() : $(field).val()); 8348 // cast to string avoiding encoding special values 8349 value = (value === undefined || value === '' || value === null) 8350 ? '' 8351 : (settings.shouldTrim) ? String(value + '').trim() : String(value + '') 8352 ; 8353 return ruleFunction.call(field, value, ancillary, $module); 8354 } 8355 ; 8356 if( !$.isFunction(ruleFunction) ) { 8357 module.error(error.noRule, ruleName); 8358 return; 8359 } 8360 if(isCheckbox) { 8361 if (!isValid($field)) { 8362 invalidFields = $field; 8363 } 8364 } else { 8365 $.each($field, function (index, field) { 8366 if (!isValid(field)) { 8367 invalidFields.push(field); 8368 } 8369 }); 8370 } 8371 return internal ? invalidFields : !(invalidFields.length>0); 8372 } 8373 }, 8374 8375 setting: function(name, value) { 8376 if( $.isPlainObject(name) ) { 8377 $.extend(true, settings, name); 8378 } 8379 else if(value !== undefined) { 8380 settings[name] = value; 8381 } 8382 else { 8383 return settings[name]; 8384 } 8385 }, 8386 internal: function(name, value) { 8387 if( $.isPlainObject(name) ) { 8388 $.extend(true, module, name); 8389 } 8390 else if(value !== undefined) { 8391 module[name] = value; 8392 } 8393 else { 8394 return module[name]; 8395 } 8396 }, 8397 debug: function() { 8398 if(!settings.silent && settings.debug) { 8399 if(settings.performance) { 8400 module.performance.log(arguments); 8401 } 8402 else { 8403 module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); 8404 module.debug.apply(console, arguments); 8405 } 8406 } 8407 }, 8408 verbose: function() { 8409 if(!settings.silent && settings.verbose && settings.debug) { 8410 if(settings.performance) { 8411 module.performance.log(arguments); 8412 } 8413 else { 8414 module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); 8415 module.verbose.apply(console, arguments); 8416 } 8417 } 8418 }, 8419 error: function() { 8420 if(!settings.silent) { 8421 module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); 8422 module.error.apply(console, arguments); 8423 } 8424 }, 8425 performance: { 8426 log: function(message) { 8427 var 8428 currentTime, 8429 executionTime, 8430 previousTime 8431 ; 8432 if(settings.performance) { 8433 currentTime = new Date().getTime(); 8434 previousTime = time || currentTime; 8435 executionTime = currentTime - previousTime; 8436 time = currentTime; 8437 performance.push({ 8438 'Name' : message[0], 8439 'Arguments' : [].slice.call(message, 1) || '', 8440 'Element' : element, 8441 'Execution Time' : executionTime 8442 }); 8443 } 8444 clearTimeout(module.performance.timer); 8445 module.performance.timer = setTimeout(module.performance.display, 500); 8446 }, 8447 display: function() { 8448 var 8449 title = settings.name + ':', 8450 totalTime = 0 8451 ; 8452 time = false; 8453 clearTimeout(module.performance.timer); 8454 $.each(performance, function(index, data) { 8455 totalTime += data['Execution Time']; 8456 }); 8457 title += ' ' + totalTime + 'ms'; 8458 if(moduleSelector) { 8459 title += ' \'' + moduleSelector + '\''; 8460 } 8461 if($allModules.length > 1) { 8462 title += ' ' + '(' + $allModules.length + ')'; 8463 } 8464 if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { 8465 console.groupCollapsed(title); 8466 if(console.table) { 8467 console.table(performance); 8468 } 8469 else { 8470 $.each(performance, function(index, data) { 8471 console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); 8472 }); 8473 } 8474 console.groupEnd(); 8475 } 8476 performance = []; 8477 } 8478 }, 8479 invoke: function(query, passedArguments, context) { 8480 var 8481 object = instance, 8482 maxDepth, 8483 found, 8484 response 8485 ; 8486 passedArguments = passedArguments || queryArguments; 8487 context = element || context; 8488 if(typeof query == 'string' && object !== undefined) { 8489 query = query.split(/[\. ]/); 8490 maxDepth = query.length - 1; 8491 $.each(query, function(depth, value) { 8492 var camelCaseValue = (depth != maxDepth) 8493 ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) 8494 : query 8495 ; 8496 if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { 8497 object = object[camelCaseValue]; 8498 } 8499 else if( object[camelCaseValue] !== undefined ) { 8500 found = object[camelCaseValue]; 8501 return false; 8502 } 8503 else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { 8504 object = object[value]; 8505 } 8506 else if( object[value] !== undefined ) { 8507 found = object[value]; 8508 return false; 8509 } 8510 else { 8511 return false; 8512 } 8513 }); 8514 } 8515 if( $.isFunction( found ) ) { 8516 response = found.apply(context, passedArguments); 8517 } 8518 else if(found !== undefined) { 8519 response = found; 8520 } 8521 if(Array.isArray(returnedValue)) { 8522 returnedValue.push(response); 8523 } 8524 else if(returnedValue !== undefined) { 8525 returnedValue = [returnedValue, response]; 8526 } 8527 else if(response !== undefined) { 8528 returnedValue = response; 8529 } 8530 return found; 8531 } 8532 }; 8533 module.initialize(); 8534 }) 8535 ; 8536 8537 return (returnedValue !== undefined) 8538 ? returnedValue 8539 : this 8540 ; 8541 }; 8542 8543 $.fn.form.settings = { 8544 8545 name : 'Form', 8546 namespace : 'form', 8547 8548 debug : false, 8549 verbose : false, 8550 performance : true, 8551 8552 fields : false, 8553 8554 keyboardShortcuts : true, 8555 on : 'submit', 8556 inline : false, 8557 8558 delay : 200, 8559 revalidate : true, 8560 shouldTrim : true, 8561 8562 transition : 'scale', 8563 duration : 200, 8564 8565 autoCheckRequired : false, 8566 preventLeaving : false, 8567 dateHandling : 'date', // 'date', 'input', 'formatter' 8568 8569 onValid : function() {}, 8570 onInvalid : function() {}, 8571 onSuccess : function() { return true; }, 8572 onFailure : function() { return false; }, 8573 onDirty : function() {}, 8574 onClean : function() {}, 8575 8576 metadata : { 8577 defaultValue : 'default', 8578 validate : 'validate', 8579 isDirty : 'isDirty' 8580 }, 8581 8582 regExp: { 8583 htmlID : /^[a-zA-Z][\w:.-]*$/g, 8584 bracket : /\[(.*)\]/i, 8585 decimal : /^\d+\.?\d*$/, 8586 email : /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i, 8587 escape : /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|:,=@]/g, 8588 flags : /^\/(.*)\/(.*)?/, 8589 integer : /^\-?\d+$/, 8590 number : /^\-?\d*(\.\d+)?$/, 8591 url : /(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/i 8592 }, 8593 8594 text: { 8595 unspecifiedRule : 'Please enter a valid value', 8596 unspecifiedField : 'This field', 8597 leavingMessage : 'There are unsaved changes on this page which will be discarded if you continue.' 8598 }, 8599 8600 prompt: { 8601 empty : '{name} must have a value', 8602 checked : '{name} must be checked', 8603 email : '{name} must be a valid e-mail', 8604 url : '{name} must be a valid url', 8605 regExp : '{name} is not formatted correctly', 8606 integer : '{name} must be an integer', 8607 decimal : '{name} must be a decimal number', 8608 number : '{name} must be set to a number', 8609 is : '{name} must be "{ruleValue}"', 8610 isExactly : '{name} must be exactly "{ruleValue}"', 8611 not : '{name} cannot be set to "{ruleValue}"', 8612 notExactly : '{name} cannot be set to exactly "{ruleValue}"', 8613 contain : '{name} must contain "{ruleValue}"', 8614 containExactly : '{name} must contain exactly "{ruleValue}"', 8615 doesntContain : '{name} cannot contain "{ruleValue}"', 8616 doesntContainExactly : '{name} cannot contain exactly "{ruleValue}"', 8617 minLength : '{name} must be at least {ruleValue} characters', 8618 length : '{name} must be at least {ruleValue} characters', 8619 exactLength : '{name} must be exactly {ruleValue} characters', 8620 maxLength : '{name} cannot be longer than {ruleValue} characters', 8621 match : '{name} must match {ruleValue} field', 8622 different : '{name} must have a different value than {ruleValue} field', 8623 creditCard : '{name} must be a valid credit card number', 8624 minCount : '{name} must have at least {ruleValue} choices', 8625 exactCount : '{name} must have exactly {ruleValue} choices', 8626 maxCount : '{name} must have {ruleValue} or less choices' 8627 }, 8628 8629 selector : { 8630 checkbox : 'input[type="checkbox"], input[type="radio"]', 8631 clear : '.clear', 8632 field : 'input:not(.search), textarea, select', 8633 group : '.field', 8634 input : 'input', 8635 message : '.error.message', 8636 prompt : '.prompt.label', 8637 radio : 'input[type="radio"]', 8638 reset : '.reset:not([type="reset"])', 8639 submit : '.submit:not([type="submit"])', 8640 uiCheckbox : '.ui.checkbox', 8641 uiDropdown : '.ui.dropdown', 8642 uiCalendar : '.ui.calendar' 8643 }, 8644 8645 className : { 8646 error : 'error', 8647 label : 'ui basic red pointing prompt label', 8648 pressed : 'down', 8649 success : 'success', 8650 required : 'required', 8651 disabled : 'disabled' 8652 }, 8653 8654 error: { 8655 identifier : 'You must specify a string identifier for each field', 8656 method : 'The method you called is not defined.', 8657 noRule : 'There is no rule matching the one you specified', 8658 oldSyntax : 'Starting in 2.0 forms now only take a single settings object. Validation settings converted to new syntax automatically.', 8659 noElement : 'This module requires ui {element}' 8660 }, 8661 8662 templates: { 8663 8664 // template that produces error message 8665 error: function(errors) { 8666 var 8667 html = '<ul class="list">' 8668 ; 8669 $.each(errors, function(index, value) { 8670 html += '<li>' + value + '</li>'; 8671 }); 8672 html += '</ul>'; 8673 return $(html); 8674 }, 8675 8676 // template that produces label 8677 prompt: function(errors, labelClasses) { 8678 return $('<div/>') 8679 .addClass(labelClasses) 8680 .html(errors[0]) 8681 ; 8682 } 8683 }, 8684 8685 formatter: { 8686 date: function(date) { 8687 return Intl.DateTimeFormat('en-GB').format(date); 8688 }, 8689 datetime: function(date) { 8690 return Intl.DateTimeFormat('en-GB', { 8691 year: "numeric", 8692 month: "2-digit", 8693 day: "2-digit", 8694 hour: '2-digit', 8695 minute: '2-digit', 8696 second: '2-digit' 8697 }).format(date); 8698 }, 8699 time: function(date) { 8700 return Intl.DateTimeFormat('en-GB', { 8701 hour: '2-digit', 8702 minute: '2-digit', 8703 second: '2-digit' 8704 }).format(date); 8705 }, 8706 month: function(date) { 8707 return Intl.DateTimeFormat('en-GB', { 8708 month: '2-digit', 8709 year: 'numeric' 8710 }).format(date); 8711 }, 8712 year: function(date) { 8713 return Intl.DateTimeFormat('en-GB', { 8714 year: 'numeric' 8715 }).format(date); 8716 } 8717 }, 8718 8719 rules: { 8720 8721 // is not empty or blank string 8722 empty: function(value) { 8723 return !(value === undefined || '' === value || Array.isArray(value) && value.length === 0); 8724 }, 8725 8726 // checkbox checked 8727 checked: function() { 8728 return ($(this).filter(':checked').length > 0); 8729 }, 8730 8731 // is most likely an email 8732 email: function(value){ 8733 return $.fn.form.settings.regExp.email.test(value); 8734 }, 8735 8736 // value is most likely url 8737 url: function(value) { 8738 return $.fn.form.settings.regExp.url.test(value); 8739 }, 8740 8741 // matches specified regExp 8742 regExp: function(value, regExp) { 8743 if(regExp instanceof RegExp) { 8744 return value.match(regExp); 8745 } 8746 var 8747 regExpParts = regExp.match($.fn.form.settings.regExp.flags), 8748 flags 8749 ; 8750 // regular expression specified as /baz/gi (flags) 8751 if(regExpParts) { 8752 regExp = (regExpParts.length >= 2) 8753 ? regExpParts[1] 8754 : regExp 8755 ; 8756 flags = (regExpParts.length >= 3) 8757 ? regExpParts[2] 8758 : '' 8759 ; 8760 } 8761 return value.match( new RegExp(regExp, flags) ); 8762 }, 8763 8764 // is valid integer or matches range 8765 integer: function(value, range) { 8766 var 8767 intRegExp = $.fn.form.settings.regExp.integer, 8768 min, 8769 max, 8770 parts 8771 ; 8772 if( !range || ['', '..'].indexOf(range) !== -1) { 8773 // do nothing 8774 } 8775 else if(range.indexOf('..') == -1) { 8776 if(intRegExp.test(range)) { 8777 min = max = range - 0; 8778 } 8779 } 8780 else { 8781 parts = range.split('..', 2); 8782 if(intRegExp.test(parts[0])) { 8783 min = parts[0] - 0; 8784 } 8785 if(intRegExp.test(parts[1])) { 8786 max = parts[1] - 0; 8787 } 8788 } 8789 return ( 8790 intRegExp.test(value) && 8791 (min === undefined || value >= min) && 8792 (max === undefined || value <= max) 8793 ); 8794 }, 8795 8796 // is valid number (with decimal) 8797 decimal: function(value) { 8798 return $.fn.form.settings.regExp.decimal.test(value); 8799 }, 8800 8801 // is valid number 8802 number: function(value) { 8803 return $.fn.form.settings.regExp.number.test(value); 8804 }, 8805 8806 // is value (case insensitive) 8807 is: function(value, text) { 8808 text = (typeof text == 'string') 8809 ? text.toLowerCase() 8810 : text 8811 ; 8812 value = (typeof value == 'string') 8813 ? value.toLowerCase() 8814 : value 8815 ; 8816 return (value == text); 8817 }, 8818 8819 // is value 8820 isExactly: function(value, text) { 8821 return (value == text); 8822 }, 8823 8824 // value is not another value (case insensitive) 8825 not: function(value, notValue) { 8826 value = (typeof value == 'string') 8827 ? value.toLowerCase() 8828 : value 8829 ; 8830 notValue = (typeof notValue == 'string') 8831 ? notValue.toLowerCase() 8832 : notValue 8833 ; 8834 return (value != notValue); 8835 }, 8836 8837 // value is not another value (case sensitive) 8838 notExactly: function(value, notValue) { 8839 return (value != notValue); 8840 }, 8841 8842 // value contains text (insensitive) 8843 contains: function(value, text) { 8844 // escape regex characters 8845 text = text.replace($.fn.form.settings.regExp.escape, "\\$&"); 8846 return (value.search( new RegExp(text, 'i') ) !== -1); 8847 }, 8848 8849 // value contains text (case sensitive) 8850 containsExactly: function(value, text) { 8851 // escape regex characters 8852 text = text.replace($.fn.form.settings.regExp.escape, "\\$&"); 8853 return (value.search( new RegExp(text) ) !== -1); 8854 }, 8855 8856 // value contains text (insensitive) 8857 doesntContain: function(value, text) { 8858 // escape regex characters 8859 text = text.replace($.fn.form.settings.regExp.escape, "\\$&"); 8860 return (value.search( new RegExp(text, 'i') ) === -1); 8861 }, 8862 8863 // value contains text (case sensitive) 8864 doesntContainExactly: function(value, text) { 8865 // escape regex characters 8866 text = text.replace($.fn.form.settings.regExp.escape, "\\$&"); 8867 return (value.search( new RegExp(text) ) === -1); 8868 }, 8869 8870 // is at least string length 8871 minLength: function(value, requiredLength) { 8872 return (value !== undefined) 8873 ? (value.length >= requiredLength) 8874 : false 8875 ; 8876 }, 8877 8878 // see rls notes for 2.0.6 (this is a duplicate of minLength) 8879 length: function(value, requiredLength) { 8880 return (value !== undefined) 8881 ? (value.length >= requiredLength) 8882 : false 8883 ; 8884 }, 8885 8886 // is exactly length 8887 exactLength: function(value, requiredLength) { 8888 return (value !== undefined) 8889 ? (value.length == requiredLength) 8890 : false 8891 ; 8892 }, 8893 8894 // is less than length 8895 maxLength: function(value, maxLength) { 8896 return (value !== undefined) 8897 ? (value.length <= maxLength) 8898 : false 8899 ; 8900 }, 8901 8902 // matches another field 8903 match: function(value, identifier, $module) { 8904 var 8905 matchingValue, 8906 matchingElement 8907 ; 8908 if((matchingElement = $module.find('[data-validate="'+ identifier +'"]')).length > 0 ) { 8909 matchingValue = matchingElement.val(); 8910 } 8911 else if((matchingElement = $module.find('#' + identifier)).length > 0) { 8912 matchingValue = matchingElement.val(); 8913 } 8914 else if((matchingElement = $module.find('[name="' + identifier +'"]')).length > 0) { 8915 matchingValue = matchingElement.val(); 8916 } 8917 else if((matchingElement = $module.find('[name="' + identifier +'[]"]')).length > 0 ) { 8918 matchingValue = matchingElement; 8919 } 8920 return (matchingValue !== undefined) 8921 ? ( value.toString() == matchingValue.toString() ) 8922 : false 8923 ; 8924 }, 8925 8926 // different than another field 8927 different: function(value, identifier, $module) { 8928 // use either id or name of field 8929 var 8930 matchingValue, 8931 matchingElement 8932 ; 8933 if((matchingElement = $module.find('[data-validate="'+ identifier +'"]')).length > 0 ) { 8934 matchingValue = matchingElement.val(); 8935 } 8936 else if((matchingElement = $module.find('#' + identifier)).length > 0) { 8937 matchingValue = matchingElement.val(); 8938 } 8939 else if((matchingElement = $module.find('[name="' + identifier +'"]')).length > 0) { 8940 matchingValue = matchingElement.val(); 8941 } 8942 else if((matchingElement = $module.find('[name="' + identifier +'[]"]')).length > 0 ) { 8943 matchingValue = matchingElement; 8944 } 8945 return (matchingValue !== undefined) 8946 ? ( value.toString() !== matchingValue.toString() ) 8947 : false 8948 ; 8949 }, 8950 8951 creditCard: function(cardNumber, cardTypes) { 8952 var 8953 cards = { 8954 visa: { 8955 pattern : /^4/, 8956 length : [16] 8957 }, 8958 amex: { 8959 pattern : /^3[47]/, 8960 length : [15] 8961 }, 8962 mastercard: { 8963 pattern : /^5[1-5]/, 8964 length : [16] 8965 }, 8966 discover: { 8967 pattern : /^(6011|622(12[6-9]|1[3-9][0-9]|[2-8][0-9]{2}|9[0-1][0-9]|92[0-5]|64[4-9])|65)/, 8968 length : [16] 8969 }, 8970 unionPay: { 8971 pattern : /^(62|88)/, 8972 length : [16, 17, 18, 19] 8973 }, 8974 jcb: { 8975 pattern : /^35(2[89]|[3-8][0-9])/, 8976 length : [16] 8977 }, 8978 maestro: { 8979 pattern : /^(5018|5020|5038|6304|6759|676[1-3])/, 8980 length : [12, 13, 14, 15, 16, 17, 18, 19] 8981 }, 8982 dinersClub: { 8983 pattern : /^(30[0-5]|^36)/, 8984 length : [14] 8985 }, 8986 laser: { 8987 pattern : /^(6304|670[69]|6771)/, 8988 length : [16, 17, 18, 19] 8989 }, 8990 visaElectron: { 8991 pattern : /^(4026|417500|4508|4844|491(3|7))/, 8992 length : [16] 8993 } 8994 }, 8995 valid = {}, 8996 validCard = false, 8997 requiredTypes = (typeof cardTypes == 'string') 8998 ? cardTypes.split(',') 8999 : false, 9000 unionPay, 9001 validation 9002 ; 9003 9004 if(typeof cardNumber !== 'string' || cardNumber.length === 0) { 9005 return; 9006 } 9007 9008 // allow dashes in card 9009 cardNumber = cardNumber.replace(/[\-]/g, ''); 9010 9011 // verify card types 9012 if(requiredTypes) { 9013 $.each(requiredTypes, function(index, type){ 9014 // verify each card type 9015 validation = cards[type]; 9016 if(validation) { 9017 valid = { 9018 length : ($.inArray(cardNumber.length, validation.length) !== -1), 9019 pattern : (cardNumber.search(validation.pattern) !== -1) 9020 }; 9021 if(valid.length && valid.pattern) { 9022 validCard = true; 9023 } 9024 } 9025 }); 9026 9027 if(!validCard) { 9028 return false; 9029 } 9030 } 9031 9032 // skip luhn for UnionPay 9033 unionPay = { 9034 number : ($.inArray(cardNumber.length, cards.unionPay.length) !== -1), 9035 pattern : (cardNumber.search(cards.unionPay.pattern) !== -1) 9036 }; 9037 if(unionPay.number && unionPay.pattern) { 9038 return true; 9039 } 9040 9041 // verify luhn, adapted from <https://gist.github.com/2134376> 9042 var 9043 length = cardNumber.length, 9044 multiple = 0, 9045 producedValue = [ 9046 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 9047 [0, 2, 4, 6, 8, 1, 3, 5, 7, 9] 9048 ], 9049 sum = 0 9050 ; 9051 while (length--) { 9052 sum += producedValue[multiple][parseInt(cardNumber.charAt(length), 10)]; 9053 multiple ^= 1; 9054 } 9055 return (sum % 10 === 0 && sum > 0); 9056 }, 9057 9058 minCount: function(value, minCount) { 9059 if(minCount == 0) { 9060 return true; 9061 } 9062 if(minCount == 1) { 9063 return (value !== ''); 9064 } 9065 return (value.split(',').length >= minCount); 9066 }, 9067 9068 exactCount: function(value, exactCount) { 9069 if(exactCount == 0) { 9070 return (value === ''); 9071 } 9072 if(exactCount == 1) { 9073 return (value !== '' && value.search(',') === -1); 9074 } 9075 return (value.split(',').length == exactCount); 9076 }, 9077 9078 maxCount: function(value, maxCount) { 9079 if(maxCount == 0) { 9080 return false; 9081 } 9082 if(maxCount == 1) { 9083 return (value.search(',') === -1); 9084 } 9085 return (value.split(',').length <= maxCount); 9086 } 9087 } 9088 9089 }; 9090 9091 })( jQuery, window, document ); 9092 9093 /*! 9094 * # Fomantic-UI - Modal 9095 * http://github.com/fomantic/Fomantic-UI/ 9096 * 9097 * 9098 * Released under the MIT license 9099 * http://opensource.org/licenses/MIT 9100 * 9101 */ 9102 9103 ;(function ($, window, document, undefined) { 9104 9105 'use strict'; 9106 9107 $.isFunction = $.isFunction || function(obj) { 9108 return typeof obj === "function" && typeof obj.nodeType !== "number"; 9109 }; 9110 9111 window = (typeof window != 'undefined' && window.Math == Math) 9112 ? window 9113 : (typeof self != 'undefined' && self.Math == Math) 9114 ? self 9115 : Function('return this')() 9116 ; 9117 9118 $.fn.modal = function(parameters) { 9119 var 9120 $allModules = $(this), 9121 $window = $(window), 9122 $document = $(document), 9123 $body = $('body'), 9124 9125 moduleSelector = $allModules.selector || '', 9126 9127 time = new Date().getTime(), 9128 performance = [], 9129 9130 query = arguments[0], 9131 methodInvoked = (typeof query == 'string'), 9132 queryArguments = [].slice.call(arguments, 1), 9133 9134 requestAnimationFrame = window.requestAnimationFrame 9135 || window.mozRequestAnimationFrame 9136 || window.webkitRequestAnimationFrame 9137 || window.msRequestAnimationFrame 9138 || function(callback) { setTimeout(callback, 0); }, 9139 9140 returnedValue 9141 ; 9142 9143 $allModules 9144 .each(function() { 9145 var 9146 settings = ( $.isPlainObject(parameters) ) 9147 ? $.extend(true, {}, $.fn.modal.settings, parameters) 9148 : $.extend({}, $.fn.modal.settings), 9149 9150 selector = settings.selector, 9151 className = settings.className, 9152 namespace = settings.namespace, 9153 error = settings.error, 9154 9155 eventNamespace = '.' + namespace, 9156 moduleNamespace = 'module-' + namespace, 9157 9158 $module = $(this), 9159 $context = $(settings.context), 9160 $close = $module.find(selector.close), 9161 9162 $allModals, 9163 $otherModals, 9164 $focusedElement, 9165 $dimmable, 9166 $dimmer, 9167 9168 element = this, 9169 instance = $module.data(moduleNamespace), 9170 9171 ignoreRepeatedEvents = false, 9172 9173 initialMouseDownInModal, 9174 initialMouseDownInScrollbar, 9175 initialBodyMargin = '', 9176 tempBodyMargin = '', 9177 9178 elementEventNamespace, 9179 id, 9180 observer, 9181 module 9182 ; 9183 module = { 9184 9185 initialize: function() { 9186 module.cache = {}; 9187 module.verbose('Initializing dimmer', $context); 9188 9189 module.create.id(); 9190 module.create.dimmer(); 9191 9192 if ( settings.allowMultiple ) { 9193 module.create.innerDimmer(); 9194 } 9195 if (!settings.centered){ 9196 $module.addClass('top aligned'); 9197 } 9198 module.refreshModals(); 9199 9200 module.bind.events(); 9201 if(settings.observeChanges) { 9202 module.observeChanges(); 9203 } 9204 module.instantiate(); 9205 }, 9206 9207 instantiate: function() { 9208 module.verbose('Storing instance of modal'); 9209 instance = module; 9210 $module 9211 .data(moduleNamespace, instance) 9212 ; 9213 }, 9214 9215 create: { 9216 dimmer: function() { 9217 var 9218 defaultSettings = { 9219 debug : settings.debug, 9220 dimmerName : 'modals' 9221 }, 9222 dimmerSettings = $.extend(true, defaultSettings, settings.dimmerSettings) 9223 ; 9224 if($.fn.dimmer === undefined) { 9225 module.error(error.dimmer); 9226 return; 9227 } 9228 module.debug('Creating dimmer'); 9229 $dimmable = $context.dimmer(dimmerSettings); 9230 if(settings.detachable) { 9231 module.verbose('Modal is detachable, moving content into dimmer'); 9232 $dimmable.dimmer('add content', $module); 9233 } 9234 else { 9235 module.set.undetached(); 9236 } 9237 $dimmer = $dimmable.dimmer('get dimmer'); 9238 }, 9239 id: function() { 9240 id = (Math.random().toString(16) + '000000000').substr(2, 8); 9241 elementEventNamespace = '.' + id; 9242 module.verbose('Creating unique id for element', id); 9243 }, 9244 innerDimmer: function() { 9245 if ( $module.find(selector.dimmer).length == 0 ) { 9246 $module.prepend('<div class="ui inverted dimmer"></div>'); 9247 } 9248 } 9249 }, 9250 9251 destroy: function() { 9252 if (observer) { 9253 observer.disconnect(); 9254 } 9255 module.verbose('Destroying previous modal'); 9256 $module 9257 .removeData(moduleNamespace) 9258 .off(eventNamespace) 9259 ; 9260 $window.off(elementEventNamespace); 9261 $dimmer.off(elementEventNamespace); 9262 $close.off(eventNamespace); 9263 $context.dimmer('destroy'); 9264 }, 9265 9266 observeChanges: function() { 9267 if('MutationObserver' in window) { 9268 observer = new MutationObserver(function(mutations) { 9269 module.debug('DOM tree modified, refreshing'); 9270 module.refresh(); 9271 }); 9272 observer.observe(element, { 9273 childList : true, 9274 subtree : true 9275 }); 9276 module.debug('Setting up mutation observer', observer); 9277 } 9278 }, 9279 9280 refresh: function() { 9281 module.remove.scrolling(); 9282 module.cacheSizes(); 9283 if(!module.can.useFlex()) { 9284 module.set.modalOffset(); 9285 } 9286 module.set.screenHeight(); 9287 module.set.type(); 9288 }, 9289 9290 refreshModals: function() { 9291 $otherModals = $module.siblings(selector.modal); 9292 $allModals = $otherModals.add($module); 9293 }, 9294 9295 attachEvents: function(selector, event) { 9296 var 9297 $toggle = $(selector) 9298 ; 9299 event = $.isFunction(module[event]) 9300 ? module[event] 9301 : module.toggle 9302 ; 9303 if($toggle.length > 0) { 9304 module.debug('Attaching modal events to element', selector, event); 9305 $toggle 9306 .off(eventNamespace) 9307 .on('click' + eventNamespace, event) 9308 ; 9309 } 9310 else { 9311 module.error(error.notFound, selector); 9312 } 9313 }, 9314 9315 bind: { 9316 events: function() { 9317 module.verbose('Attaching events'); 9318 $module 9319 .on('click' + eventNamespace, selector.close, module.event.close) 9320 .on('click' + eventNamespace, selector.approve, module.event.approve) 9321 .on('click' + eventNamespace, selector.deny, module.event.deny) 9322 ; 9323 $window 9324 .on('resize' + elementEventNamespace, module.event.resize) 9325 ; 9326 }, 9327 scrollLock: function() { 9328 // touch events default to passive, due to changes in chrome to optimize mobile perf 9329 $dimmable.get(0).addEventListener('touchmove', module.event.preventScroll, { passive: false }); 9330 } 9331 }, 9332 9333 unbind: { 9334 scrollLock: function() { 9335 $dimmable.get(0).removeEventListener('touchmove', module.event.preventScroll, { passive: false }); 9336 } 9337 }, 9338 9339 get: { 9340 id: function() { 9341 return (Math.random().toString(16) + '000000000').substr(2, 8); 9342 } 9343 }, 9344 9345 event: { 9346 approve: function() { 9347 if(ignoreRepeatedEvents || settings.onApprove.call(element, $(this)) === false) { 9348 module.verbose('Approve callback returned false cancelling hide'); 9349 return; 9350 } 9351 ignoreRepeatedEvents = true; 9352 module.hide(function() { 9353 ignoreRepeatedEvents = false; 9354 }); 9355 }, 9356 preventScroll: function(event) { 9357 if(event.target.className.indexOf('dimmer') !== -1) { 9358 event.preventDefault(); 9359 } 9360 }, 9361 deny: function() { 9362 if(ignoreRepeatedEvents || settings.onDeny.call(element, $(this)) === false) { 9363 module.verbose('Deny callback returned false cancelling hide'); 9364 return; 9365 } 9366 ignoreRepeatedEvents = true; 9367 module.hide(function() { 9368 ignoreRepeatedEvents = false; 9369 }); 9370 }, 9371 close: function() { 9372 module.hide(); 9373 }, 9374 mousedown: function(event) { 9375 var 9376 $target = $(event.target), 9377 isRtl = module.is.rtl(); 9378 ; 9379 initialMouseDownInModal = ($target.closest(selector.modal).length > 0); 9380 if(initialMouseDownInModal) { 9381 module.verbose('Mouse down event registered inside the modal'); 9382 } 9383 initialMouseDownInScrollbar = module.is.scrolling() && ((!isRtl && $(window).outerWidth() - settings.scrollbarWidth <= event.clientX) || (isRtl && settings.scrollbarWidth >= event.clientX)); 9384 if(initialMouseDownInScrollbar) { 9385 module.verbose('Mouse down event registered inside the scrollbar'); 9386 } 9387 }, 9388 mouseup: function(event) { 9389 if(!settings.closable) { 9390 module.verbose('Dimmer clicked but closable setting is disabled'); 9391 return; 9392 } 9393 if(initialMouseDownInModal) { 9394 module.debug('Dimmer clicked but mouse down was initially registered inside the modal'); 9395 return; 9396 } 9397 if(initialMouseDownInScrollbar){ 9398 module.debug('Dimmer clicked but mouse down was initially registered inside the scrollbar'); 9399 return; 9400 } 9401 var 9402 $target = $(event.target), 9403 isInModal = ($target.closest(selector.modal).length > 0), 9404 isInDOM = $.contains(document.documentElement, event.target) 9405 ; 9406 if(!isInModal && isInDOM && module.is.active() && $module.hasClass(className.front) ) { 9407 module.debug('Dimmer clicked, hiding all modals'); 9408 if(settings.allowMultiple) { 9409 if(!module.hideAll()) { 9410 return; 9411 } 9412 } 9413 else if(!module.hide()){ 9414 return; 9415 } 9416 module.remove.clickaway(); 9417 } 9418 }, 9419 debounce: function(method, delay) { 9420 clearTimeout(module.timer); 9421 module.timer = setTimeout(method, delay); 9422 }, 9423 keyboard: function(event) { 9424 var 9425 keyCode = event.which, 9426 escapeKey = 27 9427 ; 9428 if(keyCode == escapeKey) { 9429 if(settings.closable) { 9430 module.debug('Escape key pressed hiding modal'); 9431 if ( $module.hasClass(className.front) ) { 9432 module.hide(); 9433 } 9434 } 9435 else { 9436 module.debug('Escape key pressed, but closable is set to false'); 9437 } 9438 event.preventDefault(); 9439 } 9440 }, 9441 resize: function() { 9442 if( $dimmable.dimmer('is active') && ( module.is.animating() || module.is.active() ) ) { 9443 requestAnimationFrame(module.refresh); 9444 } 9445 } 9446 }, 9447 9448 toggle: function() { 9449 if( module.is.active() || module.is.animating() ) { 9450 module.hide(); 9451 } 9452 else { 9453 module.show(); 9454 } 9455 }, 9456 9457 show: function(callback) { 9458 callback = $.isFunction(callback) 9459 ? callback 9460 : function(){} 9461 ; 9462 module.refreshModals(); 9463 module.set.dimmerSettings(); 9464 module.set.dimmerStyles(); 9465 9466 module.showModal(callback); 9467 }, 9468 9469 hide: function(callback) { 9470 callback = $.isFunction(callback) 9471 ? callback 9472 : function(){} 9473 ; 9474 module.refreshModals(); 9475 return module.hideModal(callback); 9476 }, 9477 9478 showModal: function(callback) { 9479 callback = $.isFunction(callback) 9480 ? callback 9481 : function(){} 9482 ; 9483 if( module.is.animating() || !module.is.active() ) { 9484 module.showDimmer(); 9485 module.cacheSizes(); 9486 module.set.bodyMargin(); 9487 if(module.can.useFlex()) { 9488 module.remove.legacy(); 9489 } 9490 else { 9491 module.set.legacy(); 9492 module.set.modalOffset(); 9493 module.debug('Using non-flex legacy modal positioning.'); 9494 } 9495 module.set.screenHeight(); 9496 module.set.type(); 9497 module.set.clickaway(); 9498 9499 if( !settings.allowMultiple && module.others.active() ) { 9500 module.hideOthers(module.showModal); 9501 } 9502 else { 9503 ignoreRepeatedEvents = false; 9504 if( settings.allowMultiple ) { 9505 if ( module.others.active() ) { 9506 $otherModals.filter('.' + className.active).find(selector.dimmer).addClass('active'); 9507 } 9508 9509 if ( settings.detachable ) { 9510 $module.detach().appendTo($dimmer); 9511 } 9512 } 9513 settings.onShow.call(element); 9514 if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) { 9515 module.debug('Showing modal with css animations'); 9516 $module 9517 .transition({ 9518 debug : settings.debug, 9519 animation : settings.transition + ' in', 9520 queue : settings.queue, 9521 duration : settings.duration, 9522 useFailSafe : true, 9523 onComplete : function() { 9524 settings.onVisible.apply(element); 9525 if(settings.keyboardShortcuts) { 9526 module.add.keyboardShortcuts(); 9527 } 9528 module.save.focus(); 9529 module.set.active(); 9530 if(settings.autofocus) { 9531 module.set.autofocus(); 9532 } 9533 callback(); 9534 } 9535 }) 9536 ; 9537 } 9538 else { 9539 module.error(error.noTransition); 9540 } 9541 } 9542 } 9543 else { 9544 module.debug('Modal is already visible'); 9545 } 9546 }, 9547 9548 hideModal: function(callback, keepDimmed, hideOthersToo) { 9549 var 9550 $previousModal = $otherModals.filter('.' + className.active).last() 9551 ; 9552 callback = $.isFunction(callback) 9553 ? callback 9554 : function(){} 9555 ; 9556 module.debug('Hiding modal'); 9557 if(settings.onHide.call(element, $(this)) === false) { 9558 module.verbose('Hide callback returned false cancelling hide'); 9559 ignoreRepeatedEvents = false; 9560 return false; 9561 } 9562 9563 if( module.is.animating() || module.is.active() ) { 9564 if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) { 9565 module.remove.active(); 9566 $module 9567 .transition({ 9568 debug : settings.debug, 9569 animation : settings.transition + ' out', 9570 queue : settings.queue, 9571 duration : settings.duration, 9572 useFailSafe : true, 9573 onStart : function() { 9574 if(!module.others.active() && !module.others.animating() && !keepDimmed) { 9575 module.hideDimmer(); 9576 } 9577 if( settings.keyboardShortcuts && !module.others.active() ) { 9578 module.remove.keyboardShortcuts(); 9579 } 9580 }, 9581 onComplete : function() { 9582 module.unbind.scrollLock(); 9583 if ( settings.allowMultiple ) { 9584 $previousModal.addClass(className.front); 9585 $module.removeClass(className.front); 9586 9587 if ( hideOthersToo ) { 9588 $allModals.find(selector.dimmer).removeClass('active'); 9589 } 9590 else { 9591 $previousModal.find(selector.dimmer).removeClass('active'); 9592 } 9593 } 9594 settings.onHidden.call(element); 9595 module.remove.dimmerStyles(); 9596 module.restore.focus(); 9597 callback(); 9598 } 9599 }) 9600 ; 9601 } 9602 else { 9603 module.error(error.noTransition); 9604 } 9605 } 9606 }, 9607 9608 showDimmer: function() { 9609 if($dimmable.dimmer('is animating') || !$dimmable.dimmer('is active') ) { 9610 module.save.bodyMargin(); 9611 module.debug('Showing dimmer'); 9612 $dimmable.dimmer('show'); 9613 } 9614 else { 9615 module.debug('Dimmer already visible'); 9616 } 9617 }, 9618 9619 hideDimmer: function() { 9620 if( $dimmable.dimmer('is animating') || ($dimmable.dimmer('is active')) ) { 9621 module.unbind.scrollLock(); 9622 $dimmable.dimmer('hide', function() { 9623 module.restore.bodyMargin(); 9624 module.remove.clickaway(); 9625 module.remove.screenHeight(); 9626 }); 9627 } 9628 else { 9629 module.debug('Dimmer is not visible cannot hide'); 9630 return; 9631 } 9632 }, 9633 9634 hideAll: function(callback) { 9635 var 9636 $visibleModals = $allModals.filter('.' + className.active + ', .' + className.animating) 9637 ; 9638 callback = $.isFunction(callback) 9639 ? callback 9640 : function(){} 9641 ; 9642 if( $visibleModals.length > 0 ) { 9643 module.debug('Hiding all visible modals'); 9644 var hideOk = true; 9645 //check in reverse order trying to hide most top displayed modal first 9646 $($visibleModals.get().reverse()).each(function(index,element){ 9647 if(hideOk){ 9648 hideOk = $(element).modal('hide modal', callback, false, true); 9649 } 9650 }); 9651 if(hideOk) { 9652 module.hideDimmer(); 9653 } 9654 return hideOk; 9655 } 9656 }, 9657 9658 hideOthers: function(callback) { 9659 var 9660 $visibleModals = $otherModals.filter('.' + className.active + ', .' + className.animating) 9661 ; 9662 callback = $.isFunction(callback) 9663 ? callback 9664 : function(){} 9665 ; 9666 if( $visibleModals.length > 0 ) { 9667 module.debug('Hiding other modals', $otherModals); 9668 $visibleModals 9669 .modal('hide modal', callback, true) 9670 ; 9671 } 9672 }, 9673 9674 others: { 9675 active: function() { 9676 return ($otherModals.filter('.' + className.active).length > 0); 9677 }, 9678 animating: function() { 9679 return ($otherModals.filter('.' + className.animating).length > 0); 9680 } 9681 }, 9682 9683 9684 add: { 9685 keyboardShortcuts: function() { 9686 module.verbose('Adding keyboard shortcuts'); 9687 $document 9688 .on('keyup' + eventNamespace, module.event.keyboard) 9689 ; 9690 } 9691 }, 9692 9693 save: { 9694 focus: function() { 9695 var 9696 $activeElement = $(document.activeElement), 9697 inCurrentModal = $activeElement.closest($module).length > 0 9698 ; 9699 if(!inCurrentModal) { 9700 $focusedElement = $(document.activeElement).blur(); 9701 } 9702 }, 9703 bodyMargin: function() { 9704 initialBodyMargin = $body.css('margin-'+(module.can.leftBodyScrollbar() ? 'left':'right')); 9705 var bodyMarginRightPixel = parseInt(initialBodyMargin.replace(/[^\d.]/g, '')), 9706 bodyScrollbarWidth = window.innerWidth - document.documentElement.clientWidth; 9707 tempBodyMargin = bodyMarginRightPixel + bodyScrollbarWidth; 9708 } 9709 }, 9710 9711 restore: { 9712 focus: function() { 9713 if($focusedElement && $focusedElement.length > 0 && settings.restoreFocus) { 9714 $focusedElement.focus(); 9715 } 9716 }, 9717 bodyMargin: function() { 9718 var position = module.can.leftBodyScrollbar() ? 'left':'right'; 9719 $body.css('margin-'+position, initialBodyMargin); 9720 $body.find(selector.bodyFixed.replace('right',position)).css('padding-'+position, initialBodyMargin); 9721 } 9722 }, 9723 9724 remove: { 9725 active: function() { 9726 $module.removeClass(className.active); 9727 }, 9728 legacy: function() { 9729 $module.removeClass(className.legacy); 9730 }, 9731 clickaway: function() { 9732 if (!settings.detachable) { 9733 $module 9734 .off('mousedown' + elementEventNamespace) 9735 ; 9736 } 9737 $dimmer 9738 .off('mousedown' + elementEventNamespace) 9739 ; 9740 $dimmer 9741 .off('mouseup' + elementEventNamespace) 9742 ; 9743 }, 9744 dimmerStyles: function() { 9745 $dimmer.removeClass(className.inverted); 9746 $dimmable.removeClass(className.blurring); 9747 }, 9748 bodyStyle: function() { 9749 if($body.attr('style') === '') { 9750 module.verbose('Removing style attribute'); 9751 $body.removeAttr('style'); 9752 } 9753 }, 9754 screenHeight: function() { 9755 module.debug('Removing page height'); 9756 $body 9757 .css('height', '') 9758 ; 9759 }, 9760 keyboardShortcuts: function() { 9761 module.verbose('Removing keyboard shortcuts'); 9762 $document 9763 .off('keyup' + eventNamespace) 9764 ; 9765 }, 9766 scrolling: function() { 9767 $dimmable.removeClass(className.scrolling); 9768 $module.removeClass(className.scrolling); 9769 } 9770 }, 9771 9772 cacheSizes: function() { 9773 $module.addClass(className.loading); 9774 var 9775 scrollHeight = $module.prop('scrollHeight'), 9776 modalWidth = $module.outerWidth(), 9777 modalHeight = $module.outerHeight() 9778 ; 9779 if(module.cache.pageHeight === undefined || modalHeight !== 0) { 9780 $.extend(module.cache, { 9781 pageHeight : $(document).outerHeight(), 9782 width : modalWidth, 9783 height : modalHeight + settings.offset, 9784 scrollHeight : scrollHeight + settings.offset, 9785 contextHeight : (settings.context == 'body') 9786 ? $(window).height() 9787 : $dimmable.height(), 9788 }); 9789 module.cache.topOffset = -(module.cache.height / 2); 9790 } 9791 $module.removeClass(className.loading); 9792 module.debug('Caching modal and container sizes', module.cache); 9793 }, 9794 9795 can: { 9796 leftBodyScrollbar: function(){ 9797 if(module.cache.leftBodyScrollbar === undefined) { 9798 module.cache.leftBodyScrollbar = module.is.rtl() && ((module.is.iframe && !module.is.firefox()) || module.is.safari() || module.is.edge() || module.is.ie()); 9799 } 9800 return module.cache.leftBodyScrollbar; 9801 }, 9802 useFlex: function() { 9803 if (settings.useFlex === 'auto') { 9804 return settings.detachable && !module.is.ie(); 9805 } 9806 if(settings.useFlex && module.is.ie()) { 9807 module.debug('useFlex true is not supported in IE'); 9808 } else if(settings.useFlex && !settings.detachable) { 9809 module.debug('useFlex true in combination with detachable false is not supported'); 9810 } 9811 return settings.useFlex; 9812 }, 9813 fit: function() { 9814 var 9815 contextHeight = module.cache.contextHeight, 9816 verticalCenter = module.cache.contextHeight / 2, 9817 topOffset = module.cache.topOffset, 9818 scrollHeight = module.cache.scrollHeight, 9819 height = module.cache.height, 9820 paddingHeight = settings.padding, 9821 startPosition = (verticalCenter + topOffset) 9822 ; 9823 return (scrollHeight > height) 9824 ? (startPosition + scrollHeight + paddingHeight < contextHeight) 9825 : (height + (paddingHeight * 2) < contextHeight) 9826 ; 9827 } 9828 }, 9829 9830 is: { 9831 active: function() { 9832 return $module.hasClass(className.active); 9833 }, 9834 ie: function() { 9835 if(module.cache.isIE === undefined) { 9836 var 9837 isIE11 = (!(window.ActiveXObject) && 'ActiveXObject' in window), 9838 isIE = ('ActiveXObject' in window) 9839 ; 9840 module.cache.isIE = (isIE11 || isIE); 9841 } 9842 return module.cache.isIE; 9843 }, 9844 animating: function() { 9845 return $module.transition('is supported') 9846 ? $module.transition('is animating') 9847 : $module.is(':visible') 9848 ; 9849 }, 9850 scrolling: function() { 9851 return $dimmable.hasClass(className.scrolling); 9852 }, 9853 modernBrowser: function() { 9854 // appName for IE11 reports 'Netscape' can no longer use 9855 return !(window.ActiveXObject || 'ActiveXObject' in window); 9856 }, 9857 rtl: function() { 9858 if(module.cache.isRTL === undefined) { 9859 module.cache.isRTL = $body.attr('dir') === 'rtl' || $body.css('direction') === 'rtl'; 9860 } 9861 return module.cache.isRTL; 9862 }, 9863 safari: function() { 9864 if(module.cache.isSafari === undefined) { 9865 module.cache.isSafari = /constructor/i.test(window.HTMLElement) || !!window.ApplePaySession; 9866 } 9867 return module.cache.isSafari; 9868 }, 9869 edge: function(){ 9870 if(module.cache.isEdge === undefined) { 9871 module.cache.isEdge = !!window.setImmediate && !module.is.ie(); 9872 } 9873 return module.cache.isEdge; 9874 }, 9875 firefox: function(){ 9876 if(module.cache.isFirefox === undefined) { 9877 module.cache.isFirefox = !!window.InstallTrigger; 9878 } 9879 return module.cache.isFirefox; 9880 }, 9881 iframe: function() { 9882 return !(self === top); 9883 } 9884 }, 9885 9886 set: { 9887 autofocus: function() { 9888 var 9889 $inputs = $module.find('[tabindex], :input').filter(':visible').filter(function() { 9890 return $(this).closest('.disabled').length === 0; 9891 }), 9892 $autofocus = $inputs.filter('[autofocus]'), 9893 $input = ($autofocus.length > 0) 9894 ? $autofocus.first() 9895 : $inputs.first() 9896 ; 9897 if($input.length > 0) { 9898 $input.focus(); 9899 } 9900 }, 9901 bodyMargin: function() { 9902 var position = module.can.leftBodyScrollbar() ? 'left':'right'; 9903 if(settings.detachable || module.can.fit()) { 9904 $body.css('margin-'+position, tempBodyMargin + 'px'); 9905 } 9906 $body.find(selector.bodyFixed.replace('right',position)).css('padding-'+position, tempBodyMargin + 'px'); 9907 }, 9908 clickaway: function() { 9909 if (!settings.detachable) { 9910 $module 9911 .on('mousedown' + elementEventNamespace, module.event.mousedown) 9912 ; 9913 } 9914 $dimmer 9915 .on('mousedown' + elementEventNamespace, module.event.mousedown) 9916 ; 9917 $dimmer 9918 .on('mouseup' + elementEventNamespace, module.event.mouseup) 9919 ; 9920 }, 9921 dimmerSettings: function() { 9922 if($.fn.dimmer === undefined) { 9923 module.error(error.dimmer); 9924 return; 9925 } 9926 var 9927 defaultSettings = { 9928 debug : settings.debug, 9929 dimmerName : 'modals', 9930 closable : 'auto', 9931 useFlex : module.can.useFlex(), 9932 duration : { 9933 show : settings.duration, 9934 hide : settings.duration 9935 } 9936 }, 9937 dimmerSettings = $.extend(true, defaultSettings, settings.dimmerSettings) 9938 ; 9939 if(settings.inverted) { 9940 dimmerSettings.variation = (dimmerSettings.variation !== undefined) 9941 ? dimmerSettings.variation + ' inverted' 9942 : 'inverted' 9943 ; 9944 } 9945 $context.dimmer('setting', dimmerSettings); 9946 }, 9947 dimmerStyles: function() { 9948 if(settings.inverted) { 9949 $dimmer.addClass(className.inverted); 9950 } 9951 else { 9952 $dimmer.removeClass(className.inverted); 9953 } 9954 if(settings.blurring) { 9955 $dimmable.addClass(className.blurring); 9956 } 9957 else { 9958 $dimmable.removeClass(className.blurring); 9959 } 9960 }, 9961 modalOffset: function() { 9962 if (!settings.detachable) { 9963 var canFit = module.can.fit(); 9964 $module 9965 .css({ 9966 top: (!$module.hasClass('aligned') && canFit) 9967 ? $(document).scrollTop() + (module.cache.contextHeight - module.cache.height) / 2 9968 : !canFit || $module.hasClass('top') 9969 ? $(document).scrollTop() + settings.padding 9970 : $(document).scrollTop() + (module.cache.contextHeight - module.cache.height - settings.padding), 9971 marginLeft: -(module.cache.width / 2) 9972 }) 9973 ; 9974 } else { 9975 $module 9976 .css({ 9977 marginTop: (!$module.hasClass('aligned') && module.can.fit()) 9978 ? -(module.cache.height / 2) 9979 : settings.padding / 2, 9980 marginLeft: -(module.cache.width / 2) 9981 }) 9982 ; 9983 } 9984 module.verbose('Setting modal offset for legacy mode'); 9985 }, 9986 screenHeight: function() { 9987 if( module.can.fit() ) { 9988 $body.css('height', ''); 9989 } 9990 else if(!$module.hasClass('bottom')) { 9991 module.debug('Modal is taller than page content, resizing page height'); 9992 $body 9993 .css('height', module.cache.height + (settings.padding * 2) ) 9994 ; 9995 } 9996 }, 9997 active: function() { 9998 $module.addClass(className.active + ' ' + className.front); 9999 $otherModals.filter('.' + className.active).removeClass(className.front); 10000 }, 10001 scrolling: function() { 10002 $dimmable.addClass(className.scrolling); 10003 $module.addClass(className.scrolling); 10004 module.unbind.scrollLock(); 10005 }, 10006 legacy: function() { 10007 $module.addClass(className.legacy); 10008 }, 10009 type: function() { 10010 if(module.can.fit()) { 10011 module.verbose('Modal fits on screen'); 10012 if(!module.others.active() && !module.others.animating()) { 10013 module.remove.scrolling(); 10014 module.bind.scrollLock(); 10015 } 10016 } 10017 else if (!$module.hasClass('bottom')){ 10018 module.verbose('Modal cannot fit on screen setting to scrolling'); 10019 module.set.scrolling(); 10020 } else { 10021 module.verbose('Bottom aligned modal not fitting on screen is unsupported for scrolling'); 10022 } 10023 }, 10024 undetached: function() { 10025 $dimmable.addClass(className.undetached); 10026 } 10027 }, 10028 10029 setting: function(name, value) { 10030 module.debug('Changing setting', name, value); 10031 if( $.isPlainObject(name) ) { 10032 $.extend(true, settings, name); 10033 } 10034 else if(value !== undefined) { 10035 if($.isPlainObject(settings[name])) { 10036 $.extend(true, settings[name], value); 10037 } 10038 else { 10039 settings[name] = value; 10040 } 10041 } 10042 else { 10043 return settings[name]; 10044 } 10045 }, 10046 internal: function(name, value) { 10047 if( $.isPlainObject(name) ) { 10048 $.extend(true, module, name); 10049 } 10050 else if(value !== undefined) { 10051 module[name] = value; 10052 } 10053 else { 10054 return module[name]; 10055 } 10056 }, 10057 debug: function() { 10058 if(!settings.silent && settings.debug) { 10059 if(settings.performance) { 10060 module.performance.log(arguments); 10061 } 10062 else { 10063 module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); 10064 module.debug.apply(console, arguments); 10065 } 10066 } 10067 }, 10068 verbose: function() { 10069 if(!settings.silent && settings.verbose && settings.debug) { 10070 if(settings.performance) { 10071 module.performance.log(arguments); 10072 } 10073 else { 10074 module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); 10075 module.verbose.apply(console, arguments); 10076 } 10077 } 10078 }, 10079 error: function() { 10080 if(!settings.silent) { 10081 module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); 10082 module.error.apply(console, arguments); 10083 } 10084 }, 10085 performance: { 10086 log: function(message) { 10087 var 10088 currentTime, 10089 executionTime, 10090 previousTime 10091 ; 10092 if(settings.performance) { 10093 currentTime = new Date().getTime(); 10094 previousTime = time || currentTime; 10095 executionTime = currentTime - previousTime; 10096 time = currentTime; 10097 performance.push({ 10098 'Name' : message[0], 10099 'Arguments' : [].slice.call(message, 1) || '', 10100 'Element' : element, 10101 'Execution Time' : executionTime 10102 }); 10103 } 10104 clearTimeout(module.performance.timer); 10105 module.performance.timer = setTimeout(module.performance.display, 500); 10106 }, 10107 display: function() { 10108 var 10109 title = settings.name + ':', 10110 totalTime = 0 10111 ; 10112 time = false; 10113 clearTimeout(module.performance.timer); 10114 $.each(performance, function(index, data) { 10115 totalTime += data['Execution Time']; 10116 }); 10117 title += ' ' + totalTime + 'ms'; 10118 if(moduleSelector) { 10119 title += ' \'' + moduleSelector + '\''; 10120 } 10121 if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { 10122 console.groupCollapsed(title); 10123 if(console.table) { 10124 console.table(performance); 10125 } 10126 else { 10127 $.each(performance, function(index, data) { 10128 console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); 10129 }); 10130 } 10131 console.groupEnd(); 10132 } 10133 performance = []; 10134 } 10135 }, 10136 invoke: function(query, passedArguments, context) { 10137 var 10138 object = instance, 10139 maxDepth, 10140 found, 10141 response 10142 ; 10143 passedArguments = passedArguments || queryArguments; 10144 context = element || context; 10145 if(typeof query == 'string' && object !== undefined) { 10146 query = query.split(/[\. ]/); 10147 maxDepth = query.length - 1; 10148 $.each(query, function(depth, value) { 10149 var camelCaseValue = (depth != maxDepth) 10150 ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) 10151 : query 10152 ; 10153 if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { 10154 object = object[camelCaseValue]; 10155 } 10156 else if( object[camelCaseValue] !== undefined ) { 10157 found = object[camelCaseValue]; 10158 return false; 10159 } 10160 else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { 10161 object = object[value]; 10162 } 10163 else if( object[value] !== undefined ) { 10164 found = object[value]; 10165 return false; 10166 } 10167 else { 10168 return false; 10169 } 10170 }); 10171 } 10172 if ( $.isFunction( found ) ) { 10173 response = found.apply(context, passedArguments); 10174 } 10175 else if(found !== undefined) { 10176 response = found; 10177 } 10178 if(Array.isArray(returnedValue)) { 10179 returnedValue.push(response); 10180 } 10181 else if(returnedValue !== undefined) { 10182 returnedValue = [returnedValue, response]; 10183 } 10184 else if(response !== undefined) { 10185 returnedValue = response; 10186 } 10187 return found; 10188 } 10189 }; 10190 10191 if(methodInvoked) { 10192 if(instance === undefined) { 10193 module.initialize(); 10194 } 10195 module.invoke(query); 10196 } 10197 else { 10198 if(instance !== undefined) { 10199 instance.invoke('destroy'); 10200 } 10201 module.initialize(); 10202 } 10203 }) 10204 ; 10205 10206 return (returnedValue !== undefined) 10207 ? returnedValue 10208 : this 10209 ; 10210 }; 10211 10212 $.fn.modal.settings = { 10213 10214 name : 'Modal', 10215 namespace : 'modal', 10216 10217 useFlex : 'auto', 10218 offset : 0, 10219 10220 silent : false, 10221 debug : false, 10222 verbose : false, 10223 performance : true, 10224 10225 observeChanges : false, 10226 10227 allowMultiple : false, 10228 detachable : true, 10229 closable : true, 10230 autofocus : true, 10231 restoreFocus : true, 10232 10233 inverted : false, 10234 blurring : false, 10235 10236 centered : true, 10237 10238 dimmerSettings : { 10239 closable : false, 10240 useCSS : true 10241 }, 10242 10243 // whether to use keyboard shortcuts 10244 keyboardShortcuts: true, 10245 10246 context : 'body', 10247 10248 queue : false, 10249 duration : 500, 10250 transition : 'scale', 10251 10252 // padding with edge of page 10253 padding : 50, 10254 scrollbarWidth: 10, 10255 10256 // called before show animation 10257 onShow : function(){}, 10258 10259 // called after show animation 10260 onVisible : function(){}, 10261 10262 // called before hide animation 10263 onHide : function(){ return true; }, 10264 10265 // called after hide animation 10266 onHidden : function(){}, 10267 10268 // called after approve selector match 10269 onApprove : function(){ return true; }, 10270 10271 // called after deny selector match 10272 onDeny : function(){ return true; }, 10273 10274 selector : { 10275 close : '> .close', 10276 approve : '.actions .positive, .actions .approve, .actions .ok', 10277 deny : '.actions .negative, .actions .deny, .actions .cancel', 10278 modal : '.ui.modal', 10279 dimmer : '> .ui.dimmer', 10280 bodyFixed: '> .ui.fixed.menu, > .ui.right.toast-container, > .ui.right.sidebar' 10281 }, 10282 error : { 10283 dimmer : 'UI Dimmer, a required component is not included in this page', 10284 method : 'The method you called is not defined.', 10285 notFound : 'The element you specified could not be found' 10286 }, 10287 className : { 10288 active : 'active', 10289 animating : 'animating', 10290 blurring : 'blurring', 10291 inverted : 'inverted', 10292 legacy : 'legacy', 10293 loading : 'loading', 10294 scrolling : 'scrolling', 10295 undetached : 'undetached', 10296 front : 'front' 10297 } 10298 }; 10299 10300 10301 })( jQuery, window, document ); 10302 10303 /*! 10304 * # Fomantic-UI - Search 10305 * http://github.com/fomantic/Fomantic-UI/ 10306 * 10307 * 10308 * Released under the MIT license 10309 * http://opensource.org/licenses/MIT 10310 * 10311 */ 10312 10313 ;(function ($, window, document, undefined) { 10314 10315 'use strict'; 10316 10317 $.isFunction = $.isFunction || function(obj) { 10318 return typeof obj === "function" && typeof obj.nodeType !== "number"; 10319 }; 10320 10321 window = (typeof window != 'undefined' && window.Math == Math) 10322 ? window 10323 : (typeof self != 'undefined' && self.Math == Math) 10324 ? self 10325 : Function('return this')() 10326 ; 10327 10328 $.fn.search = function(parameters) { 10329 var 10330 $allModules = $(this), 10331 moduleSelector = $allModules.selector || '', 10332 10333 time = new Date().getTime(), 10334 performance = [], 10335 10336 query = arguments[0], 10337 methodInvoked = (typeof query == 'string'), 10338 queryArguments = [].slice.call(arguments, 1), 10339 returnedValue 10340 ; 10341 $(this) 10342 .each(function() { 10343 var 10344 settings = ( $.isPlainObject(parameters) ) 10345 ? $.extend(true, {}, $.fn.search.settings, parameters) 10346 : $.extend({}, $.fn.search.settings), 10347 10348 className = settings.className, 10349 metadata = settings.metadata, 10350 regExp = settings.regExp, 10351 fields = settings.fields, 10352 selector = settings.selector, 10353 error = settings.error, 10354 namespace = settings.namespace, 10355 10356 eventNamespace = '.' + namespace, 10357 moduleNamespace = namespace + '-module', 10358 10359 $module = $(this), 10360 $prompt = $module.find(selector.prompt), 10361 $searchButton = $module.find(selector.searchButton), 10362 $results = $module.find(selector.results), 10363 $result = $module.find(selector.result), 10364 $category = $module.find(selector.category), 10365 10366 element = this, 10367 instance = $module.data(moduleNamespace), 10368 10369 disabledBubbled = false, 10370 resultsDismissed = false, 10371 10372 module 10373 ; 10374 10375 module = { 10376 10377 initialize: function() { 10378 module.verbose('Initializing module'); 10379 module.get.settings(); 10380 module.determine.searchFields(); 10381 module.bind.events(); 10382 module.set.type(); 10383 module.create.results(); 10384 module.instantiate(); 10385 }, 10386 instantiate: function() { 10387 module.verbose('Storing instance of module', module); 10388 instance = module; 10389 $module 10390 .data(moduleNamespace, module) 10391 ; 10392 }, 10393 destroy: function() { 10394 module.verbose('Destroying instance'); 10395 $module 10396 .off(eventNamespace) 10397 .removeData(moduleNamespace) 10398 ; 10399 }, 10400 10401 refresh: function() { 10402 module.debug('Refreshing selector cache'); 10403 $prompt = $module.find(selector.prompt); 10404 $searchButton = $module.find(selector.searchButton); 10405 $category = $module.find(selector.category); 10406 $results = $module.find(selector.results); 10407 $result = $module.find(selector.result); 10408 }, 10409 10410 refreshResults: function() { 10411 $results = $module.find(selector.results); 10412 $result = $module.find(selector.result); 10413 }, 10414 10415 bind: { 10416 events: function() { 10417 module.verbose('Binding events to search'); 10418 if(settings.automatic) { 10419 $module 10420 .on(module.get.inputEvent() + eventNamespace, selector.prompt, module.event.input) 10421 ; 10422 $prompt 10423 .attr('autocomplete', 'off') 10424 ; 10425 } 10426 $module 10427 // prompt 10428 .on('focus' + eventNamespace, selector.prompt, module.event.focus) 10429 .on('blur' + eventNamespace, selector.prompt, module.event.blur) 10430 .on('keydown' + eventNamespace, selector.prompt, module.handleKeyboard) 10431 // search button 10432 .on('click' + eventNamespace, selector.searchButton, module.query) 10433 // results 10434 .on('mousedown' + eventNamespace, selector.results, module.event.result.mousedown) 10435 .on('mouseup' + eventNamespace, selector.results, module.event.result.mouseup) 10436 .on('click' + eventNamespace, selector.result, module.event.result.click) 10437 ; 10438 } 10439 }, 10440 10441 determine: { 10442 searchFields: function() { 10443 // this makes sure $.extend does not add specified search fields to default fields 10444 // this is the only setting which should not extend defaults 10445 if(parameters && parameters.searchFields !== undefined) { 10446 settings.searchFields = parameters.searchFields; 10447 } 10448 } 10449 }, 10450 10451 event: { 10452 input: function() { 10453 if(settings.searchDelay) { 10454 clearTimeout(module.timer); 10455 module.timer = setTimeout(function() { 10456 if(module.is.focused()) { 10457 module.query(); 10458 } 10459 }, settings.searchDelay); 10460 } 10461 else { 10462 module.query(); 10463 } 10464 }, 10465 focus: function() { 10466 module.set.focus(); 10467 if(settings.searchOnFocus && module.has.minimumCharacters() ) { 10468 module.query(function() { 10469 if(module.can.show() ) { 10470 module.showResults(); 10471 } 10472 }); 10473 } 10474 }, 10475 blur: function(event) { 10476 var 10477 pageLostFocus = (document.activeElement === this), 10478 callback = function() { 10479 module.cancel.query(); 10480 module.remove.focus(); 10481 module.timer = setTimeout(module.hideResults, settings.hideDelay); 10482 } 10483 ; 10484 if(pageLostFocus) { 10485 return; 10486 } 10487 resultsDismissed = false; 10488 if(module.resultsClicked) { 10489 module.debug('Determining if user action caused search to close'); 10490 $module 10491 .one('click.close' + eventNamespace, selector.results, function(event) { 10492 if(module.is.inMessage(event) || disabledBubbled) { 10493 $prompt.focus(); 10494 return; 10495 } 10496 disabledBubbled = false; 10497 if( !module.is.animating() && !module.is.hidden()) { 10498 callback(); 10499 } 10500 }) 10501 ; 10502 } 10503 else { 10504 module.debug('Input blurred without user action, closing results'); 10505 callback(); 10506 } 10507 }, 10508 result: { 10509 mousedown: function() { 10510 module.resultsClicked = true; 10511 }, 10512 mouseup: function() { 10513 module.resultsClicked = false; 10514 }, 10515 click: function(event) { 10516 module.debug('Search result selected'); 10517 var 10518 $result = $(this), 10519 $title = $result.find(selector.title).eq(0), 10520 $link = $result.is('a[href]') 10521 ? $result 10522 : $result.find('a[href]').eq(0), 10523 href = $link.attr('href') || false, 10524 target = $link.attr('target') || false, 10525 // title is used for result lookup 10526 value = ($title.length > 0) 10527 ? $title.text() 10528 : false, 10529 results = module.get.results(), 10530 result = $result.data(metadata.result) || module.get.result(value, results) 10531 ; 10532 if(value) { 10533 module.set.value(value); 10534 } 10535 if( $.isFunction(settings.onSelect) ) { 10536 if(settings.onSelect.call(element, result, results) === false) { 10537 module.debug('Custom onSelect callback cancelled default select action'); 10538 disabledBubbled = true; 10539 return; 10540 } 10541 } 10542 module.hideResults(); 10543 if(href) { 10544 event.preventDefault(); 10545 module.verbose('Opening search link found in result', $link); 10546 if(target == '_blank' || event.ctrlKey) { 10547 window.open(href); 10548 } 10549 else { 10550 window.location.href = (href); 10551 } 10552 } 10553 } 10554 } 10555 }, 10556 ensureVisible: function ensureVisible($el) { 10557 var elTop, elBottom, resultsScrollTop, resultsHeight; 10558 10559 elTop = $el.position().top; 10560 elBottom = elTop + $el.outerHeight(true); 10561 10562 resultsScrollTop = $results.scrollTop(); 10563 resultsHeight = $results.height() 10564 parseInt($results.css('paddingTop'), 0) + 10565 parseInt($results.css('paddingBottom'), 0); 10566 10567 if (elTop < 0) { 10568 $results.scrollTop(resultsScrollTop + elTop); 10569 } 10570 10571 else if (resultsHeight < elBottom) { 10572 $results.scrollTop(resultsScrollTop + (elBottom - resultsHeight)); 10573 } 10574 }, 10575 handleKeyboard: function(event) { 10576 var 10577 // force selector refresh 10578 $result = $module.find(selector.result), 10579 $category = $module.find(selector.category), 10580 $activeResult = $result.filter('.' + className.active), 10581 currentIndex = $result.index( $activeResult ), 10582 resultSize = $result.length, 10583 hasActiveResult = $activeResult.length > 0, 10584 10585 keyCode = event.which, 10586 keys = { 10587 backspace : 8, 10588 enter : 13, 10589 escape : 27, 10590 upArrow : 38, 10591 downArrow : 40 10592 }, 10593 newIndex 10594 ; 10595 // search shortcuts 10596 if(keyCode == keys.escape) { 10597 module.verbose('Escape key pressed, blurring search field'); 10598 module.hideResults(); 10599 resultsDismissed = true; 10600 } 10601 if( module.is.visible() ) { 10602 if(keyCode == keys.enter) { 10603 module.verbose('Enter key pressed, selecting active result'); 10604 if( $result.filter('.' + className.active).length > 0 ) { 10605 module.event.result.click.call($result.filter('.' + className.active), event); 10606 event.preventDefault(); 10607 return false; 10608 } 10609 } 10610 else if(keyCode == keys.upArrow && hasActiveResult) { 10611 module.verbose('Up key pressed, changing active result'); 10612 newIndex = (currentIndex - 1 < 0) 10613 ? currentIndex 10614 : currentIndex - 1 10615 ; 10616 $category 10617 .removeClass(className.active) 10618 ; 10619 $result 10620 .removeClass(className.active) 10621 .eq(newIndex) 10622 .addClass(className.active) 10623 .closest($category) 10624 .addClass(className.active) 10625 ; 10626 module.ensureVisible($result.eq(newIndex)); 10627 event.preventDefault(); 10628 } 10629 else if(keyCode == keys.downArrow) { 10630 module.verbose('Down key pressed, changing active result'); 10631 newIndex = (currentIndex + 1 >= resultSize) 10632 ? currentIndex 10633 : currentIndex + 1 10634 ; 10635 $category 10636 .removeClass(className.active) 10637 ; 10638 $result 10639 .removeClass(className.active) 10640 .eq(newIndex) 10641 .addClass(className.active) 10642 .closest($category) 10643 .addClass(className.active) 10644 ; 10645 module.ensureVisible($result.eq(newIndex)); 10646 event.preventDefault(); 10647 } 10648 } 10649 else { 10650 // query shortcuts 10651 if(keyCode == keys.enter) { 10652 module.verbose('Enter key pressed, executing query'); 10653 module.query(); 10654 module.set.buttonPressed(); 10655 $prompt.one('keyup', module.remove.buttonFocus); 10656 } 10657 } 10658 }, 10659 10660 setup: { 10661 api: function(searchTerm, callback) { 10662 var 10663 apiSettings = { 10664 debug : settings.debug, 10665 on : false, 10666 cache : settings.cache, 10667 action : 'search', 10668 urlData : { 10669 query : searchTerm 10670 }, 10671 onSuccess : function(response) { 10672 module.parse.response.call(element, response, searchTerm); 10673 callback(); 10674 }, 10675 onFailure : function() { 10676 module.displayMessage(error.serverError); 10677 callback(); 10678 }, 10679 onAbort : function(response) { 10680 }, 10681 onError : module.error 10682 } 10683 ; 10684 $.extend(true, apiSettings, settings.apiSettings); 10685 module.verbose('Setting up API request', apiSettings); 10686 $module.api(apiSettings); 10687 } 10688 }, 10689 10690 can: { 10691 useAPI: function() { 10692 return $.fn.api !== undefined; 10693 }, 10694 show: function() { 10695 return module.is.focused() && !module.is.visible() && !module.is.empty(); 10696 }, 10697 transition: function() { 10698 return settings.transition && $.fn.transition !== undefined && $module.transition('is supported'); 10699 } 10700 }, 10701 10702 is: { 10703 animating: function() { 10704 return $results.hasClass(className.animating); 10705 }, 10706 hidden: function() { 10707 return $results.hasClass(className.hidden); 10708 }, 10709 inMessage: function(event) { 10710 if(!event.target) { 10711 return; 10712 } 10713 var 10714 $target = $(event.target), 10715 isInDOM = $.contains(document.documentElement, event.target) 10716 ; 10717 return (isInDOM && $target.closest(selector.message).length > 0); 10718 }, 10719 empty: function() { 10720 return ($results.html() === ''); 10721 }, 10722 visible: function() { 10723 return ($results.filter(':visible').length > 0); 10724 }, 10725 focused: function() { 10726 return ($prompt.filter(':focus').length > 0); 10727 } 10728 }, 10729 10730 get: { 10731 settings: function() { 10732 if($.isPlainObject(parameters) && parameters.searchFullText) { 10733 settings.fullTextSearch = parameters.searchFullText; 10734 module.error(settings.error.oldSearchSyntax, element); 10735 } 10736 if (settings.ignoreDiacritics && !String.prototype.normalize) { 10737 settings.ignoreDiacritics = false; 10738 module.error(error.noNormalize, element); 10739 } 10740 }, 10741 inputEvent: function() { 10742 var 10743 prompt = $prompt[0], 10744 inputEvent = (prompt !== undefined && prompt.oninput !== undefined) 10745 ? 'input' 10746 : (prompt !== undefined && prompt.onpropertychange !== undefined) 10747 ? 'propertychange' 10748 : 'keyup' 10749 ; 10750 return inputEvent; 10751 }, 10752 value: function() { 10753 return $prompt.val(); 10754 }, 10755 results: function() { 10756 var 10757 results = $module.data(metadata.results) 10758 ; 10759 return results; 10760 }, 10761 result: function(value, results) { 10762 var 10763 result = false 10764 ; 10765 value = (value !== undefined) 10766 ? value 10767 : module.get.value() 10768 ; 10769 results = (results !== undefined) 10770 ? results 10771 : module.get.results() 10772 ; 10773 if(settings.type === 'category') { 10774 module.debug('Finding result that matches', value); 10775 $.each(results, function(index, category) { 10776 if(Array.isArray(category.results)) { 10777 result = module.search.object(value, category.results)[0]; 10778 // don't continue searching if a result is found 10779 if(result) { 10780 return false; 10781 } 10782 } 10783 }); 10784 } 10785 else { 10786 module.debug('Finding result in results object', value); 10787 result = module.search.object(value, results)[0]; 10788 } 10789 return result || false; 10790 }, 10791 }, 10792 10793 select: { 10794 firstResult: function() { 10795 module.verbose('Selecting first result'); 10796 $result.first().addClass(className.active); 10797 } 10798 }, 10799 10800 set: { 10801 focus: function() { 10802 $module.addClass(className.focus); 10803 }, 10804 loading: function() { 10805 $module.addClass(className.loading); 10806 }, 10807 value: function(value) { 10808 module.verbose('Setting search input value', value); 10809 $prompt 10810 .val(value) 10811 ; 10812 }, 10813 type: function(type) { 10814 type = type || settings.type; 10815 if(settings.type == 'category') { 10816 $module.addClass(settings.type); 10817 } 10818 }, 10819 buttonPressed: function() { 10820 $searchButton.addClass(className.pressed); 10821 } 10822 }, 10823 10824 remove: { 10825 loading: function() { 10826 $module.removeClass(className.loading); 10827 }, 10828 focus: function() { 10829 $module.removeClass(className.focus); 10830 }, 10831 buttonPressed: function() { 10832 $searchButton.removeClass(className.pressed); 10833 }, 10834 diacritics: function(text) { 10835 return settings.ignoreDiacritics ? text.normalize('NFD').replace(/[\u0300-\u036f]/g, '') : text; 10836 } 10837 }, 10838 10839 query: function(callback) { 10840 callback = $.isFunction(callback) 10841 ? callback 10842 : function(){} 10843 ; 10844 var 10845 searchTerm = module.get.value(), 10846 cache = module.read.cache(searchTerm) 10847 ; 10848 callback = callback || function() {}; 10849 if( module.has.minimumCharacters() ) { 10850 if(cache) { 10851 module.debug('Reading result from cache', searchTerm); 10852 module.save.results(cache.results); 10853 module.addResults(cache.html); 10854 module.inject.id(cache.results); 10855 callback(); 10856 } 10857 else { 10858 module.debug('Querying for', searchTerm); 10859 if($.isPlainObject(settings.source) || Array.isArray(settings.source)) { 10860 module.search.local(searchTerm); 10861 callback(); 10862 } 10863 else if( module.can.useAPI() ) { 10864 module.search.remote(searchTerm, callback); 10865 } 10866 else { 10867 module.error(error.source); 10868 callback(); 10869 } 10870 } 10871 settings.onSearchQuery.call(element, searchTerm); 10872 } 10873 else { 10874 module.hideResults(); 10875 } 10876 }, 10877 10878 search: { 10879 local: function(searchTerm) { 10880 var 10881 results = module.search.object(searchTerm, settings.source), 10882 searchHTML 10883 ; 10884 module.set.loading(); 10885 module.save.results(results); 10886 module.debug('Returned full local search results', results); 10887 if(settings.maxResults > 0) { 10888 module.debug('Using specified max results', results); 10889 results = results.slice(0, settings.maxResults); 10890 } 10891 if(settings.type == 'category') { 10892 results = module.create.categoryResults(results); 10893 } 10894 searchHTML = module.generateResults({ 10895 results: results 10896 }); 10897 module.remove.loading(); 10898 module.addResults(searchHTML); 10899 module.inject.id(results); 10900 module.write.cache(searchTerm, { 10901 html : searchHTML, 10902 results : results 10903 }); 10904 }, 10905 remote: function(searchTerm, callback) { 10906 callback = $.isFunction(callback) 10907 ? callback 10908 : function(){} 10909 ; 10910 if($module.api('is loading')) { 10911 $module.api('abort'); 10912 } 10913 module.setup.api(searchTerm, callback); 10914 $module 10915 .api('query') 10916 ; 10917 }, 10918 object: function(searchTerm, source, searchFields) { 10919 searchTerm = module.remove.diacritics(String(searchTerm)); 10920 var 10921 results = [], 10922 exactResults = [], 10923 fuzzyResults = [], 10924 searchExp = searchTerm.replace(regExp.escape, '\\$&'), 10925 matchRegExp = new RegExp(regExp.beginsWith + searchExp, 'i'), 10926 10927 // avoid duplicates when pushing results 10928 addResult = function(array, result) { 10929 var 10930 notResult = ($.inArray(result, results) == -1), 10931 notFuzzyResult = ($.inArray(result, fuzzyResults) == -1), 10932 notExactResults = ($.inArray(result, exactResults) == -1) 10933 ; 10934 if(notResult && notFuzzyResult && notExactResults) { 10935 array.push(result); 10936 } 10937 } 10938 ; 10939 source = source || settings.source; 10940 searchFields = (searchFields !== undefined) 10941 ? searchFields 10942 : settings.searchFields 10943 ; 10944 10945 // search fields should be array to loop correctly 10946 if(!Array.isArray(searchFields)) { 10947 searchFields = [searchFields]; 10948 } 10949 10950 // exit conditions if no source 10951 if(source === undefined || source === false) { 10952 module.error(error.source); 10953 return []; 10954 } 10955 // iterate through search fields looking for matches 10956 $.each(searchFields, function(index, field) { 10957 $.each(source, function(label, content) { 10958 var 10959 fieldExists = (typeof content[field] == 'string') || (typeof content[field] == 'number') 10960 ; 10961 if(fieldExists) { 10962 var text; 10963 if (typeof content[field] === 'string'){ 10964 text = module.remove.diacritics(content[field]); 10965 } else { 10966 text = content[field].toString(); 10967 } 10968 if( text.search(matchRegExp) !== -1) { 10969 // content starts with value (first in results) 10970 addResult(results, content); 10971 } 10972 else if(settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, text) ) { 10973 // content fuzzy matches (last in results) 10974 addResult(exactResults, content); 10975 } 10976 else if(settings.fullTextSearch == true && module.fuzzySearch(searchTerm, text) ) { 10977 // content fuzzy matches (last in results) 10978 addResult(fuzzyResults, content); 10979 } 10980 } 10981 }); 10982 }); 10983 $.merge(exactResults, fuzzyResults); 10984 $.merge(results, exactResults); 10985 return results; 10986 } 10987 }, 10988 exactSearch: function (query, term) { 10989 query = query.toLowerCase(); 10990 term = term.toLowerCase(); 10991 return term.indexOf(query) > -1; 10992 }, 10993 fuzzySearch: function(query, term) { 10994 var 10995 termLength = term.length, 10996 queryLength = query.length 10997 ; 10998 if(typeof query !== 'string') { 10999 return false; 11000 } 11001 query = query.toLowerCase(); 11002 term = term.toLowerCase(); 11003 if(queryLength > termLength) { 11004 return false; 11005 } 11006 if(queryLength === termLength) { 11007 return (query === term); 11008 } 11009 search: for (var characterIndex = 0, nextCharacterIndex = 0; characterIndex < queryLength; characterIndex++) { 11010 var 11011 queryCharacter = query.charCodeAt(characterIndex) 11012 ; 11013 while(nextCharacterIndex < termLength) { 11014 if(term.charCodeAt(nextCharacterIndex++) === queryCharacter) { 11015 continue search; 11016 } 11017 } 11018 return false; 11019 } 11020 return true; 11021 }, 11022 11023 parse: { 11024 response: function(response, searchTerm) { 11025 if(Array.isArray(response)){ 11026 var o={}; 11027 o[fields.results]=response; 11028 response = o; 11029 } 11030 var 11031 searchHTML = module.generateResults(response) 11032 ; 11033 module.verbose('Parsing server response', response); 11034 if(response !== undefined) { 11035 if(searchTerm !== undefined && response[fields.results] !== undefined) { 11036 module.addResults(searchHTML); 11037 module.inject.id(response[fields.results]); 11038 module.write.cache(searchTerm, { 11039 html : searchHTML, 11040 results : response[fields.results] 11041 }); 11042 module.save.results(response[fields.results]); 11043 } 11044 } 11045 } 11046 }, 11047 11048 cancel: { 11049 query: function() { 11050 if( module.can.useAPI() ) { 11051 $module.api('abort'); 11052 } 11053 } 11054 }, 11055 11056 has: { 11057 minimumCharacters: function() { 11058 var 11059 searchTerm = module.get.value(), 11060 numCharacters = searchTerm.length 11061 ; 11062 return (numCharacters >= settings.minCharacters); 11063 }, 11064 results: function() { 11065 if($results.length === 0) { 11066 return false; 11067 } 11068 var 11069 html = $results.html() 11070 ; 11071 return html != ''; 11072 } 11073 }, 11074 11075 clear: { 11076 cache: function(value) { 11077 var 11078 cache = $module.data(metadata.cache) 11079 ; 11080 if(!value) { 11081 module.debug('Clearing cache', value); 11082 $module.removeData(metadata.cache); 11083 } 11084 else if(value && cache && cache[value]) { 11085 module.debug('Removing value from cache', value); 11086 delete cache[value]; 11087 $module.data(metadata.cache, cache); 11088 } 11089 } 11090 }, 11091 11092 read: { 11093 cache: function(name) { 11094 var 11095 cache = $module.data(metadata.cache) 11096 ; 11097 if(settings.cache) { 11098 module.verbose('Checking cache for generated html for query', name); 11099 return (typeof cache == 'object') && (cache[name] !== undefined) 11100 ? cache[name] 11101 : false 11102 ; 11103 } 11104 return false; 11105 } 11106 }, 11107 11108 create: { 11109 categoryResults: function(results) { 11110 var 11111 categoryResults = {} 11112 ; 11113 $.each(results, function(index, result) { 11114 if(!result.category) { 11115 return; 11116 } 11117 if(categoryResults[result.category] === undefined) { 11118 module.verbose('Creating new category of results', result.category); 11119 categoryResults[result.category] = { 11120 name : result.category, 11121 results : [result] 11122 }; 11123 } 11124 else { 11125 categoryResults[result.category].results.push(result); 11126 } 11127 }); 11128 return categoryResults; 11129 }, 11130 id: function(resultIndex, categoryIndex) { 11131 var 11132 resultID = (resultIndex + 1), // not zero indexed 11133 letterID, 11134 id 11135 ; 11136 if(categoryIndex !== undefined) { 11137 // start char code for "A" 11138 letterID = String.fromCharCode(97 + categoryIndex); 11139 id = letterID + resultID; 11140 module.verbose('Creating category result id', id); 11141 } 11142 else { 11143 id = resultID; 11144 module.verbose('Creating result id', id); 11145 } 11146 return id; 11147 }, 11148 results: function() { 11149 if($results.length === 0) { 11150 $results = $('<div />') 11151 .addClass(className.results) 11152 .appendTo($module) 11153 ; 11154 } 11155 } 11156 }, 11157 11158 inject: { 11159 result: function(result, resultIndex, categoryIndex) { 11160 module.verbose('Injecting result into results'); 11161 var 11162 $selectedResult = (categoryIndex !== undefined) 11163 ? $results 11164 .children().eq(categoryIndex) 11165 .children(selector.results) 11166 .first() 11167 .children(selector.result) 11168 .eq(resultIndex) 11169 : $results 11170 .children(selector.result).eq(resultIndex) 11171 ; 11172 module.verbose('Injecting results metadata', $selectedResult); 11173 $selectedResult 11174 .data(metadata.result, result) 11175 ; 11176 }, 11177 id: function(results) { 11178 module.debug('Injecting unique ids into results'); 11179 var 11180 // since results may be object, we must use counters 11181 categoryIndex = 0, 11182 resultIndex = 0 11183 ; 11184 if(settings.type === 'category') { 11185 // iterate through each category result 11186 $.each(results, function(index, category) { 11187 if(category.results.length > 0){ 11188 resultIndex = 0; 11189 $.each(category.results, function(index, result) { 11190 if(result.id === undefined) { 11191 result.id = module.create.id(resultIndex, categoryIndex); 11192 } 11193 module.inject.result(result, resultIndex, categoryIndex); 11194 resultIndex++; 11195 }); 11196 categoryIndex++; 11197 } 11198 }); 11199 } 11200 else { 11201 // top level 11202 $.each(results, function(index, result) { 11203 if(result.id === undefined) { 11204 result.id = module.create.id(resultIndex); 11205 } 11206 module.inject.result(result, resultIndex); 11207 resultIndex++; 11208 }); 11209 } 11210 return results; 11211 } 11212 }, 11213 11214 save: { 11215 results: function(results) { 11216 module.verbose('Saving current search results to metadata', results); 11217 $module.data(metadata.results, results); 11218 } 11219 }, 11220 11221 write: { 11222 cache: function(name, value) { 11223 var 11224 cache = ($module.data(metadata.cache) !== undefined) 11225 ? $module.data(metadata.cache) 11226 : {} 11227 ; 11228 if(settings.cache) { 11229 module.verbose('Writing generated html to cache', name, value); 11230 cache[name] = value; 11231 $module 11232 .data(metadata.cache, cache) 11233 ; 11234 } 11235 } 11236 }, 11237 11238 addResults: function(html) { 11239 if( $.isFunction(settings.onResultsAdd) ) { 11240 if( settings.onResultsAdd.call($results, html) === false ) { 11241 module.debug('onResultsAdd callback cancelled default action'); 11242 return false; 11243 } 11244 } 11245 if(html) { 11246 $results 11247 .html(html) 11248 ; 11249 module.refreshResults(); 11250 if(settings.selectFirstResult) { 11251 module.select.firstResult(); 11252 } 11253 module.showResults(); 11254 } 11255 else { 11256 module.hideResults(function() { 11257 $results.empty(); 11258 }); 11259 } 11260 }, 11261 11262 showResults: function(callback) { 11263 callback = $.isFunction(callback) 11264 ? callback 11265 : function(){} 11266 ; 11267 if(resultsDismissed) { 11268 return; 11269 } 11270 if(!module.is.visible() && module.has.results()) { 11271 if( module.can.transition() ) { 11272 module.debug('Showing results with css animations'); 11273 $results 11274 .transition({ 11275 animation : settings.transition + ' in', 11276 debug : settings.debug, 11277 verbose : settings.verbose, 11278 duration : settings.duration, 11279 onShow : function() { 11280 var $firstResult = $module.find(selector.result).eq(0); 11281 if($firstResult.length > 0) { 11282 module.ensureVisible($firstResult); 11283 } 11284 }, 11285 onComplete : function() { 11286 callback(); 11287 }, 11288 queue : true 11289 }) 11290 ; 11291 } 11292 else { 11293 module.debug('Showing results with javascript'); 11294 $results 11295 .stop() 11296 .fadeIn(settings.duration, settings.easing) 11297 ; 11298 } 11299 settings.onResultsOpen.call($results); 11300 } 11301 }, 11302 hideResults: function(callback) { 11303 callback = $.isFunction(callback) 11304 ? callback 11305 : function(){} 11306 ; 11307 if( module.is.visible() ) { 11308 if( module.can.transition() ) { 11309 module.debug('Hiding results with css animations'); 11310 $results 11311 .transition({ 11312 animation : settings.transition + ' out', 11313 debug : settings.debug, 11314 verbose : settings.verbose, 11315 duration : settings.duration, 11316 onComplete : function() { 11317 callback(); 11318 }, 11319 queue : true 11320 }) 11321 ; 11322 } 11323 else { 11324 module.debug('Hiding results with javascript'); 11325 $results 11326 .stop() 11327 .fadeOut(settings.duration, settings.easing) 11328 ; 11329 } 11330 settings.onResultsClose.call($results); 11331 } 11332 }, 11333 11334 generateResults: function(response) { 11335 module.debug('Generating html from response', response); 11336 var 11337 template = settings.templates[settings.type], 11338 isProperObject = ($.isPlainObject(response[fields.results]) && !$.isEmptyObject(response[fields.results])), 11339 isProperArray = (Array.isArray(response[fields.results]) && response[fields.results].length > 0), 11340 html = '' 11341 ; 11342 if(isProperObject || isProperArray ) { 11343 if(settings.maxResults > 0) { 11344 if(isProperObject) { 11345 if(settings.type == 'standard') { 11346 module.error(error.maxResults); 11347 } 11348 } 11349 else { 11350 response[fields.results] = response[fields.results].slice(0, settings.maxResults); 11351 } 11352 } 11353 if($.isFunction(template)) { 11354 html = template(response, fields, settings.preserveHTML); 11355 } 11356 else { 11357 module.error(error.noTemplate, false); 11358 } 11359 } 11360 else if(settings.showNoResults) { 11361 html = module.displayMessage(error.noResults, 'empty', error.noResultsHeader); 11362 } 11363 settings.onResults.call(element, response); 11364 return html; 11365 }, 11366 11367 displayMessage: function(text, type, header) { 11368 type = type || 'standard'; 11369 module.debug('Displaying message', text, type, header); 11370 module.addResults( settings.templates.message(text, type, header) ); 11371 return settings.templates.message(text, type, header); 11372 }, 11373 11374 setting: function(name, value) { 11375 if( $.isPlainObject(name) ) { 11376 $.extend(true, settings, name); 11377 } 11378 else if(value !== undefined) { 11379 settings[name] = value; 11380 } 11381 else { 11382 return settings[name]; 11383 } 11384 }, 11385 internal: function(name, value) { 11386 if( $.isPlainObject(name) ) { 11387 $.extend(true, module, name); 11388 } 11389 else if(value !== undefined) { 11390 module[name] = value; 11391 } 11392 else { 11393 return module[name]; 11394 } 11395 }, 11396 debug: function() { 11397 if(!settings.silent && settings.debug) { 11398 if(settings.performance) { 11399 module.performance.log(arguments); 11400 } 11401 else { 11402 module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); 11403 module.debug.apply(console, arguments); 11404 } 11405 } 11406 }, 11407 verbose: function() { 11408 if(!settings.silent && settings.verbose && settings.debug) { 11409 if(settings.performance) { 11410 module.performance.log(arguments); 11411 } 11412 else { 11413 module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); 11414 module.verbose.apply(console, arguments); 11415 } 11416 } 11417 }, 11418 error: function() { 11419 if(!settings.silent) { 11420 module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); 11421 module.error.apply(console, arguments); 11422 } 11423 }, 11424 performance: { 11425 log: function(message) { 11426 var 11427 currentTime, 11428 executionTime, 11429 previousTime 11430 ; 11431 if(settings.performance) { 11432 currentTime = new Date().getTime(); 11433 previousTime = time || currentTime; 11434 executionTime = currentTime - previousTime; 11435 time = currentTime; 11436 performance.push({ 11437 'Name' : message[0], 11438 'Arguments' : [].slice.call(message, 1) || '', 11439 'Element' : element, 11440 'Execution Time' : executionTime 11441 }); 11442 } 11443 clearTimeout(module.performance.timer); 11444 module.performance.timer = setTimeout(module.performance.display, 500); 11445 }, 11446 display: function() { 11447 var 11448 title = settings.name + ':', 11449 totalTime = 0 11450 ; 11451 time = false; 11452 clearTimeout(module.performance.timer); 11453 $.each(performance, function(index, data) { 11454 totalTime += data['Execution Time']; 11455 }); 11456 title += ' ' + totalTime + 'ms'; 11457 if(moduleSelector) { 11458 title += ' \'' + moduleSelector + '\''; 11459 } 11460 if($allModules.length > 1) { 11461 title += ' ' + '(' + $allModules.length + ')'; 11462 } 11463 if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { 11464 console.groupCollapsed(title); 11465 if(console.table) { 11466 console.table(performance); 11467 } 11468 else { 11469 $.each(performance, function(index, data) { 11470 console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); 11471 }); 11472 } 11473 console.groupEnd(); 11474 } 11475 performance = []; 11476 } 11477 }, 11478 invoke: function(query, passedArguments, context) { 11479 var 11480 object = instance, 11481 maxDepth, 11482 found, 11483 response 11484 ; 11485 passedArguments = passedArguments || queryArguments; 11486 context = element || context; 11487 if(typeof query == 'string' && object !== undefined) { 11488 query = query.split(/[\. ]/); 11489 maxDepth = query.length - 1; 11490 $.each(query, function(depth, value) { 11491 var camelCaseValue = (depth != maxDepth) 11492 ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) 11493 : query 11494 ; 11495 if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { 11496 object = object[camelCaseValue]; 11497 } 11498 else if( object[camelCaseValue] !== undefined ) { 11499 found = object[camelCaseValue]; 11500 return false; 11501 } 11502 else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { 11503 object = object[value]; 11504 } 11505 else if( object[value] !== undefined ) { 11506 found = object[value]; 11507 return false; 11508 } 11509 else { 11510 return false; 11511 } 11512 }); 11513 } 11514 if( $.isFunction( found ) ) { 11515 response = found.apply(context, passedArguments); 11516 } 11517 else if(found !== undefined) { 11518 response = found; 11519 } 11520 if(Array.isArray(returnedValue)) { 11521 returnedValue.push(response); 11522 } 11523 else if(returnedValue !== undefined) { 11524 returnedValue = [returnedValue, response]; 11525 } 11526 else if(response !== undefined) { 11527 returnedValue = response; 11528 } 11529 return found; 11530 } 11531 }; 11532 if(methodInvoked) { 11533 if(instance === undefined) { 11534 module.initialize(); 11535 } 11536 module.invoke(query); 11537 } 11538 else { 11539 if(instance !== undefined) { 11540 instance.invoke('destroy'); 11541 } 11542 module.initialize(); 11543 } 11544 11545 }) 11546 ; 11547 11548 return (returnedValue !== undefined) 11549 ? returnedValue 11550 : this 11551 ; 11552 }; 11553 11554 $.fn.search.settings = { 11555 11556 name : 'Search', 11557 namespace : 'search', 11558 11559 silent : false, 11560 debug : false, 11561 verbose : false, 11562 performance : true, 11563 11564 // template to use (specified in settings.templates) 11565 type : 'standard', 11566 11567 // minimum characters required to search 11568 minCharacters : 1, 11569 11570 // whether to select first result after searching automatically 11571 selectFirstResult : false, 11572 11573 // API config 11574 apiSettings : false, 11575 11576 // object to search 11577 source : false, 11578 11579 // Whether search should query current term on focus 11580 searchOnFocus : true, 11581 11582 // fields to search 11583 searchFields : [ 11584 'id', 11585 'title', 11586 'description' 11587 ], 11588 11589 // field to display in standard results template 11590 displayField : '', 11591 11592 // search anywhere in value (set to 'exact' to require exact matches 11593 fullTextSearch : 'exact', 11594 11595 // match results also if they contain diacritics of the same base character (for example searching for "a" will also match "á" or "â" or "à ", etc...) 11596 ignoreDiacritics : false, 11597 11598 // whether to add events to prompt automatically 11599 automatic : true, 11600 11601 // delay before hiding menu after blur 11602 hideDelay : 0, 11603 11604 // delay before searching 11605 searchDelay : 200, 11606 11607 // maximum results returned from search 11608 maxResults : 7, 11609 11610 // whether to store lookups in local cache 11611 cache : true, 11612 11613 // whether no results errors should be shown 11614 showNoResults : true, 11615 11616 // preserve possible html of resultset values 11617 preserveHTML : true, 11618 11619 // transition settings 11620 transition : 'scale', 11621 duration : 200, 11622 easing : 'easeOutExpo', 11623 11624 // callbacks 11625 onSelect : false, 11626 onResultsAdd : false, 11627 11628 onSearchQuery : function(query){}, 11629 onResults : function(response){}, 11630 11631 onResultsOpen : function(){}, 11632 onResultsClose : function(){}, 11633 11634 className: { 11635 animating : 'animating', 11636 active : 'active', 11637 empty : 'empty', 11638 focus : 'focus', 11639 hidden : 'hidden', 11640 loading : 'loading', 11641 results : 'results', 11642 pressed : 'down' 11643 }, 11644 11645 error : { 11646 source : 'Cannot search. No source used, and Semantic API module was not included', 11647 noResultsHeader : 'No Results', 11648 noResults : 'Your search returned no results', 11649 logging : 'Error in debug logging, exiting.', 11650 noEndpoint : 'No search endpoint was specified', 11651 noTemplate : 'A valid template name was not specified.', 11652 oldSearchSyntax : 'searchFullText setting has been renamed fullTextSearch for consistency, please adjust your settings.', 11653 serverError : 'There was an issue querying the server.', 11654 maxResults : 'Results must be an array to use maxResults setting', 11655 method : 'The method you called is not defined.', 11656 noNormalize : '"ignoreDiacritics" setting will be ignored. Browser does not support String().normalize(). You may consider including <https://cdn.jsdelivr.net/npm/unorm@1.4.1/lib/unorm.min.js> as a polyfill.' 11657 }, 11658 11659 metadata: { 11660 cache : 'cache', 11661 results : 'results', 11662 result : 'result' 11663 }, 11664 11665 regExp: { 11666 escape : /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, 11667 beginsWith : '(?:\s|^)' 11668 }, 11669 11670 // maps api response attributes to internal representation 11671 fields: { 11672 categories : 'results', // array of categories (category view) 11673 categoryName : 'name', // name of category (category view) 11674 categoryResults : 'results', // array of results (category view) 11675 description : 'description', // result description 11676 image : 'image', // result image 11677 price : 'price', // result price 11678 results : 'results', // array of results (standard) 11679 title : 'title', // result title 11680 url : 'url', // result url 11681 action : 'action', // "view more" object name 11682 actionText : 'text', // "view more" text 11683 actionURL : 'url' // "view more" url 11684 }, 11685 11686 selector : { 11687 prompt : '.prompt', 11688 searchButton : '.search.button', 11689 results : '.results', 11690 message : '.results > .message', 11691 category : '.category', 11692 result : '.result', 11693 title : '.title, .name' 11694 }, 11695 11696 templates: { 11697 escape: function(string, preserveHTML) { 11698 if (preserveHTML){ 11699 return string; 11700 } 11701 var 11702 badChars = /[<>"'`]/g, 11703 shouldEscape = /[&<>"'`]/, 11704 escape = { 11705 "<": "<", 11706 ">": ">", 11707 '"': """, 11708 "'": "'", 11709 "`": "`" 11710 }, 11711 escapedChar = function(chr) { 11712 return escape[chr]; 11713 } 11714 ; 11715 if(shouldEscape.test(string)) { 11716 string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&"); 11717 return string.replace(badChars, escapedChar); 11718 } 11719 return string; 11720 }, 11721 message: function(message, type, header) { 11722 var 11723 html = '' 11724 ; 11725 if(message !== undefined && type !== undefined) { 11726 html += '' 11727 + '<div class="message ' + type + '">' 11728 ; 11729 if(header) { 11730 html += '' 11731 + '<div class="header">' + header + '</div>' 11732 ; 11733 } 11734 html += ' <div class="description">' + message + '</div>'; 11735 html += '</div>'; 11736 } 11737 return html; 11738 }, 11739 category: function(response, fields, preserveHTML) { 11740 var 11741 html = '', 11742 escape = $.fn.search.settings.templates.escape 11743 ; 11744 if(response[fields.categoryResults] !== undefined) { 11745 11746 // each category 11747 $.each(response[fields.categoryResults], function(index, category) { 11748 if(category[fields.results] !== undefined && category.results.length > 0) { 11749 11750 html += '<div class="category">'; 11751 11752 if(category[fields.categoryName] !== undefined) { 11753 html += '<div class="name">' + escape(category[fields.categoryName], preserveHTML) + '</div>'; 11754 } 11755 11756 // each item inside category 11757 html += '<div class="results">'; 11758 $.each(category.results, function(index, result) { 11759 if(result[fields.url]) { 11760 html += '<a class="result" href="' + result[fields.url].replace(/"/g,"") + '">'; 11761 } 11762 else { 11763 html += '<a class="result">'; 11764 } 11765 if(result[fields.image] !== undefined) { 11766 html += '' 11767 + '<div class="image">' 11768 + ' <img src="' + result[fields.image].replace(/"/g,"") + '">' 11769 + '</div>' 11770 ; 11771 } 11772 html += '<div class="content">'; 11773 if(result[fields.price] !== undefined) { 11774 html += '<div class="price">' + escape(result[fields.price], preserveHTML) + '</div>'; 11775 } 11776 if(result[fields.title] !== undefined) { 11777 html += '<div class="title">' + escape(result[fields.title], preserveHTML) + '</div>'; 11778 } 11779 if(result[fields.description] !== undefined) { 11780 html += '<div class="description">' + escape(result[fields.description], preserveHTML) + '</div>'; 11781 } 11782 html += '' 11783 + '</div>' 11784 ; 11785 html += '</a>'; 11786 }); 11787 html += '</div>'; 11788 html += '' 11789 + '</div>' 11790 ; 11791 } 11792 }); 11793 if(response[fields.action]) { 11794 if(fields.actionURL === false) { 11795 html += '' 11796 + '<div class="action">' 11797 + escape(response[fields.action][fields.actionText], preserveHTML) 11798 + '</div>'; 11799 } else { 11800 html += '' 11801 + '<a href="' + response[fields.action][fields.actionURL].replace(/"/g,"") + '" class="action">' 11802 + escape(response[fields.action][fields.actionText], preserveHTML) 11803 + '</a>'; 11804 } 11805 } 11806 return html; 11807 } 11808 return false; 11809 }, 11810 standard: function(response, fields, preserveHTML) { 11811 var 11812 html = '', 11813 escape = $.fn.search.settings.templates.escape 11814 ; 11815 if(response[fields.results] !== undefined) { 11816 11817 // each result 11818 $.each(response[fields.results], function(index, result) { 11819 if(result[fields.url]) { 11820 html += '<a class="result" href="' + result[fields.url].replace(/"/g,"") + '">'; 11821 } 11822 else { 11823 html += '<a class="result">'; 11824 } 11825 if(result[fields.image] !== undefined) { 11826 html += '' 11827 + '<div class="image">' 11828 + ' <img src="' + result[fields.image].replace(/"/g,"") + '">' 11829 + '</div>' 11830 ; 11831 } 11832 html += '<div class="content">'; 11833 if(result[fields.price] !== undefined) { 11834 html += '<div class="price">' + escape(result[fields.price], preserveHTML) + '</div>'; 11835 } 11836 if(result[fields.title] !== undefined) { 11837 html += '<div class="title">' + escape(result[fields.title], preserveHTML) + '</div>'; 11838 } 11839 if(result[fields.description] !== undefined) { 11840 html += '<div class="description">' + escape(result[fields.description], preserveHTML) + '</div>'; 11841 } 11842 html += '' 11843 + '</div>' 11844 ; 11845 html += '</a>'; 11846 }); 11847 if(response[fields.action]) { 11848 if(fields.actionURL === false) { 11849 html += '' 11850 + '<div class="action">' 11851 + escape(response[fields.action][fields.actionText], preserveHTML) 11852 + '</div>'; 11853 } else { 11854 html += '' 11855 + '<a href="' + response[fields.action][fields.actionURL].replace(/"/g,"") + '" class="action">' 11856 + escape(response[fields.action][fields.actionText], preserveHTML) 11857 + '</a>'; 11858 } 11859 } 11860 return html; 11861 } 11862 return false; 11863 } 11864 } 11865 }; 11866 11867 })( jQuery, window, document ); 11868 11869 /*! 11870 * # Fomantic-UI - Site 11871 * http://github.com/fomantic/Fomantic-UI/ 11872 * 11873 * 11874 * Released under the MIT license 11875 * http://opensource.org/licenses/MIT 11876 * 11877 */ 11878 11879 ;(function ($, window, document, undefined) { 11880 11881 $.isFunction = $.isFunction || function(obj) { 11882 return typeof obj === "function" && typeof obj.nodeType !== "number"; 11883 }; 11884 11885 $.site = $.fn.site = function(parameters) { 11886 var 11887 time = new Date().getTime(), 11888 performance = [], 11889 11890 query = arguments[0], 11891 methodInvoked = (typeof query == 'string'), 11892 queryArguments = [].slice.call(arguments, 1), 11893 11894 settings = ( $.isPlainObject(parameters) ) 11895 ? $.extend(true, {}, $.site.settings, parameters) 11896 : $.extend({}, $.site.settings), 11897 11898 namespace = settings.namespace, 11899 error = settings.error, 11900 11901 moduleNamespace = 'module-' + namespace, 11902 11903 $document = $(document), 11904 $module = $document, 11905 element = this, 11906 instance = $module.data(moduleNamespace), 11907 11908 module, 11909 returnedValue 11910 ; 11911 module = { 11912 11913 initialize: function() { 11914 module.instantiate(); 11915 }, 11916 11917 instantiate: function() { 11918 module.verbose('Storing instance of site', module); 11919 instance = module; 11920 $module 11921 .data(moduleNamespace, module) 11922 ; 11923 }, 11924 11925 normalize: function() { 11926 module.fix.console(); 11927 module.fix.requestAnimationFrame(); 11928 }, 11929 11930 fix: { 11931 console: function() { 11932 module.debug('Normalizing window.console'); 11933 if (console === undefined || console.log === undefined) { 11934 module.verbose('Console not available, normalizing events'); 11935 module.disable.console(); 11936 } 11937 if (typeof console.group == 'undefined' || typeof console.groupEnd == 'undefined' || typeof console.groupCollapsed == 'undefined') { 11938 module.verbose('Console group not available, normalizing events'); 11939 window.console.group = function() {}; 11940 window.console.groupEnd = function() {}; 11941 window.console.groupCollapsed = function() {}; 11942 } 11943 if (typeof console.markTimeline == 'undefined') { 11944 module.verbose('Mark timeline not available, normalizing events'); 11945 window.console.markTimeline = function() {}; 11946 } 11947 }, 11948 consoleClear: function() { 11949 module.debug('Disabling programmatic console clearing'); 11950 window.console.clear = function() {}; 11951 }, 11952 requestAnimationFrame: function() { 11953 module.debug('Normalizing requestAnimationFrame'); 11954 if(window.requestAnimationFrame === undefined) { 11955 module.debug('RequestAnimationFrame not available, normalizing event'); 11956 window.requestAnimationFrame = window.requestAnimationFrame 11957 || window.mozRequestAnimationFrame 11958 || window.webkitRequestAnimationFrame 11959 || window.msRequestAnimationFrame 11960 || function(callback) { setTimeout(callback, 0); } 11961 ; 11962 } 11963 } 11964 }, 11965 11966 moduleExists: function(name) { 11967 return ($.fn[name] !== undefined && $.fn[name].settings !== undefined); 11968 }, 11969 11970 enabled: { 11971 modules: function(modules) { 11972 var 11973 enabledModules = [] 11974 ; 11975 modules = modules || settings.modules; 11976 $.each(modules, function(index, name) { 11977 if(module.moduleExists(name)) { 11978 enabledModules.push(name); 11979 } 11980 }); 11981 return enabledModules; 11982 } 11983 }, 11984 11985 disabled: { 11986 modules: function(modules) { 11987 var 11988 disabledModules = [] 11989 ; 11990 modules = modules || settings.modules; 11991 $.each(modules, function(index, name) { 11992 if(!module.moduleExists(name)) { 11993 disabledModules.push(name); 11994 } 11995 }); 11996 return disabledModules; 11997 } 11998 }, 11999 12000 change: { 12001 setting: function(setting, value, modules, modifyExisting) { 12002 modules = (typeof modules === 'string') 12003 ? (modules === 'all') 12004 ? settings.modules 12005 : [modules] 12006 : modules || settings.modules 12007 ; 12008 modifyExisting = (modifyExisting !== undefined) 12009 ? modifyExisting 12010 : true 12011 ; 12012 $.each(modules, function(index, name) { 12013 var 12014 namespace = (module.moduleExists(name)) 12015 ? $.fn[name].settings.namespace || false 12016 : true, 12017 $existingModules 12018 ; 12019 if(module.moduleExists(name)) { 12020 module.verbose('Changing default setting', setting, value, name); 12021 $.fn[name].settings[setting] = value; 12022 if(modifyExisting && namespace) { 12023 $existingModules = $(':data(module-' + namespace + ')'); 12024 if($existingModules.length > 0) { 12025 module.verbose('Modifying existing settings', $existingModules); 12026 $existingModules[name]('setting', setting, value); 12027 } 12028 } 12029 } 12030 }); 12031 }, 12032 settings: function(newSettings, modules, modifyExisting) { 12033 modules = (typeof modules === 'string') 12034 ? [modules] 12035 : modules || settings.modules 12036 ; 12037 modifyExisting = (modifyExisting !== undefined) 12038 ? modifyExisting 12039 : true 12040 ; 12041 $.each(modules, function(index, name) { 12042 var 12043 $existingModules 12044 ; 12045 if(module.moduleExists(name)) { 12046 module.verbose('Changing default setting', newSettings, name); 12047 $.extend(true, $.fn[name].settings, newSettings); 12048 if(modifyExisting && namespace) { 12049 $existingModules = $(':data(module-' + namespace + ')'); 12050 if($existingModules.length > 0) { 12051 module.verbose('Modifying existing settings', $existingModules); 12052 $existingModules[name]('setting', newSettings); 12053 } 12054 } 12055 } 12056 }); 12057 } 12058 }, 12059 12060 enable: { 12061 console: function() { 12062 module.console(true); 12063 }, 12064 debug: function(modules, modifyExisting) { 12065 modules = modules || settings.modules; 12066 module.debug('Enabling debug for modules', modules); 12067 module.change.setting('debug', true, modules, modifyExisting); 12068 }, 12069 verbose: function(modules, modifyExisting) { 12070 modules = modules || settings.modules; 12071 module.debug('Enabling verbose debug for modules', modules); 12072 module.change.setting('verbose', true, modules, modifyExisting); 12073 } 12074 }, 12075 disable: { 12076 console: function() { 12077 module.console(false); 12078 }, 12079 debug: function(modules, modifyExisting) { 12080 modules = modules || settings.modules; 12081 module.debug('Disabling debug for modules', modules); 12082 module.change.setting('debug', false, modules, modifyExisting); 12083 }, 12084 verbose: function(modules, modifyExisting) { 12085 modules = modules || settings.modules; 12086 module.debug('Disabling verbose debug for modules', modules); 12087 module.change.setting('verbose', false, modules, modifyExisting); 12088 } 12089 }, 12090 12091 console: function(enable) { 12092 if(enable) { 12093 if(instance.cache.console === undefined) { 12094 module.error(error.console); 12095 return; 12096 } 12097 module.debug('Restoring console function'); 12098 window.console = instance.cache.console; 12099 } 12100 else { 12101 module.debug('Disabling console function'); 12102 instance.cache.console = window.console; 12103 window.console = { 12104 clear : function(){}, 12105 error : function(){}, 12106 group : function(){}, 12107 groupCollapsed : function(){}, 12108 groupEnd : function(){}, 12109 info : function(){}, 12110 log : function(){}, 12111 markTimeline : function(){}, 12112 warn : function(){} 12113 }; 12114 } 12115 }, 12116 12117 destroy: function() { 12118 module.verbose('Destroying previous site for', $module); 12119 $module 12120 .removeData(moduleNamespace) 12121 ; 12122 }, 12123 12124 cache: {}, 12125 12126 setting: function(name, value) { 12127 if( $.isPlainObject(name) ) { 12128 $.extend(true, settings, name); 12129 } 12130 else if(value !== undefined) { 12131 settings[name] = value; 12132 } 12133 else { 12134 return settings[name]; 12135 } 12136 }, 12137 internal: function(name, value) { 12138 if( $.isPlainObject(name) ) { 12139 $.extend(true, module, name); 12140 } 12141 else if(value !== undefined) { 12142 module[name] = value; 12143 } 12144 else { 12145 return module[name]; 12146 } 12147 }, 12148 debug: function() { 12149 if(settings.debug) { 12150 if(settings.performance) { 12151 module.performance.log(arguments); 12152 } 12153 else { 12154 module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); 12155 module.debug.apply(console, arguments); 12156 } 12157 } 12158 }, 12159 verbose: function() { 12160 if(settings.verbose && settings.debug) { 12161 if(settings.performance) { 12162 module.performance.log(arguments); 12163 } 12164 else { 12165 module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); 12166 module.verbose.apply(console, arguments); 12167 } 12168 } 12169 }, 12170 error: function() { 12171 module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); 12172 module.error.apply(console, arguments); 12173 }, 12174 performance: { 12175 log: function(message) { 12176 var 12177 currentTime, 12178 executionTime, 12179 previousTime 12180 ; 12181 if(settings.performance) { 12182 currentTime = new Date().getTime(); 12183 previousTime = time || currentTime; 12184 executionTime = currentTime - previousTime; 12185 time = currentTime; 12186 performance.push({ 12187 'Element' : element, 12188 'Name' : message[0], 12189 'Arguments' : [].slice.call(message, 1) || '', 12190 'Execution Time' : executionTime 12191 }); 12192 } 12193 clearTimeout(module.performance.timer); 12194 module.performance.timer = setTimeout(module.performance.display, 500); 12195 }, 12196 display: function() { 12197 var 12198 title = settings.name + ':', 12199 totalTime = 0 12200 ; 12201 time = false; 12202 clearTimeout(module.performance.timer); 12203 $.each(performance, function(index, data) { 12204 totalTime += data['Execution Time']; 12205 }); 12206 title += ' ' + totalTime + 'ms'; 12207 if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { 12208 console.groupCollapsed(title); 12209 if(console.table) { 12210 console.table(performance); 12211 } 12212 else { 12213 $.each(performance, function(index, data) { 12214 console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); 12215 }); 12216 } 12217 console.groupEnd(); 12218 } 12219 performance = []; 12220 } 12221 }, 12222 invoke: function(query, passedArguments, context) { 12223 var 12224 object = instance, 12225 maxDepth, 12226 found, 12227 response 12228 ; 12229 passedArguments = passedArguments || queryArguments; 12230 context = element || context; 12231 if(typeof query == 'string' && object !== undefined) { 12232 query = query.split(/[\. ]/); 12233 maxDepth = query.length - 1; 12234 $.each(query, function(depth, value) { 12235 var camelCaseValue = (depth != maxDepth) 12236 ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) 12237 : query 12238 ; 12239 if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { 12240 object = object[camelCaseValue]; 12241 } 12242 else if( object[camelCaseValue] !== undefined ) { 12243 found = object[camelCaseValue]; 12244 return false; 12245 } 12246 else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { 12247 object = object[value]; 12248 } 12249 else if( object[value] !== undefined ) { 12250 found = object[value]; 12251 return false; 12252 } 12253 else { 12254 module.error(error.method, query); 12255 return false; 12256 } 12257 }); 12258 } 12259 if ( $.isFunction( found ) ) { 12260 response = found.apply(context, passedArguments); 12261 } 12262 else if(found !== undefined) { 12263 response = found; 12264 } 12265 if(Array.isArray(returnedValue)) { 12266 returnedValue.push(response); 12267 } 12268 else if(returnedValue !== undefined) { 12269 returnedValue = [returnedValue, response]; 12270 } 12271 else if(response !== undefined) { 12272 returnedValue = response; 12273 } 12274 return found; 12275 } 12276 }; 12277 12278 if(methodInvoked) { 12279 if(instance === undefined) { 12280 module.initialize(); 12281 } 12282 module.invoke(query); 12283 } 12284 else { 12285 if(instance !== undefined) { 12286 module.destroy(); 12287 } 12288 module.initialize(); 12289 } 12290 return (returnedValue !== undefined) 12291 ? returnedValue 12292 : this 12293 ; 12294 }; 12295 12296 $.site.settings = { 12297 12298 name : 'Site', 12299 namespace : 'site', 12300 12301 error : { 12302 console : 'Console cannot be restored, most likely it was overwritten outside of module', 12303 method : 'The method you called is not defined.' 12304 }, 12305 12306 debug : false, 12307 verbose : false, 12308 performance : true, 12309 12310 modules: [ 12311 'accordion', 12312 'api', 12313 'calendar', 12314 'checkbox', 12315 'dimmer', 12316 'dropdown', 12317 'embed', 12318 'form', 12319 'modal', 12320 'nag', 12321 'popup', 12322 'slider', 12323 'rating', 12324 'shape', 12325 'sidebar', 12326 'state', 12327 'sticky', 12328 'tab', 12329 'toast', 12330 'transition', 12331 'visibility', 12332 'visit' 12333 ], 12334 12335 siteNamespace : 'site', 12336 namespaceStub : { 12337 cache : {}, 12338 config : {}, 12339 sections : {}, 12340 section : {}, 12341 utilities : {} 12342 } 12343 12344 }; 12345 12346 // allows for selection of elements with data attributes 12347 $.extend($.expr[ ":" ], { 12348 data: ($.expr.createPseudo) 12349 ? $.expr.createPseudo(function(dataName) { 12350 return function(elem) { 12351 return !!$.data(elem, dataName); 12352 }; 12353 }) 12354 : function(elem, i, match) { 12355 // support: jQuery < 1.8 12356 return !!$.data(elem, match[ 3 ]); 12357 } 12358 }); 12359 12360 12361 })( jQuery, window, document ); 12362 12363 /*! 12364 * # Fomantic-UI - Tab 12365 * http://github.com/fomantic/Fomantic-UI/ 12366 * 12367 * 12368 * Released under the MIT license 12369 * http://opensource.org/licenses/MIT 12370 * 12371 */ 12372 12373 ;(function ($, window, document, undefined) { 12374 12375 'use strict'; 12376 12377 $.isWindow = $.isWindow || function(obj) { 12378 return obj != null && obj === obj.window; 12379 }; 12380 $.isFunction = $.isFunction || function(obj) { 12381 return typeof obj === "function" && typeof obj.nodeType !== "number"; 12382 }; 12383 12384 window = (typeof window != 'undefined' && window.Math == Math) 12385 ? window 12386 : (typeof self != 'undefined' && self.Math == Math) 12387 ? self 12388 : Function('return this')() 12389 ; 12390 12391 $.fn.tab = function(parameters) { 12392 12393 var 12394 // use window context if none specified 12395 $allModules = $.isFunction(this) 12396 ? $(window) 12397 : $(this), 12398 12399 moduleSelector = $allModules.selector || '', 12400 time = new Date().getTime(), 12401 performance = [], 12402 12403 query = arguments[0], 12404 methodInvoked = (typeof query == 'string'), 12405 queryArguments = [].slice.call(arguments, 1), 12406 12407 initializedHistory = false, 12408 returnedValue 12409 ; 12410 12411 $allModules 12412 .each(function() { 12413 var 12414 12415 settings = ( $.isPlainObject(parameters) ) 12416 ? $.extend(true, {}, $.fn.tab.settings, parameters) 12417 : $.extend({}, $.fn.tab.settings), 12418 12419 className = settings.className, 12420 metadata = settings.metadata, 12421 selector = settings.selector, 12422 error = settings.error, 12423 regExp = settings.regExp, 12424 12425 eventNamespace = '.' + settings.namespace, 12426 moduleNamespace = 'module-' + settings.namespace, 12427 12428 $module = $(this), 12429 $context, 12430 $tabs, 12431 12432 cache = {}, 12433 firstLoad = true, 12434 recursionDepth = 0, 12435 element = this, 12436 instance = $module.data(moduleNamespace), 12437 12438 activeTabPath, 12439 parameterArray, 12440 module, 12441 12442 historyEvent 12443 12444 ; 12445 12446 module = { 12447 12448 initialize: function() { 12449 module.debug('Initializing tab menu item', $module); 12450 module.fix.callbacks(); 12451 module.determineTabs(); 12452 12453 module.debug('Determining tabs', settings.context, $tabs); 12454 // set up automatic routing 12455 if(settings.auto) { 12456 module.set.auto(); 12457 } 12458 module.bind.events(); 12459 12460 if(settings.history && !initializedHistory) { 12461 module.initializeHistory(); 12462 initializedHistory = true; 12463 } 12464 12465 if(settings.autoTabActivation && instance === undefined && module.determine.activeTab() == null) { 12466 module.debug('No active tab detected, setting first tab active', module.get.initialPath()); 12467 module.changeTab(settings.autoTabActivation === true ? module.get.initialPath() : settings.autoTabActivation); 12468 }; 12469 12470 module.instantiate(); 12471 }, 12472 12473 instantiate: function () { 12474 module.verbose('Storing instance of module', module); 12475 instance = module; 12476 $module 12477 .data(moduleNamespace, module) 12478 ; 12479 }, 12480 12481 destroy: function() { 12482 module.debug('Destroying tabs', $module); 12483 $module 12484 .removeData(moduleNamespace) 12485 .off(eventNamespace) 12486 ; 12487 }, 12488 12489 bind: { 12490 events: function() { 12491 // if using $.tab don't add events 12492 if( !$.isWindow( element ) ) { 12493 module.debug('Attaching tab activation events to element', $module); 12494 $module 12495 .on('click' + eventNamespace, module.event.click) 12496 ; 12497 } 12498 } 12499 }, 12500 12501 determineTabs: function() { 12502 var 12503 $reference 12504 ; 12505 12506 // determine tab context 12507 if(settings.context === 'parent') { 12508 if($module.closest(selector.ui).length > 0) { 12509 $reference = $module.closest(selector.ui); 12510 module.verbose('Using closest UI element as parent', $reference); 12511 } 12512 else { 12513 $reference = $module; 12514 } 12515 $context = $reference.parent(); 12516 module.verbose('Determined parent element for creating context', $context); 12517 } 12518 else if(settings.context) { 12519 $context = $(settings.context); 12520 module.verbose('Using selector for tab context', settings.context, $context); 12521 } 12522 else { 12523 $context = $('body'); 12524 } 12525 // find tabs 12526 if(settings.childrenOnly) { 12527 $tabs = $context.children(selector.tabs); 12528 module.debug('Searching tab context children for tabs', $context, $tabs); 12529 } 12530 else { 12531 $tabs = $context.find(selector.tabs); 12532 module.debug('Searching tab context for tabs', $context, $tabs); 12533 } 12534 }, 12535 12536 fix: { 12537 callbacks: function() { 12538 if( $.isPlainObject(parameters) && (parameters.onTabLoad || parameters.onTabInit) ) { 12539 if(parameters.onTabLoad) { 12540 parameters.onLoad = parameters.onTabLoad; 12541 delete parameters.onTabLoad; 12542 module.error(error.legacyLoad, parameters.onLoad); 12543 } 12544 if(parameters.onTabInit) { 12545 parameters.onFirstLoad = parameters.onTabInit; 12546 delete parameters.onTabInit; 12547 module.error(error.legacyInit, parameters.onFirstLoad); 12548 } 12549 settings = $.extend(true, {}, $.fn.tab.settings, parameters); 12550 } 12551 } 12552 }, 12553 12554 initializeHistory: function() { 12555 module.debug('Initializing page state'); 12556 if( $.address === undefined ) { 12557 module.error(error.state); 12558 return false; 12559 } 12560 else { 12561 if(settings.historyType == 'state') { 12562 module.debug('Using HTML5 to manage state'); 12563 if(settings.path !== false) { 12564 $.address 12565 .history(true) 12566 .state(settings.path) 12567 ; 12568 } 12569 else { 12570 module.error(error.path); 12571 return false; 12572 } 12573 } 12574 $.address 12575 .bind('change', module.event.history.change) 12576 ; 12577 } 12578 }, 12579 12580 event: { 12581 click: function(event) { 12582 var 12583 tabPath = $(this).data(metadata.tab) 12584 ; 12585 if(tabPath !== undefined) { 12586 if(settings.history) { 12587 module.verbose('Updating page state', event); 12588 $.address.value(tabPath); 12589 } 12590 else { 12591 module.verbose('Changing tab', event); 12592 module.changeTab(tabPath); 12593 } 12594 event.preventDefault(); 12595 } 12596 else { 12597 module.debug('No tab specified'); 12598 } 12599 }, 12600 history: { 12601 change: function(event) { 12602 var 12603 tabPath = event.pathNames.join('/') || module.get.initialPath(), 12604 pageTitle = settings.templates.determineTitle(tabPath) || false 12605 ; 12606 module.performance.display(); 12607 module.debug('History change event', tabPath, event); 12608 historyEvent = event; 12609 if(tabPath !== undefined) { 12610 module.changeTab(tabPath); 12611 } 12612 if(pageTitle) { 12613 $.address.title(pageTitle); 12614 } 12615 } 12616 } 12617 }, 12618 12619 refresh: function() { 12620 if(activeTabPath) { 12621 module.debug('Refreshing tab', activeTabPath); 12622 module.changeTab(activeTabPath); 12623 } 12624 }, 12625 12626 cache: { 12627 12628 read: function(cacheKey) { 12629 return (cacheKey !== undefined) 12630 ? cache[cacheKey] 12631 : false 12632 ; 12633 }, 12634 add: function(cacheKey, content) { 12635 cacheKey = cacheKey || activeTabPath; 12636 module.debug('Adding cached content for', cacheKey); 12637 cache[cacheKey] = content; 12638 }, 12639 remove: function(cacheKey) { 12640 cacheKey = cacheKey || activeTabPath; 12641 module.debug('Removing cached content for', cacheKey); 12642 delete cache[cacheKey]; 12643 } 12644 }, 12645 12646 escape: { 12647 string: function(text) { 12648 text = String(text); 12649 return text.replace(regExp.escape, '\\$&'); 12650 } 12651 }, 12652 12653 set: { 12654 auto: function() { 12655 var 12656 url = (typeof settings.path == 'string') 12657 ? settings.path.replace(/\/$/, '') + '/{$tab}' 12658 : '/{$tab}' 12659 ; 12660 module.verbose('Setting up automatic tab retrieval from server', url); 12661 if($.isPlainObject(settings.apiSettings)) { 12662 settings.apiSettings.url = url; 12663 } 12664 else { 12665 settings.apiSettings = { 12666 url: url 12667 }; 12668 } 12669 }, 12670 loading: function(tabPath) { 12671 var 12672 $tab = module.get.tabElement(tabPath), 12673 isLoading = $tab.hasClass(className.loading) 12674 ; 12675 if(!isLoading) { 12676 module.verbose('Setting loading state for', $tab); 12677 $tab 12678 .addClass(className.loading) 12679 .siblings($tabs) 12680 .removeClass(className.active + ' ' + className.loading) 12681 ; 12682 if($tab.length > 0) { 12683 settings.onRequest.call($tab[0], tabPath); 12684 } 12685 } 12686 }, 12687 state: function(state) { 12688 $.address.value(state); 12689 } 12690 }, 12691 12692 changeTab: function(tabPath) { 12693 var 12694 pushStateAvailable = (window.history && window.history.pushState), 12695 shouldIgnoreLoad = (pushStateAvailable && settings.ignoreFirstLoad && firstLoad), 12696 remoteContent = (settings.auto || $.isPlainObject(settings.apiSettings) ), 12697 // only add default path if not remote content 12698 pathArray = (remoteContent && !shouldIgnoreLoad) 12699 ? module.utilities.pathToArray(tabPath) 12700 : module.get.defaultPathArray(tabPath) 12701 ; 12702 tabPath = module.utilities.arrayToPath(pathArray); 12703 $.each(pathArray, function(index, tab) { 12704 var 12705 currentPathArray = pathArray.slice(0, index + 1), 12706 currentPath = module.utilities.arrayToPath(currentPathArray), 12707 12708 isTab = module.is.tab(currentPath), 12709 isLastIndex = (index + 1 == pathArray.length), 12710 12711 $tab = module.get.tabElement(currentPath), 12712 $anchor, 12713 nextPathArray, 12714 nextPath, 12715 isLastTab 12716 ; 12717 module.verbose('Looking for tab', tab); 12718 if(isTab) { 12719 module.verbose('Tab was found', tab); 12720 // scope up 12721 activeTabPath = currentPath; 12722 parameterArray = module.utilities.filterArray(pathArray, currentPathArray); 12723 12724 if(isLastIndex) { 12725 isLastTab = true; 12726 } 12727 else { 12728 nextPathArray = pathArray.slice(0, index + 2); 12729 nextPath = module.utilities.arrayToPath(nextPathArray); 12730 isLastTab = ( !module.is.tab(nextPath) ); 12731 if(isLastTab) { 12732 module.verbose('Tab parameters found', nextPathArray); 12733 } 12734 } 12735 if(isLastTab && remoteContent) { 12736 if(!shouldIgnoreLoad) { 12737 module.activate.navigation(currentPath); 12738 module.fetch.content(currentPath, tabPath); 12739 } 12740 else { 12741 module.debug('Ignoring remote content on first tab load', currentPath); 12742 firstLoad = false; 12743 module.cache.add(tabPath, $tab.html()); 12744 module.activate.all(currentPath); 12745 settings.onFirstLoad.call($tab[0], currentPath, parameterArray, historyEvent); 12746 settings.onLoad.call($tab[0], currentPath, parameterArray, historyEvent); 12747 } 12748 return false; 12749 } 12750 else { 12751 module.debug('Opened local tab', currentPath); 12752 module.activate.all(currentPath); 12753 if( !module.cache.read(currentPath) ) { 12754 module.cache.add(currentPath, true); 12755 module.debug('First time tab loaded calling tab init'); 12756 settings.onFirstLoad.call($tab[0], currentPath, parameterArray, historyEvent); 12757 } 12758 settings.onLoad.call($tab[0], currentPath, parameterArray, historyEvent); 12759 } 12760 12761 } 12762 else if(tabPath.search('/') == -1 && tabPath !== '') { 12763 // look for in page anchor 12764 tabPath = module.escape.string(tabPath); 12765 $anchor = $('#' + tabPath + ', a[name="' + tabPath + '"]'); 12766 currentPath = $anchor.closest('[data-tab]').data(metadata.tab); 12767 $tab = module.get.tabElement(currentPath); 12768 // if anchor exists use parent tab 12769 if($anchor && $anchor.length > 0 && currentPath) { 12770 module.debug('Anchor link used, opening parent tab', $tab, $anchor); 12771 if( !$tab.hasClass(className.active) ) { 12772 setTimeout(function() { 12773 module.scrollTo($anchor); 12774 }, 0); 12775 } 12776 module.activate.all(currentPath); 12777 if( !module.cache.read(currentPath) ) { 12778 module.cache.add(currentPath, true); 12779 module.debug('First time tab loaded calling tab init'); 12780 settings.onFirstLoad.call($tab[0], currentPath, parameterArray, historyEvent); 12781 } 12782 settings.onLoad.call($tab[0], currentPath, parameterArray, historyEvent); 12783 return false; 12784 } 12785 } 12786 else { 12787 module.error(error.missingTab, $module, $context, currentPath); 12788 return false; 12789 } 12790 }); 12791 }, 12792 12793 scrollTo: function($element) { 12794 var 12795 scrollOffset = ($element && $element.length > 0) 12796 ? $element.offset().top 12797 : false 12798 ; 12799 if(scrollOffset !== false) { 12800 module.debug('Forcing scroll to an in-page link in a hidden tab', scrollOffset, $element); 12801 $(document).scrollTop(scrollOffset); 12802 } 12803 }, 12804 12805 update: { 12806 content: function(tabPath, html, evaluateScripts) { 12807 var 12808 $tab = module.get.tabElement(tabPath), 12809 tab = $tab[0] 12810 ; 12811 evaluateScripts = (evaluateScripts !== undefined) 12812 ? evaluateScripts 12813 : settings.evaluateScripts 12814 ; 12815 if(typeof settings.cacheType == 'string' && settings.cacheType.toLowerCase() == 'dom' && typeof html !== 'string') { 12816 $tab 12817 .empty() 12818 .append($(html).clone(true)) 12819 ; 12820 } 12821 else { 12822 if(evaluateScripts) { 12823 module.debug('Updating HTML and evaluating inline scripts', tabPath, html); 12824 $tab.html(html); 12825 } 12826 else { 12827 module.debug('Updating HTML', tabPath, html); 12828 tab.innerHTML = html; 12829 } 12830 } 12831 } 12832 }, 12833 12834 fetch: { 12835 12836 content: function(tabPath, fullTabPath) { 12837 var 12838 $tab = module.get.tabElement(tabPath), 12839 apiSettings = { 12840 dataType : 'html', 12841 encodeParameters : false, 12842 on : 'now', 12843 cache : settings.alwaysRefresh, 12844 headers : { 12845 'X-Remote': true 12846 }, 12847 onSuccess : function(response) { 12848 if(settings.cacheType == 'response') { 12849 module.cache.add(fullTabPath, response); 12850 } 12851 module.update.content(tabPath, response); 12852 if(tabPath == activeTabPath) { 12853 module.debug('Content loaded', tabPath); 12854 module.activate.tab(tabPath); 12855 } 12856 else { 12857 module.debug('Content loaded in background', tabPath); 12858 } 12859 settings.onFirstLoad.call($tab[0], tabPath, parameterArray, historyEvent); 12860 settings.onLoad.call($tab[0], tabPath, parameterArray, historyEvent); 12861 12862 if(settings.loadOnce) { 12863 module.cache.add(fullTabPath, true); 12864 } 12865 else if(typeof settings.cacheType == 'string' && settings.cacheType.toLowerCase() == 'dom' && $tab.children().length > 0) { 12866 setTimeout(function() { 12867 var 12868 $clone = $tab.children().clone(true) 12869 ; 12870 $clone = $clone.not('script'); 12871 module.cache.add(fullTabPath, $clone); 12872 }, 0); 12873 } 12874 else { 12875 module.cache.add(fullTabPath, $tab.html()); 12876 } 12877 }, 12878 urlData: { 12879 tab: fullTabPath 12880 } 12881 }, 12882 request = $tab.api('get request') || false, 12883 existingRequest = ( request && request.state() === 'pending' ), 12884 requestSettings, 12885 cachedContent 12886 ; 12887 12888 fullTabPath = fullTabPath || tabPath; 12889 cachedContent = module.cache.read(fullTabPath); 12890 12891 12892 if(settings.cache && cachedContent) { 12893 module.activate.tab(tabPath); 12894 module.debug('Adding cached content', fullTabPath); 12895 if(!settings.loadOnce) { 12896 if(settings.evaluateScripts == 'once') { 12897 module.update.content(tabPath, cachedContent, false); 12898 } 12899 else { 12900 module.update.content(tabPath, cachedContent); 12901 } 12902 } 12903 settings.onLoad.call($tab[0], tabPath, parameterArray, historyEvent); 12904 } 12905 else if(existingRequest) { 12906 module.set.loading(tabPath); 12907 module.debug('Content is already loading', fullTabPath); 12908 } 12909 else if($.api !== undefined) { 12910 requestSettings = $.extend(true, {}, settings.apiSettings, apiSettings); 12911 module.debug('Retrieving remote content', fullTabPath, requestSettings); 12912 module.set.loading(tabPath); 12913 $tab.api(requestSettings); 12914 } 12915 else { 12916 module.error(error.api); 12917 } 12918 } 12919 }, 12920 12921 activate: { 12922 all: function(tabPath) { 12923 module.activate.tab(tabPath); 12924 module.activate.navigation(tabPath); 12925 }, 12926 tab: function(tabPath) { 12927 var 12928 $tab = module.get.tabElement(tabPath), 12929 $deactiveTabs = (settings.deactivate == 'siblings') 12930 ? $tab.siblings($tabs) 12931 : $tabs.not($tab), 12932 isActive = $tab.hasClass(className.active) 12933 ; 12934 module.verbose('Showing tab content for', $tab); 12935 if(!isActive) { 12936 $tab 12937 .addClass(className.active) 12938 ; 12939 $deactiveTabs 12940 .removeClass(className.active + ' ' + className.loading) 12941 ; 12942 if($tab.length > 0) { 12943 settings.onVisible.call($tab[0], tabPath); 12944 } 12945 } 12946 }, 12947 navigation: function(tabPath) { 12948 var 12949 $navigation = module.get.navElement(tabPath), 12950 $deactiveNavigation = (settings.deactivate == 'siblings') 12951 ? $navigation.siblings($allModules) 12952 : $allModules.not($navigation), 12953 isActive = $navigation.hasClass(className.active) 12954 ; 12955 module.verbose('Activating tab navigation for', $navigation, tabPath); 12956 if(!isActive) { 12957 $navigation 12958 .addClass(className.active) 12959 ; 12960 $deactiveNavigation 12961 .removeClass(className.active + ' ' + className.loading) 12962 ; 12963 } 12964 } 12965 }, 12966 12967 deactivate: { 12968 all: function() { 12969 module.deactivate.navigation(); 12970 module.deactivate.tabs(); 12971 }, 12972 navigation: function() { 12973 $allModules 12974 .removeClass(className.active) 12975 ; 12976 }, 12977 tabs: function() { 12978 $tabs 12979 .removeClass(className.active + ' ' + className.loading) 12980 ; 12981 } 12982 }, 12983 12984 is: { 12985 tab: function(tabName) { 12986 return (tabName !== undefined) 12987 ? ( module.get.tabElement(tabName).length > 0 ) 12988 : false 12989 ; 12990 } 12991 }, 12992 12993 get: { 12994 initialPath: function() { 12995 return $allModules.eq(0).data(metadata.tab) || $tabs.eq(0).data(metadata.tab); 12996 }, 12997 path: function() { 12998 return $.address.value(); 12999 }, 13000 // adds default tabs to tab path 13001 defaultPathArray: function(tabPath) { 13002 return module.utilities.pathToArray( module.get.defaultPath(tabPath) ); 13003 }, 13004 defaultPath: function(tabPath) { 13005 var 13006 $defaultNav = $allModules.filter('[data-' + metadata.tab + '^="' + module.escape.string(tabPath) + '/"]').eq(0), 13007 defaultTab = $defaultNav.data(metadata.tab) || false 13008 ; 13009 if( defaultTab ) { 13010 module.debug('Found default tab', defaultTab); 13011 if(recursionDepth < settings.maxDepth) { 13012 recursionDepth++; 13013 return module.get.defaultPath(defaultTab); 13014 } 13015 module.error(error.recursion); 13016 } 13017 else { 13018 module.debug('No default tabs found for', tabPath, $tabs); 13019 } 13020 recursionDepth = 0; 13021 return tabPath; 13022 }, 13023 navElement: function(tabPath) { 13024 tabPath = tabPath || activeTabPath; 13025 return $allModules.filter('[data-' + metadata.tab + '="' + module.escape.string(tabPath) + '"]'); 13026 }, 13027 tabElement: function(tabPath) { 13028 var 13029 $fullPathTab, 13030 $simplePathTab, 13031 tabPathArray, 13032 lastTab 13033 ; 13034 tabPath = tabPath || activeTabPath; 13035 tabPathArray = module.utilities.pathToArray(tabPath); 13036 lastTab = module.utilities.last(tabPathArray); 13037 $fullPathTab = $tabs.filter('[data-' + metadata.tab + '="' + module.escape.string(tabPath) + '"]'); 13038 $simplePathTab = $tabs.filter('[data-' + metadata.tab + '="' + module.escape.string(lastTab) + '"]'); 13039 return ($fullPathTab.length > 0) 13040 ? $fullPathTab 13041 : $simplePathTab 13042 ; 13043 }, 13044 tab: function() { 13045 return activeTabPath; 13046 } 13047 }, 13048 13049 determine: { 13050 activeTab: function() { 13051 var activeTab = null; 13052 13053 $tabs.each(function(_index, tab) { 13054 var $tab = $(tab); 13055 13056 if( $tab.hasClass(className.active) ) { 13057 var 13058 tabPath = $(this).data(metadata.tab), 13059 $anchor = $allModules.filter('[data-' + metadata.tab + '="' + module.escape.string(tabPath) + '"]') 13060 ; 13061 13062 if( $anchor.hasClass(className.active) ) { 13063 activeTab = tabPath; 13064 } 13065 } 13066 }); 13067 13068 return activeTab; 13069 } 13070 }, 13071 13072 utilities: { 13073 filterArray: function(keepArray, removeArray) { 13074 return $.grep(keepArray, function(keepValue) { 13075 return ( $.inArray(keepValue, removeArray) == -1); 13076 }); 13077 }, 13078 last: function(array) { 13079 return Array.isArray(array) 13080 ? array[ array.length - 1] 13081 : false 13082 ; 13083 }, 13084 pathToArray: function(pathName) { 13085 if(pathName === undefined) { 13086 pathName = activeTabPath; 13087 } 13088 return typeof pathName == 'string' 13089 ? pathName.split('/') 13090 : [pathName] 13091 ; 13092 }, 13093 arrayToPath: function(pathArray) { 13094 return Array.isArray(pathArray) 13095 ? pathArray.join('/') 13096 : false 13097 ; 13098 } 13099 }, 13100 13101 setting: function(name, value) { 13102 module.debug('Changing setting', name, value); 13103 if( $.isPlainObject(name) ) { 13104 $.extend(true, settings, name); 13105 } 13106 else if(value !== undefined) { 13107 if($.isPlainObject(settings[name])) { 13108 $.extend(true, settings[name], value); 13109 } 13110 else { 13111 settings[name] = value; 13112 } 13113 } 13114 else { 13115 return settings[name]; 13116 } 13117 }, 13118 internal: function(name, value) { 13119 if( $.isPlainObject(name) ) { 13120 $.extend(true, module, name); 13121 } 13122 else if(value !== undefined) { 13123 module[name] = value; 13124 } 13125 else { 13126 return module[name]; 13127 } 13128 }, 13129 debug: function() { 13130 if(!settings.silent && settings.debug) { 13131 if(settings.performance) { 13132 module.performance.log(arguments); 13133 } 13134 else { 13135 module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); 13136 module.debug.apply(console, arguments); 13137 } 13138 } 13139 }, 13140 verbose: function() { 13141 if(!settings.silent && settings.verbose && settings.debug) { 13142 if(settings.performance) { 13143 module.performance.log(arguments); 13144 } 13145 else { 13146 module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); 13147 module.verbose.apply(console, arguments); 13148 } 13149 } 13150 }, 13151 error: function() { 13152 if(!settings.silent) { 13153 module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); 13154 module.error.apply(console, arguments); 13155 } 13156 }, 13157 performance: { 13158 log: function(message) { 13159 var 13160 currentTime, 13161 executionTime, 13162 previousTime 13163 ; 13164 if(settings.performance) { 13165 currentTime = new Date().getTime(); 13166 previousTime = time || currentTime; 13167 executionTime = currentTime - previousTime; 13168 time = currentTime; 13169 performance.push({ 13170 'Name' : message[0], 13171 'Arguments' : [].slice.call(message, 1) || '', 13172 'Element' : element, 13173 'Execution Time' : executionTime 13174 }); 13175 } 13176 clearTimeout(module.performance.timer); 13177 module.performance.timer = setTimeout(module.performance.display, 500); 13178 }, 13179 display: function() { 13180 var 13181 title = settings.name + ':', 13182 totalTime = 0 13183 ; 13184 time = false; 13185 clearTimeout(module.performance.timer); 13186 $.each(performance, function(index, data) { 13187 totalTime += data['Execution Time']; 13188 }); 13189 title += ' ' + totalTime + 'ms'; 13190 if(moduleSelector) { 13191 title += ' \'' + moduleSelector + '\''; 13192 } 13193 if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { 13194 console.groupCollapsed(title); 13195 if(console.table) { 13196 console.table(performance); 13197 } 13198 else { 13199 $.each(performance, function(index, data) { 13200 console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); 13201 }); 13202 } 13203 console.groupEnd(); 13204 } 13205 performance = []; 13206 } 13207 }, 13208 invoke: function(query, passedArguments, context) { 13209 var 13210 object = instance, 13211 maxDepth, 13212 found, 13213 response 13214 ; 13215 passedArguments = passedArguments || queryArguments; 13216 context = element || context; 13217 if(typeof query == 'string' && object !== undefined) { 13218 query = query.split(/[\. ]/); 13219 maxDepth = query.length - 1; 13220 $.each(query, function(depth, value) { 13221 var camelCaseValue = (depth != maxDepth) 13222 ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) 13223 : query 13224 ; 13225 if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { 13226 object = object[camelCaseValue]; 13227 } 13228 else if( object[camelCaseValue] !== undefined ) { 13229 found = object[camelCaseValue]; 13230 return false; 13231 } 13232 else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { 13233 object = object[value]; 13234 } 13235 else if( object[value] !== undefined ) { 13236 found = object[value]; 13237 return false; 13238 } 13239 else { 13240 module.error(error.method, query); 13241 return false; 13242 } 13243 }); 13244 } 13245 if ( $.isFunction( found ) ) { 13246 response = found.apply(context, passedArguments); 13247 } 13248 else if(found !== undefined) { 13249 response = found; 13250 } 13251 if(Array.isArray(returnedValue)) { 13252 returnedValue.push(response); 13253 } 13254 else if(returnedValue !== undefined) { 13255 returnedValue = [returnedValue, response]; 13256 } 13257 else if(response !== undefined) { 13258 returnedValue = response; 13259 } 13260 return found; 13261 } 13262 }; 13263 if(methodInvoked) { 13264 if(instance === undefined) { 13265 module.initialize(); 13266 } 13267 module.invoke(query); 13268 } 13269 else { 13270 if(instance !== undefined) { 13271 instance.invoke('destroy'); 13272 } 13273 module.initialize(); 13274 } 13275 }) 13276 ; 13277 return (returnedValue !== undefined) 13278 ? returnedValue 13279 : this 13280 ; 13281 13282 }; 13283 13284 // shortcut for tabbed content with no defined navigation 13285 $.tab = function() { 13286 $(window).tab.apply(this, arguments); 13287 }; 13288 13289 $.fn.tab.settings = { 13290 13291 name : 'Tab', 13292 namespace : 'tab', 13293 13294 silent : false, 13295 debug : false, 13296 verbose : false, 13297 performance : true, 13298 13299 auto : false, // uses pjax style endpoints fetching content from same url with remote-content headers 13300 history : false, // use browser history 13301 historyType : 'hash', // #/ or html5 state 13302 path : false, // base path of url 13303 13304 context : false, // specify a context that tabs must appear inside 13305 childrenOnly : false, // use only tabs that are children of context 13306 maxDepth : 25, // max depth a tab can be nested 13307 13308 deactivate : 'siblings', // whether tabs should deactivate sibling menu elements or all elements initialized together 13309 13310 alwaysRefresh : false, // load tab content new every tab click 13311 cache : true, // cache the content requests to pull locally 13312 loadOnce : false, // Whether tab data should only be loaded once when using remote content 13313 cacheType : 'response', // Whether to cache exact response, or to html cache contents after scripts execute 13314 ignoreFirstLoad : false, // don't load remote content on first load 13315 13316 apiSettings : false, // settings for api call 13317 evaluateScripts : 'once', // whether inline scripts should be parsed (true/false/once). Once will not re-evaluate on cached content 13318 autoTabActivation: true, // whether a non existing active tab will auto activate the first available tab 13319 13320 onFirstLoad : function(tabPath, parameterArray, historyEvent) {}, // called first time loaded 13321 onLoad : function(tabPath, parameterArray, historyEvent) {}, // called on every load 13322 onVisible : function(tabPath, parameterArray, historyEvent) {}, // called every time tab visible 13323 onRequest : function(tabPath, parameterArray, historyEvent) {}, // called ever time a tab beings loading remote content 13324 13325 templates : { 13326 determineTitle: function(tabArray) {} // returns page title for path 13327 }, 13328 13329 error: { 13330 api : 'You attempted to load content without API module', 13331 method : 'The method you called is not defined', 13332 missingTab : 'Activated tab cannot be found. Tabs are case-sensitive.', 13333 noContent : 'The tab you specified is missing a content url.', 13334 path : 'History enabled, but no path was specified', 13335 recursion : 'Max recursive depth reached', 13336 legacyInit : 'onTabInit has been renamed to onFirstLoad in 2.0, please adjust your code.', 13337 legacyLoad : 'onTabLoad has been renamed to onLoad in 2.0. Please adjust your code', 13338 state : 'History requires Asual\'s Address library <https://github.com/asual/jquery-address>' 13339 }, 13340 13341 regExp : { 13342 escape : /[-[\]{}()*+?.,\\^$|#\s:=@]/g 13343 }, 13344 13345 metadata : { 13346 tab : 'tab', 13347 loaded : 'loaded', 13348 promise: 'promise' 13349 }, 13350 13351 className : { 13352 loading : 'loading', 13353 active : 'active' 13354 }, 13355 13356 selector : { 13357 tabs : '.ui.tab', 13358 ui : '.ui' 13359 } 13360 13361 }; 13362 13363 })( jQuery, window, document );