code.gitea.io/gitea@v1.22.3/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 - Dimmer 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.dimmer = function(parameters) { 1215 var 1216 $allModules = $(this), 1217 1218 time = new Date().getTime(), 1219 performance = [], 1220 1221 query = arguments[0], 1222 methodInvoked = (typeof query == 'string'), 1223 queryArguments = [].slice.call(arguments, 1), 1224 1225 returnedValue 1226 ; 1227 1228 $allModules 1229 .each(function() { 1230 var 1231 settings = ( $.isPlainObject(parameters) ) 1232 ? $.extend(true, {}, $.fn.dimmer.settings, parameters) 1233 : $.extend({}, $.fn.dimmer.settings), 1234 1235 selector = settings.selector, 1236 namespace = settings.namespace, 1237 className = settings.className, 1238 error = settings.error, 1239 1240 eventNamespace = '.' + namespace, 1241 moduleNamespace = 'module-' + namespace, 1242 moduleSelector = $allModules.selector || '', 1243 1244 clickEvent = "click", unstableClickEvent = ('ontouchstart' in document.documentElement) 1245 ? 'touchstart' 1246 : 'click', 1247 1248 $module = $(this), 1249 $dimmer, 1250 $dimmable, 1251 1252 element = this, 1253 instance = $module.data(moduleNamespace), 1254 module 1255 ; 1256 1257 module = { 1258 1259 preinitialize: function() { 1260 if( module.is.dimmer() ) { 1261 1262 $dimmable = $module.parent(); 1263 $dimmer = $module; 1264 } 1265 else { 1266 $dimmable = $module; 1267 if( module.has.dimmer() ) { 1268 if(settings.dimmerName) { 1269 $dimmer = $dimmable.find(selector.dimmer).filter('.' + settings.dimmerName); 1270 } 1271 else { 1272 $dimmer = $dimmable.find(selector.dimmer); 1273 } 1274 } 1275 else { 1276 $dimmer = module.create(); 1277 } 1278 } 1279 }, 1280 1281 initialize: function() { 1282 module.debug('Initializing dimmer', settings); 1283 1284 module.bind.events(); 1285 module.set.dimmable(); 1286 module.instantiate(); 1287 }, 1288 1289 instantiate: function() { 1290 module.verbose('Storing instance of module', module); 1291 instance = module; 1292 $module 1293 .data(moduleNamespace, instance) 1294 ; 1295 }, 1296 1297 destroy: function() { 1298 module.verbose('Destroying previous module', $dimmer); 1299 module.unbind.events(); 1300 module.remove.variation(); 1301 $dimmable 1302 .off(eventNamespace) 1303 ; 1304 }, 1305 1306 bind: { 1307 events: function() { 1308 if(settings.on == 'hover') { 1309 $dimmable 1310 .on('mouseenter' + eventNamespace, module.show) 1311 .on('mouseleave' + eventNamespace, module.hide) 1312 ; 1313 } 1314 else if(settings.on == 'click') { 1315 $dimmable 1316 .on(clickEvent + eventNamespace, module.toggle) 1317 ; 1318 } 1319 if( module.is.page() ) { 1320 module.debug('Setting as a page dimmer', $dimmable); 1321 module.set.pageDimmer(); 1322 } 1323 1324 if( module.is.closable() ) { 1325 module.verbose('Adding dimmer close event', $dimmer); 1326 $dimmable 1327 .on(clickEvent + eventNamespace, selector.dimmer, module.event.click) 1328 ; 1329 } 1330 } 1331 }, 1332 1333 unbind: { 1334 events: function() { 1335 $module 1336 .removeData(moduleNamespace) 1337 ; 1338 $dimmable 1339 .off(eventNamespace) 1340 ; 1341 } 1342 }, 1343 1344 event: { 1345 click: function(event) { 1346 module.verbose('Determining if event occured on dimmer', event); 1347 if( $dimmer.find(event.target).length === 0 || $(event.target).is(selector.content) ) { 1348 module.hide(); 1349 event.stopImmediatePropagation(); 1350 } 1351 } 1352 }, 1353 1354 addContent: function(element) { 1355 var 1356 $content = $(element) 1357 ; 1358 module.debug('Add content to dimmer', $content); 1359 if($content.parent()[0] !== $dimmer[0]) { 1360 $content.detach().appendTo($dimmer); 1361 } 1362 }, 1363 1364 create: function() { 1365 var 1366 $element = $( settings.template.dimmer(settings) ) 1367 ; 1368 if(settings.dimmerName) { 1369 module.debug('Creating named dimmer', settings.dimmerName); 1370 $element.addClass(settings.dimmerName); 1371 } 1372 $element 1373 .appendTo($dimmable) 1374 ; 1375 return $element; 1376 }, 1377 1378 show: function(callback) { 1379 callback = $.isFunction(callback) 1380 ? callback 1381 : function(){} 1382 ; 1383 module.debug('Showing dimmer', $dimmer, settings); 1384 module.set.variation(); 1385 if( (!module.is.dimmed() || module.is.animating()) && module.is.enabled() ) { 1386 module.animate.show(callback); 1387 settings.onShow.call(element); 1388 settings.onChange.call(element); 1389 } 1390 else { 1391 module.debug('Dimmer is already shown or disabled'); 1392 } 1393 }, 1394 1395 hide: function(callback) { 1396 callback = $.isFunction(callback) 1397 ? callback 1398 : function(){} 1399 ; 1400 if( module.is.dimmed() || module.is.animating() ) { 1401 module.debug('Hiding dimmer', $dimmer); 1402 module.animate.hide(callback); 1403 settings.onHide.call(element); 1404 settings.onChange.call(element); 1405 } 1406 else { 1407 module.debug('Dimmer is not visible'); 1408 } 1409 }, 1410 1411 toggle: function() { 1412 module.verbose('Toggling dimmer visibility', $dimmer); 1413 if( !module.is.dimmed() ) { 1414 module.show(); 1415 } 1416 else { 1417 if ( module.is.closable() ) { 1418 module.hide(); 1419 } 1420 } 1421 }, 1422 1423 animate: { 1424 show: function(callback) { 1425 callback = $.isFunction(callback) 1426 ? callback 1427 : function(){} 1428 ; 1429 if(settings.useCSS && $.fn.transition !== undefined && $dimmer.transition('is supported')) { 1430 if(settings.useFlex) { 1431 module.debug('Using flex dimmer'); 1432 module.remove.legacy(); 1433 } 1434 else { 1435 module.debug('Using legacy non-flex dimmer'); 1436 module.set.legacy(); 1437 } 1438 if(settings.opacity !== 'auto') { 1439 module.set.opacity(); 1440 } 1441 $dimmer 1442 .transition({ 1443 displayType : settings.useFlex 1444 ? 'flex' 1445 : 'block', 1446 animation : settings.transition + ' in', 1447 queue : false, 1448 duration : module.get.duration(), 1449 useFailSafe : true, 1450 onStart : function() { 1451 module.set.dimmed(); 1452 }, 1453 onComplete : function() { 1454 module.set.active(); 1455 callback(); 1456 } 1457 }) 1458 ; 1459 } 1460 else { 1461 module.verbose('Showing dimmer animation with javascript'); 1462 module.set.dimmed(); 1463 if(settings.opacity == 'auto') { 1464 settings.opacity = 0.8; 1465 } 1466 $dimmer 1467 .stop() 1468 .css({ 1469 opacity : 0, 1470 width : '100%', 1471 height : '100%' 1472 }) 1473 .fadeTo(module.get.duration(), settings.opacity, function() { 1474 $dimmer.removeAttr('style'); 1475 module.set.active(); 1476 callback(); 1477 }) 1478 ; 1479 } 1480 }, 1481 hide: function(callback) { 1482 callback = $.isFunction(callback) 1483 ? callback 1484 : function(){} 1485 ; 1486 if(settings.useCSS && $.fn.transition !== undefined && $dimmer.transition('is supported')) { 1487 module.verbose('Hiding dimmer with css'); 1488 $dimmer 1489 .transition({ 1490 displayType : settings.useFlex 1491 ? 'flex' 1492 : 'block', 1493 animation : settings.transition + ' out', 1494 queue : false, 1495 duration : module.get.duration(), 1496 useFailSafe : true, 1497 onComplete : function() { 1498 module.remove.dimmed(); 1499 module.remove.variation(); 1500 module.remove.active(); 1501 callback(); 1502 } 1503 }) 1504 ; 1505 } 1506 else { 1507 module.verbose('Hiding dimmer with javascript'); 1508 $dimmer 1509 .stop() 1510 .fadeOut(module.get.duration(), function() { 1511 module.remove.dimmed(); 1512 module.remove.active(); 1513 $dimmer.removeAttr('style'); 1514 callback(); 1515 }) 1516 ; 1517 } 1518 } 1519 }, 1520 1521 get: { 1522 dimmer: function() { 1523 return $dimmer; 1524 }, 1525 duration: function() { 1526 if(typeof settings.duration == 'object') { 1527 if( module.is.active() ) { 1528 return settings.duration.hide; 1529 } 1530 else { 1531 return settings.duration.show; 1532 } 1533 } 1534 return settings.duration; 1535 } 1536 }, 1537 1538 has: { 1539 dimmer: function() { 1540 if(settings.dimmerName) { 1541 return ($module.find(selector.dimmer).filter('.' + settings.dimmerName).length > 0); 1542 } 1543 else { 1544 return ( $module.find(selector.dimmer).length > 0 ); 1545 } 1546 } 1547 }, 1548 1549 is: { 1550 active: function() { 1551 return $dimmer.hasClass(className.active); 1552 }, 1553 animating: function() { 1554 return ( $dimmer.is(':animated') || $dimmer.hasClass(className.animating) ); 1555 }, 1556 closable: function() { 1557 if(settings.closable == 'auto') { 1558 if(settings.on == 'hover') { 1559 return false; 1560 } 1561 return true; 1562 } 1563 return settings.closable; 1564 }, 1565 dimmer: function() { 1566 return $module.hasClass(className.dimmer); 1567 }, 1568 dimmable: function() { 1569 return $module.hasClass(className.dimmable); 1570 }, 1571 dimmed: function() { 1572 return $dimmable.hasClass(className.dimmed); 1573 }, 1574 disabled: function() { 1575 return $dimmable.hasClass(className.disabled); 1576 }, 1577 enabled: function() { 1578 return !module.is.disabled(); 1579 }, 1580 page: function () { 1581 return $dimmable.is('body'); 1582 }, 1583 pageDimmer: function() { 1584 return $dimmer.hasClass(className.pageDimmer); 1585 } 1586 }, 1587 1588 can: { 1589 show: function() { 1590 return !$dimmer.hasClass(className.disabled); 1591 } 1592 }, 1593 1594 set: { 1595 opacity: function(opacity) { 1596 var 1597 color = $dimmer.css('background-color'), 1598 colorArray = color.split(','), 1599 isRGB = (colorArray && colorArray.length >= 3) 1600 ; 1601 opacity = settings.opacity === 0 ? 0 : settings.opacity || opacity; 1602 if(isRGB) { 1603 colorArray[2] = colorArray[2].replace(')',''); 1604 colorArray[3] = opacity + ')'; 1605 color = colorArray.join(','); 1606 } 1607 else { 1608 color = 'rgba(0, 0, 0, ' + opacity + ')'; 1609 } 1610 module.debug('Setting opacity to', opacity); 1611 $dimmer.css('background-color', color); 1612 }, 1613 legacy: function() { 1614 $dimmer.addClass(className.legacy); 1615 }, 1616 active: function() { 1617 $dimmer.addClass(className.active); 1618 }, 1619 dimmable: function() { 1620 $dimmable.addClass(className.dimmable); 1621 }, 1622 dimmed: function() { 1623 $dimmable.addClass(className.dimmed); 1624 }, 1625 pageDimmer: function() { 1626 $dimmer.addClass(className.pageDimmer); 1627 }, 1628 disabled: function() { 1629 $dimmer.addClass(className.disabled); 1630 }, 1631 variation: function(variation) { 1632 variation = variation || settings.variation; 1633 if(variation) { 1634 $dimmer.addClass(variation); 1635 } 1636 } 1637 }, 1638 1639 remove: { 1640 active: function() { 1641 $dimmer 1642 .removeClass(className.active) 1643 ; 1644 }, 1645 legacy: function() { 1646 $dimmer.removeClass(className.legacy); 1647 }, 1648 dimmed: function() { 1649 $dimmable.removeClass(className.dimmed); 1650 }, 1651 disabled: function() { 1652 $dimmer.removeClass(className.disabled); 1653 }, 1654 variation: function(variation) { 1655 variation = variation || settings.variation; 1656 if(variation) { 1657 $dimmer.removeClass(variation); 1658 } 1659 } 1660 }, 1661 1662 setting: function(name, value) { 1663 module.debug('Changing setting', name, value); 1664 if( $.isPlainObject(name) ) { 1665 $.extend(true, settings, name); 1666 } 1667 else if(value !== undefined) { 1668 if($.isPlainObject(settings[name])) { 1669 $.extend(true, settings[name], value); 1670 } 1671 else { 1672 settings[name] = value; 1673 } 1674 } 1675 else { 1676 return settings[name]; 1677 } 1678 }, 1679 internal: function(name, value) { 1680 if( $.isPlainObject(name) ) { 1681 $.extend(true, module, name); 1682 } 1683 else if(value !== undefined) { 1684 module[name] = value; 1685 } 1686 else { 1687 return module[name]; 1688 } 1689 }, 1690 debug: function() { 1691 if(!settings.silent && settings.debug) { 1692 if(settings.performance) { 1693 module.performance.log(arguments); 1694 } 1695 else { 1696 module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); 1697 module.debug.apply(console, arguments); 1698 } 1699 } 1700 }, 1701 verbose: function() { 1702 if(!settings.silent && settings.verbose && settings.debug) { 1703 if(settings.performance) { 1704 module.performance.log(arguments); 1705 } 1706 else { 1707 module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); 1708 module.verbose.apply(console, arguments); 1709 } 1710 } 1711 }, 1712 error: function() { 1713 if(!settings.silent) { 1714 module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); 1715 module.error.apply(console, arguments); 1716 } 1717 }, 1718 performance: { 1719 log: function(message) { 1720 var 1721 currentTime, 1722 executionTime, 1723 previousTime 1724 ; 1725 if(settings.performance) { 1726 currentTime = new Date().getTime(); 1727 previousTime = time || currentTime; 1728 executionTime = currentTime - previousTime; 1729 time = currentTime; 1730 performance.push({ 1731 'Name' : message[0], 1732 'Arguments' : [].slice.call(message, 1) || '', 1733 'Element' : element, 1734 'Execution Time' : executionTime 1735 }); 1736 } 1737 clearTimeout(module.performance.timer); 1738 module.performance.timer = setTimeout(module.performance.display, 500); 1739 }, 1740 display: function() { 1741 var 1742 title = settings.name + ':', 1743 totalTime = 0 1744 ; 1745 time = false; 1746 clearTimeout(module.performance.timer); 1747 $.each(performance, function(index, data) { 1748 totalTime += data['Execution Time']; 1749 }); 1750 title += ' ' + totalTime + 'ms'; 1751 if(moduleSelector) { 1752 title += ' \'' + moduleSelector + '\''; 1753 } 1754 if($allModules.length > 1) { 1755 title += ' ' + '(' + $allModules.length + ')'; 1756 } 1757 if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { 1758 console.groupCollapsed(title); 1759 if(console.table) { 1760 console.table(performance); 1761 } 1762 else { 1763 $.each(performance, function(index, data) { 1764 console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); 1765 }); 1766 } 1767 console.groupEnd(); 1768 } 1769 performance = []; 1770 } 1771 }, 1772 invoke: function(query, passedArguments, context) { 1773 var 1774 object = instance, 1775 maxDepth, 1776 found, 1777 response 1778 ; 1779 passedArguments = passedArguments || queryArguments; 1780 context = element || context; 1781 if(typeof query == 'string' && object !== undefined) { 1782 query = query.split(/[\. ]/); 1783 maxDepth = query.length - 1; 1784 $.each(query, function(depth, value) { 1785 var camelCaseValue = (depth != maxDepth) 1786 ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) 1787 : query 1788 ; 1789 if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { 1790 object = object[camelCaseValue]; 1791 } 1792 else if( object[camelCaseValue] !== undefined ) { 1793 found = object[camelCaseValue]; 1794 return false; 1795 } 1796 else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { 1797 object = object[value]; 1798 } 1799 else if( object[value] !== undefined ) { 1800 found = object[value]; 1801 return false; 1802 } 1803 else { 1804 module.error(error.method, query); 1805 return false; 1806 } 1807 }); 1808 } 1809 if ( $.isFunction( found ) ) { 1810 response = found.apply(context, passedArguments); 1811 } 1812 else if(found !== undefined) { 1813 response = found; 1814 } 1815 if(Array.isArray(returnedValue)) { 1816 returnedValue.push(response); 1817 } 1818 else if(returnedValue !== undefined) { 1819 returnedValue = [returnedValue, response]; 1820 } 1821 else if(response !== undefined) { 1822 returnedValue = response; 1823 } 1824 return found; 1825 } 1826 }; 1827 1828 module.preinitialize(); 1829 1830 if(methodInvoked) { 1831 if(instance === undefined) { 1832 module.initialize(); 1833 } 1834 module.invoke(query); 1835 } 1836 else { 1837 if(instance !== undefined) { 1838 instance.invoke('destroy'); 1839 } 1840 module.initialize(); 1841 } 1842 }) 1843 ; 1844 1845 return (returnedValue !== undefined) 1846 ? returnedValue 1847 : this 1848 ; 1849 }; 1850 1851 $.fn.dimmer.settings = { 1852 1853 name : 'Dimmer', 1854 namespace : 'dimmer', 1855 1856 silent : false, 1857 debug : false, 1858 verbose : false, 1859 performance : true, 1860 1861 // whether should use flex layout 1862 useFlex : true, 1863 1864 // name to distinguish between multiple dimmers in context 1865 dimmerName : false, 1866 1867 // whether to add a variation type 1868 variation : false, 1869 1870 // whether to bind close events 1871 closable : 'auto', 1872 1873 // whether to use css animations 1874 useCSS : true, 1875 1876 // css animation to use 1877 transition : 'fade', 1878 1879 // event to bind to 1880 on : false, 1881 1882 // overriding opacity value 1883 opacity : 'auto', 1884 1885 // transition durations 1886 duration : { 1887 show : 500, 1888 hide : 500 1889 }, 1890 // whether the dynamically created dimmer should have a loader 1891 displayLoader: false, 1892 loaderText : false, 1893 loaderVariation : '', 1894 1895 onChange : function(){}, 1896 onShow : function(){}, 1897 onHide : function(){}, 1898 1899 error : { 1900 method : 'The method you called is not defined.' 1901 }, 1902 1903 className : { 1904 active : 'active', 1905 animating : 'animating', 1906 dimmable : 'dimmable', 1907 dimmed : 'dimmed', 1908 dimmer : 'dimmer', 1909 disabled : 'disabled', 1910 hide : 'hide', 1911 legacy : 'legacy', 1912 pageDimmer : 'page', 1913 show : 'show', 1914 loader : 'ui loader' 1915 }, 1916 1917 selector: { 1918 dimmer : '> .ui.dimmer', 1919 content : '.ui.dimmer > .content, .ui.dimmer > .content > .center' 1920 }, 1921 1922 template: { 1923 dimmer: function(settings) { 1924 var d = $('<div/>').addClass('ui dimmer'),l; 1925 if(settings.displayLoader) { 1926 l = $('<div/>') 1927 .addClass(settings.className.loader) 1928 .addClass(settings.loaderVariation); 1929 if(!!settings.loaderText){ 1930 l.text(settings.loaderText); 1931 l.addClass('text'); 1932 } 1933 d.append(l); 1934 } 1935 return d; 1936 } 1937 } 1938 1939 }; 1940 1941 })( jQuery, window, document ); 1942 1943 /*! 1944 * # Fomantic-UI - Dropdown 1945 * http://github.com/fomantic/Fomantic-UI/ 1946 * 1947 * 1948 * Released under the MIT license 1949 * http://opensource.org/licenses/MIT 1950 * 1951 */ 1952 1953 ;(function ($, window, document, undefined) { 1954 1955 'use strict'; 1956 1957 $.isFunction = $.isFunction || function(obj) { 1958 return typeof obj === "function" && typeof obj.nodeType !== "number"; 1959 }; 1960 1961 window = (typeof window != 'undefined' && window.Math == Math) 1962 ? window 1963 : (typeof self != 'undefined' && self.Math == Math) 1964 ? self 1965 : Function('return this')() 1966 ; 1967 1968 $.fn.dropdown = function(parameters) { 1969 var 1970 $allModules = $(this), 1971 $document = $(document), 1972 1973 moduleSelector = $allModules.selector || '', 1974 1975 hasTouch = ('ontouchstart' in document.documentElement), 1976 clickEvent = "click", unstableClickEvent = hasTouch 1977 ? 'touchstart' 1978 : 'click', 1979 1980 time = new Date().getTime(), 1981 performance = [], 1982 1983 query = arguments[0], 1984 methodInvoked = (typeof query == 'string'), 1985 queryArguments = [].slice.call(arguments, 1), 1986 returnedValue 1987 ; 1988 1989 $allModules 1990 .each(function(elementIndex) { 1991 var 1992 settings = ( $.isPlainObject(parameters) ) 1993 ? $.extend(true, {}, $.fn.dropdown.settings, parameters) 1994 : $.extend({}, $.fn.dropdown.settings), 1995 1996 className = settings.className, 1997 message = settings.message, 1998 fields = settings.fields, 1999 keys = settings.keys, 2000 metadata = settings.metadata, 2001 namespace = settings.namespace, 2002 regExp = settings.regExp, 2003 selector = settings.selector, 2004 error = settings.error, 2005 templates = settings.templates, 2006 2007 eventNamespace = '.' + namespace, 2008 moduleNamespace = 'module-' + namespace, 2009 2010 $module = $(this), 2011 $context = $(settings.context), 2012 $text = $module.find(selector.text), 2013 $search = $module.find(selector.search), 2014 $sizer = $module.find(selector.sizer), 2015 $input = $module.find(selector.input), 2016 $icon = $module.find(selector.icon), 2017 $clear = $module.find(selector.clearIcon), 2018 2019 $combo = ($module.prev().find(selector.text).length > 0) 2020 ? $module.prev().find(selector.text) 2021 : $module.prev(), 2022 2023 $menu = $module.children(selector.menu), 2024 $item = $menu.find(selector.item), 2025 $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(), 2026 2027 activated = false, 2028 itemActivated = false, 2029 internalChange = false, 2030 iconClicked = false, 2031 element = this, 2032 instance = $module.data(moduleNamespace), 2033 2034 selectActionActive, 2035 initialLoad, 2036 pageLostFocus, 2037 willRefocus, 2038 elementNamespace, 2039 id, 2040 selectObserver, 2041 menuObserver, 2042 classObserver, 2043 module 2044 ; 2045 2046 module = { 2047 2048 initialize: function() { 2049 module.debug('Initializing dropdown', settings); 2050 2051 if( module.is.alreadySetup() ) { 2052 module.setup.reference(); 2053 } 2054 else { 2055 if (settings.ignoreDiacritics && !String.prototype.normalize) { 2056 settings.ignoreDiacritics = false; 2057 module.error(error.noNormalize, element); 2058 } 2059 2060 module.setup.layout(); 2061 2062 if(settings.values) { 2063 module.set.initialLoad(); 2064 module.change.values(settings.values); 2065 module.remove.initialLoad(); 2066 } 2067 2068 module.refreshData(); 2069 2070 module.save.defaults(); 2071 module.restore.selected(); 2072 2073 module.create.id(); 2074 module.bind.events(); 2075 2076 module.observeChanges(); 2077 module.instantiate(); 2078 } 2079 2080 }, 2081 2082 instantiate: function() { 2083 module.verbose('Storing instance of dropdown', module); 2084 instance = module; 2085 $module 2086 .data(moduleNamespace, module) 2087 ; 2088 }, 2089 2090 destroy: function() { 2091 module.verbose('Destroying previous dropdown', $module); 2092 module.remove.tabbable(); 2093 module.remove.active(); 2094 $menu.transition('stop all'); 2095 $menu.removeClass(className.visible).addClass(className.hidden); 2096 $module 2097 .off(eventNamespace) 2098 .removeData(moduleNamespace) 2099 ; 2100 $menu 2101 .off(eventNamespace) 2102 ; 2103 $document 2104 .off(elementNamespace) 2105 ; 2106 module.disconnect.menuObserver(); 2107 module.disconnect.selectObserver(); 2108 module.disconnect.classObserver(); 2109 }, 2110 2111 observeChanges: function() { 2112 if('MutationObserver' in window) { 2113 selectObserver = new MutationObserver(module.event.select.mutation); 2114 menuObserver = new MutationObserver(module.event.menu.mutation); 2115 classObserver = new MutationObserver(module.event.class.mutation); 2116 module.debug('Setting up mutation observer', selectObserver, menuObserver, classObserver); 2117 module.observe.select(); 2118 module.observe.menu(); 2119 module.observe.class(); 2120 } 2121 }, 2122 2123 disconnect: { 2124 menuObserver: function() { 2125 if(menuObserver) { 2126 menuObserver.disconnect(); 2127 } 2128 }, 2129 selectObserver: function() { 2130 if(selectObserver) { 2131 selectObserver.disconnect(); 2132 } 2133 }, 2134 classObserver: function() { 2135 if(classObserver) { 2136 classObserver.disconnect(); 2137 } 2138 } 2139 }, 2140 observe: { 2141 select: function() { 2142 if(module.has.input() && selectObserver) { 2143 selectObserver.observe($module[0], { 2144 childList : true, 2145 subtree : true 2146 }); 2147 } 2148 }, 2149 menu: function() { 2150 if(module.has.menu() && menuObserver) { 2151 menuObserver.observe($menu[0], { 2152 childList : true, 2153 subtree : true 2154 }); 2155 } 2156 }, 2157 class: function() { 2158 if(module.has.search() && classObserver) { 2159 classObserver.observe($module[0], { 2160 attributes : true 2161 }); 2162 } 2163 } 2164 }, 2165 2166 create: { 2167 id: function() { 2168 id = (Math.random().toString(16) + '000000000').substr(2, 8); 2169 elementNamespace = '.' + id; 2170 module.verbose('Creating unique id for element', id); 2171 }, 2172 userChoice: function(values) { 2173 var 2174 $userChoices, 2175 $userChoice, 2176 isUserValue, 2177 html 2178 ; 2179 values = values || module.get.userValues(); 2180 if(!values) { 2181 return false; 2182 } 2183 values = Array.isArray(values) 2184 ? values 2185 : [values] 2186 ; 2187 $.each(values, function(index, value) { 2188 if(module.get.item(value) === false) { 2189 html = settings.templates.addition( module.add.variables(message.addResult, value) ); 2190 $userChoice = $('<div />') 2191 .html(html) 2192 .attr('data-' + metadata.value, value) 2193 .attr('data-' + metadata.text, value) 2194 .addClass(className.addition) 2195 .addClass(className.item) 2196 ; 2197 if(settings.hideAdditions) { 2198 $userChoice.addClass(className.hidden); 2199 } 2200 $userChoices = ($userChoices === undefined) 2201 ? $userChoice 2202 : $userChoices.add($userChoice) 2203 ; 2204 module.verbose('Creating user choices for value', value, $userChoice); 2205 } 2206 }); 2207 return $userChoices; 2208 }, 2209 userLabels: function(value) { 2210 var 2211 userValues = module.get.userValues() 2212 ; 2213 if(userValues) { 2214 module.debug('Adding user labels', userValues); 2215 $.each(userValues, function(index, value) { 2216 module.verbose('Adding custom user value'); 2217 module.add.label(value, value); 2218 }); 2219 } 2220 }, 2221 menu: function() { 2222 $menu = $('<div />') 2223 .addClass(className.menu) 2224 .appendTo($module) 2225 ; 2226 }, 2227 sizer: function() { 2228 $sizer = $('<span />') 2229 .addClass(className.sizer) 2230 .insertAfter($search) 2231 ; 2232 } 2233 }, 2234 2235 search: function(query) { 2236 query = (query !== undefined) 2237 ? query 2238 : module.get.query() 2239 ; 2240 module.verbose('Searching for query', query); 2241 if(module.has.minCharacters(query)) { 2242 module.filter(query); 2243 } 2244 else { 2245 module.hide(null,true); 2246 } 2247 }, 2248 2249 select: { 2250 firstUnfiltered: function() { 2251 module.verbose('Selecting first non-filtered element'); 2252 module.remove.selectedItem(); 2253 $item 2254 .not(selector.unselectable) 2255 .not(selector.addition + selector.hidden) 2256 .eq(0) 2257 .addClass(className.selected) 2258 ; 2259 }, 2260 nextAvailable: function($selected) { 2261 $selected = $selected.eq(0); 2262 var 2263 $nextAvailable = $selected.nextAll(selector.item).not(selector.unselectable).eq(0), 2264 $prevAvailable = $selected.prevAll(selector.item).not(selector.unselectable).eq(0), 2265 hasNext = ($nextAvailable.length > 0) 2266 ; 2267 if(hasNext) { 2268 module.verbose('Moving selection to', $nextAvailable); 2269 $nextAvailable.addClass(className.selected); 2270 } 2271 else { 2272 module.verbose('Moving selection to', $prevAvailable); 2273 $prevAvailable.addClass(className.selected); 2274 } 2275 } 2276 }, 2277 2278 setup: { 2279 api: function() { 2280 var 2281 apiSettings = { 2282 debug : settings.debug, 2283 urlData : { 2284 value : module.get.value(), 2285 query : module.get.query() 2286 }, 2287 on : false 2288 } 2289 ; 2290 module.verbose('First request, initializing API'); 2291 $module 2292 .api(apiSettings) 2293 ; 2294 }, 2295 layout: function() { 2296 if( $module.is('select') ) { 2297 module.setup.select(); 2298 module.setup.returnedObject(); 2299 } 2300 if( !module.has.menu() ) { 2301 module.create.menu(); 2302 } 2303 if ( module.is.selection() && module.is.clearable() && !module.has.clearItem() ) { 2304 module.verbose('Adding clear icon'); 2305 $clear = $('<i />') 2306 .addClass('remove icon') 2307 .insertBefore($text) 2308 ; 2309 } 2310 if( module.is.search() && !module.has.search() ) { 2311 module.verbose('Adding search input'); 2312 $search = $('<input />') 2313 .addClass(className.search) 2314 .prop('autocomplete', 'off') 2315 .insertBefore($text) 2316 ; 2317 } 2318 if( module.is.multiple() && module.is.searchSelection() && !module.has.sizer()) { 2319 module.create.sizer(); 2320 } 2321 if(settings.allowTab) { 2322 module.set.tabbable(); 2323 } 2324 }, 2325 select: function() { 2326 var 2327 selectValues = module.get.selectValues() 2328 ; 2329 module.debug('Dropdown initialized on a select', selectValues); 2330 if( $module.is('select') ) { 2331 $input = $module; 2332 } 2333 // see if select is placed correctly already 2334 if($input.parent(selector.dropdown).length > 0) { 2335 module.debug('UI dropdown already exists. Creating dropdown menu only'); 2336 $module = $input.closest(selector.dropdown); 2337 if( !module.has.menu() ) { 2338 module.create.menu(); 2339 } 2340 $menu = $module.children(selector.menu); 2341 module.setup.menu(selectValues); 2342 } 2343 else { 2344 module.debug('Creating entire dropdown from select'); 2345 $module = $('<div />') 2346 .attr('class', $input.attr('class') ) 2347 .addClass(className.selection) 2348 .addClass(className.dropdown) 2349 .html( templates.dropdown(selectValues, fields, settings.preserveHTML, settings.className) ) 2350 .insertBefore($input) 2351 ; 2352 if($input.hasClass(className.multiple) && $input.prop('multiple') === false) { 2353 module.error(error.missingMultiple); 2354 $input.prop('multiple', true); 2355 } 2356 if($input.is('[multiple]')) { 2357 module.set.multiple(); 2358 } 2359 if ($input.prop('disabled')) { 2360 module.debug('Disabling dropdown'); 2361 $module.addClass(className.disabled); 2362 } 2363 $input 2364 .removeAttr('required') 2365 .removeAttr('class') 2366 .detach() 2367 .prependTo($module) 2368 ; 2369 } 2370 module.refresh(); 2371 }, 2372 menu: function(values) { 2373 $menu.html( templates.menu(values, fields,settings.preserveHTML,settings.className)); 2374 $item = $menu.find(selector.item); 2375 $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); 2376 }, 2377 reference: function() { 2378 module.debug('Dropdown behavior was called on select, replacing with closest dropdown'); 2379 // replace module reference 2380 $module = $module.parent(selector.dropdown); 2381 instance = $module.data(moduleNamespace); 2382 element = $module.get(0); 2383 module.refresh(); 2384 module.setup.returnedObject(); 2385 }, 2386 returnedObject: function() { 2387 var 2388 $firstModules = $allModules.slice(0, elementIndex), 2389 $lastModules = $allModules.slice(elementIndex + 1) 2390 ; 2391 // adjust all modules to use correct reference 2392 $allModules = $firstModules.add($module).add($lastModules); 2393 } 2394 }, 2395 2396 refresh: function() { 2397 module.refreshSelectors(); 2398 module.refreshData(); 2399 }, 2400 2401 refreshItems: function() { 2402 $item = $menu.find(selector.item); 2403 $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); 2404 }, 2405 2406 refreshSelectors: function() { 2407 module.verbose('Refreshing selector cache'); 2408 $text = $module.find(selector.text); 2409 $search = $module.find(selector.search); 2410 $input = $module.find(selector.input); 2411 $icon = $module.find(selector.icon); 2412 $combo = ($module.prev().find(selector.text).length > 0) 2413 ? $module.prev().find(selector.text) 2414 : $module.prev() 2415 ; 2416 $menu = $module.children(selector.menu); 2417 $item = $menu.find(selector.item); 2418 $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); 2419 }, 2420 2421 refreshData: function() { 2422 module.verbose('Refreshing cached metadata'); 2423 $item 2424 .removeData(metadata.text) 2425 .removeData(metadata.value) 2426 ; 2427 }, 2428 2429 clearData: function() { 2430 module.verbose('Clearing metadata'); 2431 $item 2432 .removeData(metadata.text) 2433 .removeData(metadata.value) 2434 ; 2435 $module 2436 .removeData(metadata.defaultText) 2437 .removeData(metadata.defaultValue) 2438 .removeData(metadata.placeholderText) 2439 ; 2440 }, 2441 2442 toggle: function() { 2443 module.verbose('Toggling menu visibility'); 2444 if( !module.is.active() ) { 2445 module.show(); 2446 } 2447 else { 2448 module.hide(); 2449 } 2450 }, 2451 2452 show: function(callback, preventFocus) { 2453 callback = $.isFunction(callback) 2454 ? callback 2455 : function(){} 2456 ; 2457 if(!module.can.show() && module.is.remote()) { 2458 module.debug('No API results retrieved, searching before show'); 2459 module.queryRemote(module.get.query(), module.show); 2460 } 2461 if( module.can.show() && !module.is.active() ) { 2462 module.debug('Showing dropdown'); 2463 if(module.has.message() && !(module.has.maxSelections() || module.has.allResultsFiltered()) ) { 2464 module.remove.message(); 2465 } 2466 if(module.is.allFiltered()) { 2467 return true; 2468 } 2469 if(settings.onShow.call(element) !== false) { 2470 module.animate.show(function() { 2471 if( module.can.click() ) { 2472 module.bind.intent(); 2473 } 2474 if(module.has.search() && !preventFocus) { 2475 module.focusSearch(); 2476 } 2477 module.set.visible(); 2478 callback.call(element); 2479 }); 2480 } 2481 } 2482 }, 2483 2484 hide: function(callback, preventBlur) { 2485 callback = $.isFunction(callback) 2486 ? callback 2487 : function(){} 2488 ; 2489 if( module.is.active() && !module.is.animatingOutward() ) { 2490 module.debug('Hiding dropdown'); 2491 if(settings.onHide.call(element) !== false) { 2492 module.animate.hide(function() { 2493 module.remove.visible(); 2494 // hidding search focus 2495 if ( module.is.focusedOnSearch() && preventBlur !== true ) { 2496 $search.blur(); 2497 } 2498 callback.call(element); 2499 }); 2500 } 2501 } else if( module.can.click() ) { 2502 module.unbind.intent(); 2503 } 2504 iconClicked = false; 2505 }, 2506 2507 hideOthers: function() { 2508 module.verbose('Finding other dropdowns to hide'); 2509 $allModules 2510 .not($module) 2511 .has(selector.menu + '.' + className.visible) 2512 .dropdown('hide') 2513 ; 2514 }, 2515 2516 hideMenu: function() { 2517 module.verbose('Hiding menu instantaneously'); 2518 module.remove.active(); 2519 module.remove.visible(); 2520 $menu.transition('hide'); 2521 }, 2522 2523 hideSubMenus: function() { 2524 var 2525 $subMenus = $menu.children(selector.item).find(selector.menu) 2526 ; 2527 module.verbose('Hiding sub menus', $subMenus); 2528 $subMenus.transition('hide'); 2529 }, 2530 2531 bind: { 2532 events: function() { 2533 module.bind.keyboardEvents(); 2534 module.bind.inputEvents(); 2535 module.bind.mouseEvents(); 2536 }, 2537 keyboardEvents: function() { 2538 module.verbose('Binding keyboard events'); 2539 $module 2540 .on('keydown' + eventNamespace, module.event.keydown) 2541 ; 2542 if( module.has.search() ) { 2543 $module 2544 .on(module.get.inputEvent() + eventNamespace, selector.search, module.event.input) 2545 ; 2546 } 2547 if( module.is.multiple() ) { 2548 $document 2549 .on('keydown' + elementNamespace, module.event.document.keydown) 2550 ; 2551 } 2552 }, 2553 inputEvents: function() { 2554 module.verbose('Binding input change events'); 2555 $module 2556 .on('change' + eventNamespace, selector.input, module.event.change) 2557 ; 2558 }, 2559 mouseEvents: function() { 2560 module.verbose('Binding mouse events'); 2561 if(module.is.multiple()) { 2562 $module 2563 .on(clickEvent + eventNamespace, selector.label, module.event.label.click) 2564 .on(clickEvent + eventNamespace, selector.remove, module.event.remove.click) 2565 ; 2566 } 2567 if( module.is.searchSelection() ) { 2568 $module 2569 .on('mousedown' + eventNamespace, module.event.mousedown) 2570 .on('mouseup' + eventNamespace, module.event.mouseup) 2571 .on('mousedown' + eventNamespace, selector.menu, module.event.menu.mousedown) 2572 .on('mouseup' + eventNamespace, selector.menu, module.event.menu.mouseup) 2573 .on(clickEvent + eventNamespace, selector.icon, module.event.icon.click) 2574 .on(clickEvent + eventNamespace, selector.clearIcon, module.event.clearIcon.click) 2575 .on('focus' + eventNamespace, selector.search, module.event.search.focus) 2576 .on(clickEvent + eventNamespace, selector.search, module.event.search.focus) 2577 .on('blur' + eventNamespace, selector.search, module.event.search.blur) 2578 .on(clickEvent + eventNamespace, selector.text, module.event.text.focus) 2579 ; 2580 if(module.is.multiple()) { 2581 $module 2582 .on(clickEvent + eventNamespace, module.event.click) 2583 ; 2584 } 2585 } 2586 else { 2587 if(settings.on == 'click') { 2588 $module 2589 .on(clickEvent + eventNamespace, selector.icon, module.event.icon.click) 2590 .on(clickEvent + eventNamespace, module.event.test.toggle) 2591 ; 2592 } 2593 else if(settings.on == 'hover') { 2594 $module 2595 .on('mouseenter' + eventNamespace, module.delay.show) 2596 .on('mouseleave' + eventNamespace, module.delay.hide) 2597 ; 2598 } 2599 else { 2600 $module 2601 .on(settings.on + eventNamespace, module.toggle) 2602 ; 2603 } 2604 $module 2605 .on('mousedown' + eventNamespace, module.event.mousedown) 2606 .on('mouseup' + eventNamespace, module.event.mouseup) 2607 .on('focus' + eventNamespace, module.event.focus) 2608 .on(clickEvent + eventNamespace, selector.clearIcon, module.event.clearIcon.click) 2609 ; 2610 if(module.has.menuSearch() ) { 2611 $module 2612 .on('blur' + eventNamespace, selector.search, module.event.search.blur) 2613 ; 2614 } 2615 else { 2616 $module 2617 .on('blur' + eventNamespace, module.event.blur) 2618 ; 2619 } 2620 } 2621 $menu 2622 .on((hasTouch ? 'touchstart' : 'mouseenter') + eventNamespace, selector.item, module.event.item.mouseenter) 2623 .on('mouseleave' + eventNamespace, selector.item, module.event.item.mouseleave) 2624 .on('click' + eventNamespace, selector.item, module.event.item.click) 2625 ; 2626 }, 2627 intent: function() { 2628 module.verbose('Binding hide intent event to document'); 2629 if(hasTouch) { 2630 $document 2631 .on('touchstart' + elementNamespace, module.event.test.touch) 2632 .on('touchmove' + elementNamespace, module.event.test.touch) 2633 ; 2634 } 2635 $document 2636 .on(clickEvent + elementNamespace, module.event.test.hide) 2637 ; 2638 } 2639 }, 2640 2641 unbind: { 2642 intent: function() { 2643 module.verbose('Removing hide intent event from document'); 2644 if(hasTouch) { 2645 $document 2646 .off('touchstart' + elementNamespace) 2647 .off('touchmove' + elementNamespace) 2648 ; 2649 } 2650 $document 2651 .off(clickEvent + elementNamespace) 2652 ; 2653 } 2654 }, 2655 2656 filter: function(query) { 2657 var 2658 searchTerm = (query !== undefined) 2659 ? query 2660 : module.get.query(), 2661 afterFiltered = function() { 2662 if(module.is.multiple()) { 2663 module.filterActive(); 2664 } 2665 if(query || (!query && module.get.activeItem().length == 0)) { 2666 module.select.firstUnfiltered(); 2667 } 2668 if( module.has.allResultsFiltered() ) { 2669 if( settings.onNoResults.call(element, searchTerm) ) { 2670 if(settings.allowAdditions) { 2671 if(settings.hideAdditions) { 2672 module.verbose('User addition with no menu, setting empty style'); 2673 module.set.empty(); 2674 module.hideMenu(); 2675 } 2676 } 2677 else { 2678 module.verbose('All items filtered, showing message', searchTerm); 2679 module.add.message(message.noResults); 2680 } 2681 } 2682 else { 2683 module.verbose('All items filtered, hiding dropdown', searchTerm); 2684 module.hideMenu(); 2685 } 2686 } 2687 else { 2688 module.remove.empty(); 2689 module.remove.message(); 2690 } 2691 if(settings.allowAdditions) { 2692 module.add.userSuggestion(module.escape.htmlEntities(query)); 2693 } 2694 if(module.is.searchSelection() && module.can.show() && module.is.focusedOnSearch() ) { 2695 module.show(); 2696 } 2697 } 2698 ; 2699 if(settings.useLabels && module.has.maxSelections()) { 2700 return; 2701 } 2702 if(settings.apiSettings) { 2703 if( module.can.useAPI() ) { 2704 module.queryRemote(searchTerm, function() { 2705 if(settings.filterRemoteData) { 2706 module.filterItems(searchTerm); 2707 } 2708 var preSelected = $input.val(); 2709 if(!Array.isArray(preSelected)) { 2710 preSelected = preSelected && preSelected!=="" ? preSelected.split(settings.delimiter) : []; 2711 } 2712 $.each(preSelected,function(index,value){ 2713 $item.filter('[data-value="'+value+'"]') 2714 .addClass(className.filtered) 2715 ; 2716 }); 2717 afterFiltered(); 2718 }); 2719 } 2720 else { 2721 module.error(error.noAPI); 2722 } 2723 } 2724 else { 2725 module.filterItems(searchTerm); 2726 afterFiltered(); 2727 } 2728 }, 2729 2730 queryRemote: function(query, callback) { 2731 var 2732 apiSettings = { 2733 errorDuration : false, 2734 cache : 'local', 2735 throttle : settings.throttle, 2736 urlData : { 2737 query: query 2738 }, 2739 onError: function() { 2740 module.add.message(message.serverError); 2741 callback(); 2742 }, 2743 onFailure: function() { 2744 module.add.message(message.serverError); 2745 callback(); 2746 }, 2747 onSuccess : function(response) { 2748 var 2749 values = response[fields.remoteValues] 2750 ; 2751 if (!Array.isArray(values)){ 2752 values = []; 2753 } 2754 module.remove.message(); 2755 var menuConfig = {}; 2756 menuConfig[fields.values] = values; 2757 module.setup.menu(menuConfig); 2758 2759 if(values.length===0 && !settings.allowAdditions) { 2760 module.add.message(message.noResults); 2761 } 2762 callback(); 2763 } 2764 } 2765 ; 2766 if( !$module.api('get request') ) { 2767 module.setup.api(); 2768 } 2769 apiSettings = $.extend(true, {}, apiSettings, settings.apiSettings); 2770 $module 2771 .api('setting', apiSettings) 2772 .api('query') 2773 ; 2774 }, 2775 2776 filterItems: function(query) { 2777 var 2778 searchTerm = module.remove.diacritics(query !== undefined 2779 ? query 2780 : module.get.query() 2781 ), 2782 results = null, 2783 escapedTerm = module.escape.string(searchTerm), 2784 regExpFlags = (settings.ignoreSearchCase ? 'i' : '') + 'gm', 2785 beginsWithRegExp = new RegExp('^' + escapedTerm, regExpFlags) 2786 ; 2787 // avoid loop if we're matching nothing 2788 if( module.has.query() ) { 2789 results = []; 2790 2791 module.verbose('Searching for matching values', searchTerm); 2792 $item 2793 .each(function(){ 2794 var 2795 $choice = $(this), 2796 text, 2797 value 2798 ; 2799 if($choice.hasClass(className.unfilterable)) { 2800 results.push(this); 2801 return true; 2802 } 2803 if(settings.match === 'both' || settings.match === 'text') { 2804 text = module.remove.diacritics(String(module.get.choiceText($choice, false))); 2805 if(text.search(beginsWithRegExp) !== -1) { 2806 results.push(this); 2807 return true; 2808 } 2809 else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, text)) { 2810 results.push(this); 2811 return true; 2812 } 2813 else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, text)) { 2814 results.push(this); 2815 return true; 2816 } 2817 } 2818 if(settings.match === 'both' || settings.match === 'value') { 2819 value = module.remove.diacritics(String(module.get.choiceValue($choice, text))); 2820 if(value.search(beginsWithRegExp) !== -1) { 2821 results.push(this); 2822 return true; 2823 } 2824 else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, value)) { 2825 results.push(this); 2826 return true; 2827 } 2828 else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, value)) { 2829 results.push(this); 2830 return true; 2831 } 2832 } 2833 }) 2834 ; 2835 } 2836 module.debug('Showing only matched items', searchTerm); 2837 module.remove.filteredItem(); 2838 if(results) { 2839 $item 2840 .not(results) 2841 .addClass(className.filtered) 2842 ; 2843 } 2844 2845 if(!module.has.query()) { 2846 $divider 2847 .removeClass(className.hidden); 2848 } else if(settings.hideDividers === true) { 2849 $divider 2850 .addClass(className.hidden); 2851 } else if(settings.hideDividers === 'empty') { 2852 $divider 2853 .removeClass(className.hidden) 2854 .filter(function() { 2855 // First find the last divider in this divider group 2856 // Dividers which are direct siblings are considered a group 2857 var lastDivider = $(this).nextUntil(selector.item); 2858 2859 return (lastDivider.length ? lastDivider : $(this)) 2860 // Count all non-filtered items until the next divider (or end of the dropdown) 2861 .nextUntil(selector.divider) 2862 .filter(selector.item + ":not(." + className.filtered + ")") 2863 // Hide divider if no items are found 2864 .length === 0; 2865 }) 2866 .addClass(className.hidden); 2867 } 2868 }, 2869 2870 fuzzySearch: function(query, term) { 2871 var 2872 termLength = term.length, 2873 queryLength = query.length 2874 ; 2875 query = (settings.ignoreSearchCase ? query.toLowerCase() : query); 2876 term = (settings.ignoreSearchCase ? term.toLowerCase() : term); 2877 if(queryLength > termLength) { 2878 return false; 2879 } 2880 if(queryLength === termLength) { 2881 return (query === term); 2882 } 2883 search: for (var characterIndex = 0, nextCharacterIndex = 0; characterIndex < queryLength; characterIndex++) { 2884 var 2885 queryCharacter = query.charCodeAt(characterIndex) 2886 ; 2887 while(nextCharacterIndex < termLength) { 2888 if(term.charCodeAt(nextCharacterIndex++) === queryCharacter) { 2889 continue search; 2890 } 2891 } 2892 return false; 2893 } 2894 return true; 2895 }, 2896 exactSearch: function (query, term) { 2897 query = (settings.ignoreSearchCase ? query.toLowerCase() : query); 2898 term = (settings.ignoreSearchCase ? term.toLowerCase() : term); 2899 return term.indexOf(query) > -1; 2900 2901 }, 2902 filterActive: function() { 2903 if(settings.useLabels) { 2904 $item.filter('.' + className.active) 2905 .addClass(className.filtered) 2906 ; 2907 } 2908 }, 2909 2910 focusSearch: function(skipHandler) { 2911 if( module.has.search() && !module.is.focusedOnSearch() ) { 2912 if(skipHandler) { 2913 $module.off('focus' + eventNamespace, selector.search); 2914 $search.focus(); 2915 $module.on('focus' + eventNamespace, selector.search, module.event.search.focus); 2916 } 2917 else { 2918 $search.focus(); 2919 } 2920 } 2921 }, 2922 2923 blurSearch: function() { 2924 if( module.has.search() ) { 2925 $search.blur(); 2926 } 2927 }, 2928 2929 forceSelection: function() { 2930 var 2931 $currentlySelected = $item.not(className.filtered).filter('.' + className.selected).eq(0), 2932 $activeItem = $item.not(className.filtered).filter('.' + className.active).eq(0), 2933 $selectedItem = ($currentlySelected.length > 0) 2934 ? $currentlySelected 2935 : $activeItem, 2936 hasSelected = ($selectedItem.length > 0) 2937 ; 2938 if(settings.allowAdditions || (hasSelected && !module.is.multiple())) { 2939 module.debug('Forcing partial selection to selected item', $selectedItem); 2940 module.event.item.click.call($selectedItem, {}, true); 2941 } 2942 else { 2943 module.remove.searchTerm(); 2944 } 2945 }, 2946 2947 change: { 2948 values: function(values) { 2949 if(!settings.allowAdditions) { 2950 module.clear(); 2951 } 2952 module.debug('Creating dropdown with specified values', values); 2953 var menuConfig = {}; 2954 menuConfig[fields.values] = values; 2955 module.setup.menu(menuConfig); 2956 $.each(values, function(index, item) { 2957 if(item.selected == true) { 2958 module.debug('Setting initial selection to', item[fields.value]); 2959 module.set.selected(item[fields.value]); 2960 if(!module.is.multiple()) { 2961 return false; 2962 } 2963 } 2964 }); 2965 2966 if(module.has.selectInput()) { 2967 module.disconnect.selectObserver(); 2968 $input.html(''); 2969 $input.append('<option disabled selected value></option>'); 2970 $.each(values, function(index, item) { 2971 var 2972 value = settings.templates.deQuote(item[fields.value]), 2973 name = settings.templates.escape( 2974 item[fields.name] || '', 2975 settings.preserveHTML 2976 ) 2977 ; 2978 $input.append('<option value="' + value + '">' + name + '</option>'); 2979 }); 2980 module.observe.select(); 2981 } 2982 } 2983 }, 2984 2985 event: { 2986 change: function() { 2987 if(!internalChange) { 2988 module.debug('Input changed, updating selection'); 2989 module.set.selected(); 2990 } 2991 }, 2992 focus: function() { 2993 if(settings.showOnFocus && !activated && module.is.hidden() && !pageLostFocus) { 2994 module.show(); 2995 } 2996 }, 2997 blur: function(event) { 2998 pageLostFocus = (document.activeElement === this); 2999 if(!activated && !pageLostFocus) { 3000 module.remove.activeLabel(); 3001 module.hide(); 3002 } 3003 }, 3004 mousedown: function() { 3005 if(module.is.searchSelection()) { 3006 // prevent menu hiding on immediate re-focus 3007 willRefocus = true; 3008 } 3009 else { 3010 // prevents focus callback from occurring on mousedown 3011 activated = true; 3012 } 3013 }, 3014 mouseup: function() { 3015 if(module.is.searchSelection()) { 3016 // prevent menu hiding on immediate re-focus 3017 willRefocus = false; 3018 } 3019 else { 3020 activated = false; 3021 } 3022 }, 3023 click: function(event) { 3024 var 3025 $target = $(event.target) 3026 ; 3027 // focus search 3028 if($target.is($module)) { 3029 if(!module.is.focusedOnSearch()) { 3030 module.focusSearch(); 3031 } 3032 else { 3033 module.show(); 3034 } 3035 } 3036 }, 3037 search: { 3038 focus: function(event) { 3039 activated = true; 3040 if(module.is.multiple()) { 3041 module.remove.activeLabel(); 3042 } 3043 if(settings.showOnFocus || (event.type !== 'focus' && event.type !== 'focusin')) { 3044 module.search(); 3045 } 3046 }, 3047 blur: function(event) { 3048 pageLostFocus = (document.activeElement === this); 3049 if(module.is.searchSelection() && !willRefocus) { 3050 if(!itemActivated && !pageLostFocus) { 3051 if(settings.forceSelection) { 3052 module.forceSelection(); 3053 } else if(!settings.allowAdditions){ 3054 module.remove.searchTerm(); 3055 } 3056 module.hide(); 3057 } 3058 } 3059 willRefocus = false; 3060 } 3061 }, 3062 clearIcon: { 3063 click: function(event) { 3064 module.clear(); 3065 if(module.is.searchSelection()) { 3066 module.remove.searchTerm(); 3067 } 3068 module.hide(); 3069 event.stopPropagation(); 3070 } 3071 }, 3072 icon: { 3073 click: function(event) { 3074 iconClicked=true; 3075 if(module.has.search()) { 3076 if(!module.is.active()) { 3077 if(settings.showOnFocus){ 3078 module.focusSearch(); 3079 } else { 3080 module.toggle(); 3081 } 3082 } else { 3083 module.blurSearch(); 3084 } 3085 } else { 3086 module.toggle(); 3087 } 3088 } 3089 }, 3090 text: { 3091 focus: function(event) { 3092 activated = true; 3093 module.focusSearch(); 3094 } 3095 }, 3096 input: function(event) { 3097 if(module.is.multiple() || module.is.searchSelection()) { 3098 module.set.filtered(); 3099 } 3100 clearTimeout(module.timer); 3101 module.timer = setTimeout(module.search, settings.delay.search); 3102 }, 3103 label: { 3104 click: function(event) { 3105 var 3106 $label = $(this), 3107 $labels = $module.find(selector.label), 3108 $activeLabels = $labels.filter('.' + className.active), 3109 $nextActive = $label.nextAll('.' + className.active), 3110 $prevActive = $label.prevAll('.' + className.active), 3111 $range = ($nextActive.length > 0) 3112 ? $label.nextUntil($nextActive).add($activeLabels).add($label) 3113 : $label.prevUntil($prevActive).add($activeLabels).add($label) 3114 ; 3115 if(event.shiftKey) { 3116 $activeLabels.removeClass(className.active); 3117 $range.addClass(className.active); 3118 } 3119 else if(event.ctrlKey) { 3120 $label.toggleClass(className.active); 3121 } 3122 else { 3123 $activeLabels.removeClass(className.active); 3124 $label.addClass(className.active); 3125 } 3126 settings.onLabelSelect.apply(this, $labels.filter('.' + className.active)); 3127 } 3128 }, 3129 remove: { 3130 click: function() { 3131 var 3132 $label = $(this).parent() 3133 ; 3134 if( $label.hasClass(className.active) ) { 3135 // remove all selected labels 3136 module.remove.activeLabels(); 3137 } 3138 else { 3139 // remove this label only 3140 module.remove.activeLabels( $label ); 3141 } 3142 } 3143 }, 3144 test: { 3145 toggle: function(event) { 3146 var 3147 toggleBehavior = (module.is.multiple()) 3148 ? module.show 3149 : module.toggle 3150 ; 3151 if(module.is.bubbledLabelClick(event) || module.is.bubbledIconClick(event)) { 3152 return; 3153 } 3154 if( module.determine.eventOnElement(event, toggleBehavior) ) { 3155 event.preventDefault(); 3156 } 3157 }, 3158 touch: function(event) { 3159 module.determine.eventOnElement(event, function() { 3160 if(event.type == 'touchstart') { 3161 module.timer = setTimeout(function() { 3162 module.hide(); 3163 }, settings.delay.touch); 3164 } 3165 else if(event.type == 'touchmove') { 3166 clearTimeout(module.timer); 3167 } 3168 }); 3169 event.stopPropagation(); 3170 }, 3171 hide: function(event) { 3172 if(module.determine.eventInModule(event, module.hide)){ 3173 if(element.id && $(event.target).attr('for') === element.id){ 3174 event.preventDefault(); 3175 } 3176 } 3177 } 3178 }, 3179 class: { 3180 mutation: function(mutations) { 3181 mutations.forEach(function(mutation) { 3182 if(mutation.attributeName === "class") { 3183 module.check.disabled(); 3184 } 3185 }); 3186 } 3187 }, 3188 select: { 3189 mutation: function(mutations) { 3190 module.debug('<select> modified, recreating menu'); 3191 if(module.is.selectMutation(mutations)) { 3192 module.disconnect.selectObserver(); 3193 module.refresh(); 3194 module.setup.select(); 3195 module.set.selected(); 3196 module.observe.select(); 3197 } 3198 } 3199 }, 3200 menu: { 3201 mutation: function(mutations) { 3202 var 3203 mutation = mutations[0], 3204 $addedNode = mutation.addedNodes 3205 ? $(mutation.addedNodes[0]) 3206 : $(false), 3207 $removedNode = mutation.removedNodes 3208 ? $(mutation.removedNodes[0]) 3209 : $(false), 3210 $changedNodes = $addedNode.add($removedNode), 3211 isUserAddition = $changedNodes.is(selector.addition) || $changedNodes.closest(selector.addition).length > 0, 3212 isMessage = $changedNodes.is(selector.message) || $changedNodes.closest(selector.message).length > 0 3213 ; 3214 if(isUserAddition || isMessage) { 3215 module.debug('Updating item selector cache'); 3216 module.refreshItems(); 3217 } 3218 else { 3219 module.debug('Menu modified, updating selector cache'); 3220 module.refresh(); 3221 } 3222 }, 3223 mousedown: function() { 3224 itemActivated = true; 3225 }, 3226 mouseup: function() { 3227 itemActivated = false; 3228 } 3229 }, 3230 item: { 3231 mouseenter: function(event) { 3232 var 3233 $target = $(event.target), 3234 $item = $(this), 3235 $subMenu = $item.children(selector.menu), 3236 $otherMenus = $item.siblings(selector.item).children(selector.menu), 3237 hasSubMenu = ($subMenu.length > 0), 3238 isBubbledEvent = ($subMenu.find($target).length > 0) 3239 ; 3240 if( !isBubbledEvent && hasSubMenu ) { 3241 clearTimeout(module.itemTimer); 3242 module.itemTimer = setTimeout(function() { 3243 module.verbose('Showing sub-menu', $subMenu); 3244 $.each($otherMenus, function() { 3245 module.animate.hide(false, $(this)); 3246 }); 3247 module.animate.show(false, $subMenu); 3248 }, settings.delay.show); 3249 event.preventDefault(); 3250 } 3251 }, 3252 mouseleave: function(event) { 3253 var 3254 $subMenu = $(this).children(selector.menu) 3255 ; 3256 if($subMenu.length > 0) { 3257 clearTimeout(module.itemTimer); 3258 module.itemTimer = setTimeout(function() { 3259 module.verbose('Hiding sub-menu', $subMenu); 3260 module.animate.hide(false, $subMenu); 3261 }, settings.delay.hide); 3262 } 3263 }, 3264 click: function (event, skipRefocus) { 3265 var 3266 $choice = $(this), 3267 $target = (event) 3268 ? $(event.target) 3269 : $(''), 3270 $subMenu = $choice.find(selector.menu), 3271 text = module.get.choiceText($choice), 3272 value = module.get.choiceValue($choice, text), 3273 hasSubMenu = ($subMenu.length > 0), 3274 isBubbledEvent = ($subMenu.find($target).length > 0) 3275 ; 3276 // prevents IE11 bug where menu receives focus even though `tabindex=-1` 3277 if (document.activeElement.tagName.toLowerCase() !== 'input') { 3278 $(document.activeElement).blur(); 3279 } 3280 if(!isBubbledEvent && (!hasSubMenu || settings.allowCategorySelection)) { 3281 if(module.is.searchSelection()) { 3282 if(settings.allowAdditions) { 3283 module.remove.userAddition(); 3284 } 3285 module.remove.searchTerm(); 3286 if(!module.is.focusedOnSearch() && !(skipRefocus == true)) { 3287 module.focusSearch(true); 3288 } 3289 } 3290 if(!settings.useLabels) { 3291 module.remove.filteredItem(); 3292 module.set.scrollPosition($choice); 3293 } 3294 module.determine.selectAction.call(this, text, value); 3295 } 3296 } 3297 }, 3298 3299 document: { 3300 // label selection should occur even when element has no focus 3301 keydown: function(event) { 3302 var 3303 pressedKey = event.which, 3304 isShortcutKey = module.is.inObject(pressedKey, keys) 3305 ; 3306 if(isShortcutKey) { 3307 var 3308 $label = $module.find(selector.label), 3309 $activeLabel = $label.filter('.' + className.active), 3310 activeValue = $activeLabel.data(metadata.value), 3311 labelIndex = $label.index($activeLabel), 3312 labelCount = $label.length, 3313 hasActiveLabel = ($activeLabel.length > 0), 3314 hasMultipleActive = ($activeLabel.length > 1), 3315 isFirstLabel = (labelIndex === 0), 3316 isLastLabel = (labelIndex + 1 == labelCount), 3317 isSearch = module.is.searchSelection(), 3318 isFocusedOnSearch = module.is.focusedOnSearch(), 3319 isFocused = module.is.focused(), 3320 caretAtStart = (isFocusedOnSearch && module.get.caretPosition(false) === 0), 3321 isSelectedSearch = (caretAtStart && module.get.caretPosition(true) !== 0), 3322 $nextLabel 3323 ; 3324 if(isSearch && !hasActiveLabel && !isFocusedOnSearch) { 3325 return; 3326 } 3327 3328 if(pressedKey == keys.leftArrow) { 3329 // activate previous label 3330 if((isFocused || caretAtStart) && !hasActiveLabel) { 3331 module.verbose('Selecting previous label'); 3332 $label.last().addClass(className.active); 3333 } 3334 else if(hasActiveLabel) { 3335 if(!event.shiftKey) { 3336 module.verbose('Selecting previous label'); 3337 $label.removeClass(className.active); 3338 } 3339 else { 3340 module.verbose('Adding previous label to selection'); 3341 } 3342 if(isFirstLabel && !hasMultipleActive) { 3343 $activeLabel.addClass(className.active); 3344 } 3345 else { 3346 $activeLabel.prev(selector.siblingLabel) 3347 .addClass(className.active) 3348 .end() 3349 ; 3350 } 3351 event.preventDefault(); 3352 } 3353 } 3354 else if(pressedKey == keys.rightArrow) { 3355 // activate first label 3356 if(isFocused && !hasActiveLabel) { 3357 $label.first().addClass(className.active); 3358 } 3359 // activate next label 3360 if(hasActiveLabel) { 3361 if(!event.shiftKey) { 3362 module.verbose('Selecting next label'); 3363 $label.removeClass(className.active); 3364 } 3365 else { 3366 module.verbose('Adding next label to selection'); 3367 } 3368 if(isLastLabel) { 3369 if(isSearch) { 3370 if(!isFocusedOnSearch) { 3371 module.focusSearch(); 3372 } 3373 else { 3374 $label.removeClass(className.active); 3375 } 3376 } 3377 else if(hasMultipleActive) { 3378 $activeLabel.next(selector.siblingLabel).addClass(className.active); 3379 } 3380 else { 3381 $activeLabel.addClass(className.active); 3382 } 3383 } 3384 else { 3385 $activeLabel.next(selector.siblingLabel).addClass(className.active); 3386 } 3387 event.preventDefault(); 3388 } 3389 } 3390 else if(pressedKey == keys.deleteKey || pressedKey == keys.backspace) { 3391 if(hasActiveLabel) { 3392 module.verbose('Removing active labels'); 3393 if(isLastLabel) { 3394 if(isSearch && !isFocusedOnSearch) { 3395 module.focusSearch(); 3396 } 3397 } 3398 $activeLabel.last().next(selector.siblingLabel).addClass(className.active); 3399 module.remove.activeLabels($activeLabel); 3400 event.preventDefault(); 3401 } 3402 else if(caretAtStart && !isSelectedSearch && !hasActiveLabel && pressedKey == keys.backspace) { 3403 module.verbose('Removing last label on input backspace'); 3404 $activeLabel = $label.last().addClass(className.active); 3405 module.remove.activeLabels($activeLabel); 3406 } 3407 } 3408 else { 3409 $activeLabel.removeClass(className.active); 3410 } 3411 } 3412 } 3413 }, 3414 3415 keydown: function(event) { 3416 var 3417 pressedKey = event.which, 3418 isShortcutKey = module.is.inObject(pressedKey, keys) 3419 ; 3420 if(isShortcutKey) { 3421 var 3422 $currentlySelected = $item.not(selector.unselectable).filter('.' + className.selected).eq(0), 3423 $activeItem = $menu.children('.' + className.active).eq(0), 3424 $selectedItem = ($currentlySelected.length > 0) 3425 ? $currentlySelected 3426 : $activeItem, 3427 $visibleItems = ($selectedItem.length > 0) 3428 ? $selectedItem.siblings(':not(.' + className.filtered +')').addBack() 3429 : $menu.children(':not(.' + className.filtered +')'), 3430 $subMenu = $selectedItem.children(selector.menu), 3431 $parentMenu = $selectedItem.closest(selector.menu), 3432 inVisibleMenu = ($parentMenu.hasClass(className.visible) || $parentMenu.hasClass(className.animating) || $parentMenu.parent(selector.menu).length > 0), 3433 hasSubMenu = ($subMenu.length> 0), 3434 hasSelectedItem = ($selectedItem.length > 0), 3435 selectedIsSelectable = ($selectedItem.not(selector.unselectable).length > 0), 3436 delimiterPressed = (pressedKey == keys.delimiter && settings.allowAdditions && module.is.multiple()), 3437 isAdditionWithoutMenu = (settings.allowAdditions && settings.hideAdditions && (pressedKey == keys.enter || delimiterPressed) && selectedIsSelectable), 3438 $nextItem, 3439 isSubMenuItem, 3440 newIndex 3441 ; 3442 // allow selection with menu closed 3443 if(isAdditionWithoutMenu) { 3444 module.verbose('Selecting item from keyboard shortcut', $selectedItem); 3445 module.event.item.click.call($selectedItem, event); 3446 if(module.is.searchSelection()) { 3447 module.remove.searchTerm(); 3448 } 3449 if(module.is.multiple()){ 3450 event.preventDefault(); 3451 } 3452 } 3453 3454 // visible menu keyboard shortcuts 3455 if( module.is.visible() ) { 3456 3457 // enter (select or open sub-menu) 3458 if(pressedKey == keys.enter || delimiterPressed) { 3459 if(pressedKey == keys.enter && hasSelectedItem && hasSubMenu && !settings.allowCategorySelection) { 3460 module.verbose('Pressed enter on unselectable category, opening sub menu'); 3461 pressedKey = keys.rightArrow; 3462 } 3463 else if(selectedIsSelectable) { 3464 module.verbose('Selecting item from keyboard shortcut', $selectedItem); 3465 module.event.item.click.call($selectedItem, event); 3466 if(module.is.searchSelection()) { 3467 module.remove.searchTerm(); 3468 if(module.is.multiple()) { 3469 $search.focus(); 3470 } 3471 } 3472 } 3473 event.preventDefault(); 3474 } 3475 3476 // sub-menu actions 3477 if(hasSelectedItem) { 3478 3479 if(pressedKey == keys.leftArrow) { 3480 3481 isSubMenuItem = ($parentMenu[0] !== $menu[0]); 3482 3483 if(isSubMenuItem) { 3484 module.verbose('Left key pressed, closing sub-menu'); 3485 module.animate.hide(false, $parentMenu); 3486 $selectedItem 3487 .removeClass(className.selected) 3488 ; 3489 $parentMenu 3490 .closest(selector.item) 3491 .addClass(className.selected) 3492 ; 3493 event.preventDefault(); 3494 } 3495 } 3496 3497 // right arrow (show sub-menu) 3498 if(pressedKey == keys.rightArrow) { 3499 if(hasSubMenu) { 3500 module.verbose('Right key pressed, opening sub-menu'); 3501 module.animate.show(false, $subMenu); 3502 $selectedItem 3503 .removeClass(className.selected) 3504 ; 3505 $subMenu 3506 .find(selector.item).eq(0) 3507 .addClass(className.selected) 3508 ; 3509 event.preventDefault(); 3510 } 3511 } 3512 } 3513 3514 // up arrow (traverse menu up) 3515 if(pressedKey == keys.upArrow) { 3516 $nextItem = (hasSelectedItem && inVisibleMenu) 3517 ? $selectedItem.prevAll(selector.item + ':not(' + selector.unselectable + ')').eq(0) 3518 : $item.eq(0) 3519 ; 3520 if($visibleItems.index( $nextItem ) < 0) { 3521 module.verbose('Up key pressed but reached top of current menu'); 3522 event.preventDefault(); 3523 return; 3524 } 3525 else { 3526 module.verbose('Up key pressed, changing active item'); 3527 $selectedItem 3528 .removeClass(className.selected) 3529 ; 3530 $nextItem 3531 .addClass(className.selected) 3532 ; 3533 module.set.scrollPosition($nextItem); 3534 if(settings.selectOnKeydown && module.is.single()) { 3535 module.set.selectedItem($nextItem); 3536 } 3537 } 3538 event.preventDefault(); 3539 } 3540 3541 // down arrow (traverse menu down) 3542 if(pressedKey == keys.downArrow) { 3543 $nextItem = (hasSelectedItem && inVisibleMenu) 3544 ? $nextItem = $selectedItem.nextAll(selector.item + ':not(' + selector.unselectable + ')').eq(0) 3545 : $item.eq(0) 3546 ; 3547 if($nextItem.length === 0) { 3548 module.verbose('Down key pressed but reached bottom of current menu'); 3549 event.preventDefault(); 3550 return; 3551 } 3552 else { 3553 module.verbose('Down key pressed, changing active item'); 3554 $item 3555 .removeClass(className.selected) 3556 ; 3557 $nextItem 3558 .addClass(className.selected) 3559 ; 3560 module.set.scrollPosition($nextItem); 3561 if(settings.selectOnKeydown && module.is.single()) { 3562 module.set.selectedItem($nextItem); 3563 } 3564 } 3565 event.preventDefault(); 3566 } 3567 3568 // page down (show next page) 3569 if(pressedKey == keys.pageUp) { 3570 module.scrollPage('up'); 3571 event.preventDefault(); 3572 } 3573 if(pressedKey == keys.pageDown) { 3574 module.scrollPage('down'); 3575 event.preventDefault(); 3576 } 3577 3578 // escape (close menu) 3579 if(pressedKey == keys.escape) { 3580 module.verbose('Escape key pressed, closing dropdown'); 3581 module.hide(); 3582 } 3583 3584 } 3585 else { 3586 // delimiter key 3587 if(delimiterPressed) { 3588 event.preventDefault(); 3589 } 3590 // down arrow (open menu) 3591 if(pressedKey == keys.downArrow && !module.is.visible()) { 3592 module.verbose('Down key pressed, showing dropdown'); 3593 module.show(); 3594 event.preventDefault(); 3595 } 3596 } 3597 } 3598 else { 3599 if( !module.has.search() ) { 3600 module.set.selectedLetter( String.fromCharCode(pressedKey) ); 3601 } 3602 } 3603 } 3604 }, 3605 3606 trigger: { 3607 change: function() { 3608 var 3609 inputElement = $input[0] 3610 ; 3611 if(inputElement) { 3612 var events = document.createEvent('HTMLEvents'); 3613 module.verbose('Triggering native change event'); 3614 events.initEvent('change', true, false); 3615 inputElement.dispatchEvent(events); 3616 } 3617 } 3618 }, 3619 3620 determine: { 3621 selectAction: function(text, value) { 3622 selectActionActive = true; 3623 module.verbose('Determining action', settings.action); 3624 if( $.isFunction( module.action[settings.action] ) ) { 3625 module.verbose('Triggering preset action', settings.action, text, value); 3626 module.action[ settings.action ].call(element, text, value, this); 3627 } 3628 else if( $.isFunction(settings.action) ) { 3629 module.verbose('Triggering user action', settings.action, text, value); 3630 settings.action.call(element, text, value, this); 3631 } 3632 else { 3633 module.error(error.action, settings.action); 3634 } 3635 selectActionActive = false; 3636 }, 3637 eventInModule: function(event, callback) { 3638 var 3639 $target = $(event.target), 3640 inDocument = ($target.closest(document.documentElement).length > 0), 3641 inModule = ($target.closest($module).length > 0) 3642 ; 3643 callback = $.isFunction(callback) 3644 ? callback 3645 : function(){} 3646 ; 3647 if(inDocument && !inModule) { 3648 module.verbose('Triggering event', callback); 3649 callback(); 3650 return true; 3651 } 3652 else { 3653 module.verbose('Event occurred in dropdown, canceling callback'); 3654 return false; 3655 } 3656 }, 3657 eventOnElement: function(event, callback) { 3658 var 3659 $target = $(event.target), 3660 $label = $target.closest(selector.siblingLabel), 3661 inVisibleDOM = document.body.contains(event.target), 3662 notOnLabel = ($module.find($label).length === 0 || !(module.is.multiple() && settings.useLabels)), 3663 notInMenu = ($target.closest($menu).length === 0) 3664 ; 3665 callback = $.isFunction(callback) 3666 ? callback 3667 : function(){} 3668 ; 3669 if(inVisibleDOM && notOnLabel && notInMenu) { 3670 module.verbose('Triggering event', callback); 3671 callback(); 3672 return true; 3673 } 3674 else { 3675 module.verbose('Event occurred in dropdown menu, canceling callback'); 3676 return false; 3677 } 3678 } 3679 }, 3680 3681 action: { 3682 3683 nothing: function() {}, 3684 3685 activate: function(text, value, element) { 3686 value = (value !== undefined) 3687 ? value 3688 : text 3689 ; 3690 if( module.can.activate( $(element) ) ) { 3691 module.set.selected(value, $(element)); 3692 if(!module.is.multiple()) { 3693 module.hideAndClear(); 3694 } 3695 } 3696 }, 3697 3698 select: function(text, value, element) { 3699 value = (value !== undefined) 3700 ? value 3701 : text 3702 ; 3703 if( module.can.activate( $(element) ) ) { 3704 module.set.value(value, text, $(element)); 3705 if(!module.is.multiple()) { 3706 module.hideAndClear(); 3707 } 3708 } 3709 }, 3710 3711 combo: function(text, value, element) { 3712 value = (value !== undefined) 3713 ? value 3714 : text 3715 ; 3716 module.set.selected(value, $(element)); 3717 module.hideAndClear(); 3718 }, 3719 3720 hide: function(text, value, element) { 3721 module.set.value(value, text, $(element)); 3722 module.hideAndClear(); 3723 } 3724 3725 }, 3726 3727 get: { 3728 id: function() { 3729 return id; 3730 }, 3731 defaultText: function() { 3732 return $module.data(metadata.defaultText); 3733 }, 3734 defaultValue: function() { 3735 return $module.data(metadata.defaultValue); 3736 }, 3737 placeholderText: function() { 3738 if(settings.placeholder != 'auto' && typeof settings.placeholder == 'string') { 3739 return settings.placeholder; 3740 } 3741 return $module.data(metadata.placeholderText) || ''; 3742 }, 3743 text: function() { 3744 return settings.preserveHTML ? $text.html() : $text.text(); 3745 }, 3746 query: function() { 3747 return String($search.val()).trim(); 3748 }, 3749 searchWidth: function(value) { 3750 value = (value !== undefined) 3751 ? value 3752 : $search.val() 3753 ; 3754 $sizer.text(value); 3755 // prevent rounding issues 3756 return Math.ceil( $sizer.width() + 1); 3757 }, 3758 selectionCount: function() { 3759 var 3760 values = module.get.values(), 3761 count 3762 ; 3763 count = ( module.is.multiple() ) 3764 ? Array.isArray(values) 3765 ? values.length 3766 : 0 3767 : (module.get.value() !== '') 3768 ? 1 3769 : 0 3770 ; 3771 return count; 3772 }, 3773 transition: function($subMenu) { 3774 return (settings.transition == 'auto') 3775 ? module.is.upward($subMenu) 3776 ? 'slide up' 3777 : 'slide down' 3778 : settings.transition 3779 ; 3780 }, 3781 userValues: function() { 3782 var 3783 values = module.get.values() 3784 ; 3785 if(!values) { 3786 return false; 3787 } 3788 values = Array.isArray(values) 3789 ? values 3790 : [values] 3791 ; 3792 return $.grep(values, function(value) { 3793 return (module.get.item(value) === false); 3794 }); 3795 }, 3796 uniqueArray: function(array) { 3797 return $.grep(array, function (value, index) { 3798 return $.inArray(value, array) === index; 3799 }); 3800 }, 3801 caretPosition: function(returnEndPos) { 3802 var 3803 input = $search.get(0), 3804 range, 3805 rangeLength 3806 ; 3807 if(returnEndPos && 'selectionEnd' in input){ 3808 return input.selectionEnd; 3809 } 3810 else if(!returnEndPos && 'selectionStart' in input) { 3811 return input.selectionStart; 3812 } 3813 if (document.selection) { 3814 input.focus(); 3815 range = document.selection.createRange(); 3816 rangeLength = range.text.length; 3817 if(returnEndPos) { 3818 return rangeLength; 3819 } 3820 range.moveStart('character', -input.value.length); 3821 return range.text.length - rangeLength; 3822 } 3823 }, 3824 value: function() { 3825 var 3826 value = ($input.length > 0) 3827 ? $input.val() 3828 : $module.data(metadata.value), 3829 isEmptyMultiselect = (Array.isArray(value) && value.length === 1 && value[0] === '') 3830 ; 3831 // prevents placeholder element from being selected when multiple 3832 return (value === undefined || isEmptyMultiselect) 3833 ? '' 3834 : value 3835 ; 3836 }, 3837 values: function() { 3838 var 3839 value = module.get.value() 3840 ; 3841 if(value === '') { 3842 return ''; 3843 } 3844 return ( !module.has.selectInput() && module.is.multiple() ) 3845 ? (typeof value == 'string') // delimited string 3846 ? module.escape.htmlEntities(value).split(settings.delimiter) 3847 : '' 3848 : value 3849 ; 3850 }, 3851 remoteValues: function() { 3852 var 3853 values = module.get.values(), 3854 remoteValues = false 3855 ; 3856 if(values) { 3857 if(typeof values == 'string') { 3858 values = [values]; 3859 } 3860 $.each(values, function(index, value) { 3861 var 3862 name = module.read.remoteData(value) 3863 ; 3864 module.verbose('Restoring value from session data', name, value); 3865 if(name) { 3866 if(!remoteValues) { 3867 remoteValues = {}; 3868 } 3869 remoteValues[value] = name; 3870 } 3871 }); 3872 } 3873 return remoteValues; 3874 }, 3875 choiceText: function($choice, preserveHTML) { 3876 preserveHTML = (preserveHTML !== undefined) 3877 ? preserveHTML 3878 : settings.preserveHTML 3879 ; 3880 if($choice) { 3881 if($choice.find(selector.menu).length > 0) { 3882 module.verbose('Retrieving text of element with sub-menu'); 3883 $choice = $choice.clone(); 3884 $choice.find(selector.menu).remove(); 3885 $choice.find(selector.menuIcon).remove(); 3886 } 3887 return ($choice.data(metadata.text) !== undefined) 3888 ? $choice.data(metadata.text) 3889 : (preserveHTML) 3890 ? $choice.html().trim() 3891 : $choice.text().trim() 3892 ; 3893 } 3894 }, 3895 choiceValue: function($choice, choiceText) { 3896 choiceText = choiceText || module.get.choiceText($choice); 3897 if(!$choice) { 3898 return false; 3899 } 3900 return ($choice.data(metadata.value) !== undefined) 3901 ? String( $choice.data(metadata.value) ) 3902 : (typeof choiceText === 'string') 3903 ? String( 3904 settings.ignoreSearchCase 3905 ? choiceText.toLowerCase() 3906 : choiceText 3907 ).trim() 3908 : String(choiceText) 3909 ; 3910 }, 3911 inputEvent: function() { 3912 var 3913 input = $search[0] 3914 ; 3915 if(input) { 3916 return (input.oninput !== undefined) 3917 ? 'input' 3918 : (input.onpropertychange !== undefined) 3919 ? 'propertychange' 3920 : 'keyup' 3921 ; 3922 } 3923 return false; 3924 }, 3925 selectValues: function() { 3926 var 3927 select = {}, 3928 oldGroup = [], 3929 values = [] 3930 ; 3931 $module 3932 .find('option') 3933 .each(function() { 3934 var 3935 $option = $(this), 3936 name = $option.html(), 3937 disabled = $option.attr('disabled'), 3938 value = ( $option.attr('value') !== undefined ) 3939 ? $option.attr('value') 3940 : name, 3941 text = ( $option.data(metadata.text) !== undefined ) 3942 ? $option.data(metadata.text) 3943 : name, 3944 group = $option.parent('optgroup') 3945 ; 3946 if(settings.placeholder === 'auto' && value === '') { 3947 select.placeholder = name; 3948 } 3949 else { 3950 if(group.length !== oldGroup.length || group[0] !== oldGroup[0]) { 3951 values.push({ 3952 type: 'header', 3953 divider: settings.headerDivider, 3954 name: group.attr('label') || '' 3955 }); 3956 oldGroup = group; 3957 } 3958 values.push({ 3959 name : name, 3960 value : value, 3961 text : text, 3962 disabled : disabled 3963 }); 3964 } 3965 }) 3966 ; 3967 if(settings.placeholder && settings.placeholder !== 'auto') { 3968 module.debug('Setting placeholder value to', settings.placeholder); 3969 select.placeholder = settings.placeholder; 3970 } 3971 if(settings.sortSelect) { 3972 if(settings.sortSelect === true) { 3973 values.sort(function(a, b) { 3974 return a.name.localeCompare(b.name); 3975 }); 3976 } else if(settings.sortSelect === 'natural') { 3977 values.sort(function(a, b) { 3978 return (a.name.toLowerCase().localeCompare(b.name.toLowerCase())); 3979 }); 3980 } else if($.isFunction(settings.sortSelect)) { 3981 values.sort(settings.sortSelect); 3982 } 3983 select[fields.values] = values; 3984 module.debug('Retrieved and sorted values from select', select); 3985 } 3986 else { 3987 select[fields.values] = values; 3988 module.debug('Retrieved values from select', select); 3989 } 3990 return select; 3991 }, 3992 activeItem: function() { 3993 return $item.filter('.' + className.active); 3994 }, 3995 selectedItem: function() { 3996 var 3997 $selectedItem = $item.not(selector.unselectable).filter('.' + className.selected) 3998 ; 3999 return ($selectedItem.length > 0) 4000 ? $selectedItem 4001 : $item.eq(0) 4002 ; 4003 }, 4004 itemWithAdditions: function(value) { 4005 var 4006 $items = module.get.item(value), 4007 $userItems = module.create.userChoice(value), 4008 hasUserItems = ($userItems && $userItems.length > 0) 4009 ; 4010 if(hasUserItems) { 4011 $items = ($items.length > 0) 4012 ? $items.add($userItems) 4013 : $userItems 4014 ; 4015 } 4016 return $items; 4017 }, 4018 item: function(value, strict) { 4019 var 4020 $selectedItem = false, 4021 shouldSearch, 4022 isMultiple 4023 ; 4024 value = (value !== undefined) 4025 ? value 4026 : ( module.get.values() !== undefined) 4027 ? module.get.values() 4028 : module.get.text() 4029 ; 4030 isMultiple = (module.is.multiple() && Array.isArray(value)); 4031 shouldSearch = (isMultiple) 4032 ? (value.length > 0) 4033 : (value !== undefined && value !== null) 4034 ; 4035 strict = (value === '' || value === false || value === true) 4036 ? true 4037 : strict || false 4038 ; 4039 if(shouldSearch) { 4040 $item 4041 .each(function() { 4042 var 4043 $choice = $(this), 4044 optionText = module.get.choiceText($choice), 4045 optionValue = module.get.choiceValue($choice, optionText) 4046 ; 4047 // safe early exit 4048 if(optionValue === null || optionValue === undefined) { 4049 return; 4050 } 4051 if(isMultiple) { 4052 if($.inArray(module.escape.htmlEntities(String(optionValue)), value.map(function(v){return String(v);})) !== -1) { 4053 $selectedItem = ($selectedItem) 4054 ? $selectedItem.add($choice) 4055 : $choice 4056 ; 4057 } 4058 } 4059 else if(strict) { 4060 module.verbose('Ambiguous dropdown value using strict type check', $choice, value); 4061 if( optionValue === value) { 4062 $selectedItem = $choice; 4063 return true; 4064 } 4065 } 4066 else { 4067 if(settings.ignoreCase) { 4068 optionValue = optionValue.toLowerCase(); 4069 value = value.toLowerCase(); 4070 } 4071 if(module.escape.htmlEntities(String(optionValue)) === module.escape.htmlEntities(String(value))) { 4072 module.verbose('Found select item by value', optionValue, value); 4073 $selectedItem = $choice; 4074 return true; 4075 } 4076 } 4077 }) 4078 ; 4079 } 4080 return $selectedItem; 4081 } 4082 }, 4083 4084 check: { 4085 maxSelections: function(selectionCount) { 4086 if(settings.maxSelections) { 4087 selectionCount = (selectionCount !== undefined) 4088 ? selectionCount 4089 : module.get.selectionCount() 4090 ; 4091 if(selectionCount >= settings.maxSelections) { 4092 module.debug('Maximum selection count reached'); 4093 if(settings.useLabels) { 4094 $item.addClass(className.filtered); 4095 module.add.message(message.maxSelections); 4096 } 4097 return true; 4098 } 4099 else { 4100 module.verbose('No longer at maximum selection count'); 4101 module.remove.message(); 4102 module.remove.filteredItem(); 4103 if(module.is.searchSelection()) { 4104 module.filterItems(); 4105 } 4106 return false; 4107 } 4108 } 4109 return true; 4110 }, 4111 disabled: function(){ 4112 $search.attr('tabindex',module.is.disabled() ? -1 : 0); 4113 } 4114 }, 4115 4116 restore: { 4117 defaults: function(preventChangeTrigger) { 4118 module.clear(preventChangeTrigger); 4119 module.restore.defaultText(); 4120 module.restore.defaultValue(); 4121 }, 4122 defaultText: function() { 4123 var 4124 defaultText = module.get.defaultText(), 4125 placeholderText = module.get.placeholderText 4126 ; 4127 if(defaultText === placeholderText) { 4128 module.debug('Restoring default placeholder text', defaultText); 4129 module.set.placeholderText(defaultText); 4130 } 4131 else { 4132 module.debug('Restoring default text', defaultText); 4133 module.set.text(defaultText); 4134 } 4135 }, 4136 placeholderText: function() { 4137 module.set.placeholderText(); 4138 }, 4139 defaultValue: function() { 4140 var 4141 defaultValue = module.get.defaultValue() 4142 ; 4143 if(defaultValue !== undefined) { 4144 module.debug('Restoring default value', defaultValue); 4145 if(defaultValue !== '') { 4146 module.set.value(defaultValue); 4147 module.set.selected(); 4148 } 4149 else { 4150 module.remove.activeItem(); 4151 module.remove.selectedItem(); 4152 } 4153 } 4154 }, 4155 labels: function() { 4156 if(settings.allowAdditions) { 4157 if(!settings.useLabels) { 4158 module.error(error.labels); 4159 settings.useLabels = true; 4160 } 4161 module.debug('Restoring selected values'); 4162 module.create.userLabels(); 4163 } 4164 module.check.maxSelections(); 4165 }, 4166 selected: function() { 4167 module.restore.values(); 4168 if(module.is.multiple()) { 4169 module.debug('Restoring previously selected values and labels'); 4170 module.restore.labels(); 4171 } 4172 else { 4173 module.debug('Restoring previously selected values'); 4174 } 4175 }, 4176 values: function() { 4177 // prevents callbacks from occurring on initial load 4178 module.set.initialLoad(); 4179 if(settings.apiSettings && settings.saveRemoteData && module.get.remoteValues()) { 4180 module.restore.remoteValues(); 4181 } 4182 else { 4183 module.set.selected(); 4184 } 4185 var value = module.get.value(); 4186 if(value && value !== '' && !(Array.isArray(value) && value.length === 0)) { 4187 $input.removeClass(className.noselection); 4188 } else { 4189 $input.addClass(className.noselection); 4190 } 4191 module.remove.initialLoad(); 4192 }, 4193 remoteValues: function() { 4194 var 4195 values = module.get.remoteValues() 4196 ; 4197 module.debug('Recreating selected from session data', values); 4198 if(values) { 4199 if( module.is.single() ) { 4200 $.each(values, function(value, name) { 4201 module.set.text(name); 4202 }); 4203 } 4204 else { 4205 $.each(values, function(value, name) { 4206 module.add.label(value, name); 4207 }); 4208 } 4209 } 4210 } 4211 }, 4212 4213 read: { 4214 remoteData: function(value) { 4215 var 4216 name 4217 ; 4218 if(window.Storage === undefined) { 4219 module.error(error.noStorage); 4220 return; 4221 } 4222 name = sessionStorage.getItem(value); 4223 return (name !== undefined) 4224 ? name 4225 : false 4226 ; 4227 } 4228 }, 4229 4230 save: { 4231 defaults: function() { 4232 module.save.defaultText(); 4233 module.save.placeholderText(); 4234 module.save.defaultValue(); 4235 }, 4236 defaultValue: function() { 4237 var 4238 value = module.get.value() 4239 ; 4240 module.verbose('Saving default value as', value); 4241 $module.data(metadata.defaultValue, value); 4242 }, 4243 defaultText: function() { 4244 var 4245 text = module.get.text() 4246 ; 4247 module.verbose('Saving default text as', text); 4248 $module.data(metadata.defaultText, text); 4249 }, 4250 placeholderText: function() { 4251 var 4252 text 4253 ; 4254 if(settings.placeholder !== false && $text.hasClass(className.placeholder)) { 4255 text = module.get.text(); 4256 module.verbose('Saving placeholder text as', text); 4257 $module.data(metadata.placeholderText, text); 4258 } 4259 }, 4260 remoteData: function(name, value) { 4261 if(window.Storage === undefined) { 4262 module.error(error.noStorage); 4263 return; 4264 } 4265 module.verbose('Saving remote data to session storage', value, name); 4266 sessionStorage.setItem(value, name); 4267 } 4268 }, 4269 4270 clear: function(preventChangeTrigger) { 4271 if(module.is.multiple() && settings.useLabels) { 4272 module.remove.labels(); 4273 } 4274 else { 4275 module.remove.activeItem(); 4276 module.remove.selectedItem(); 4277 module.remove.filteredItem(); 4278 } 4279 module.set.placeholderText(); 4280 module.clearValue(preventChangeTrigger); 4281 }, 4282 4283 clearValue: function(preventChangeTrigger) { 4284 module.set.value('', null, null, preventChangeTrigger); 4285 }, 4286 4287 scrollPage: function(direction, $selectedItem) { 4288 var 4289 $currentItem = $selectedItem || module.get.selectedItem(), 4290 $menu = $currentItem.closest(selector.menu), 4291 menuHeight = $menu.outerHeight(), 4292 currentScroll = $menu.scrollTop(), 4293 itemHeight = $item.eq(0).outerHeight(), 4294 itemsPerPage = Math.floor(menuHeight / itemHeight), 4295 maxScroll = $menu.prop('scrollHeight'), 4296 newScroll = (direction == 'up') 4297 ? currentScroll - (itemHeight * itemsPerPage) 4298 : currentScroll + (itemHeight * itemsPerPage), 4299 $selectableItem = $item.not(selector.unselectable), 4300 isWithinRange, 4301 $nextSelectedItem, 4302 elementIndex 4303 ; 4304 elementIndex = (direction == 'up') 4305 ? $selectableItem.index($currentItem) - itemsPerPage 4306 : $selectableItem.index($currentItem) + itemsPerPage 4307 ; 4308 isWithinRange = (direction == 'up') 4309 ? (elementIndex >= 0) 4310 : (elementIndex < $selectableItem.length) 4311 ; 4312 $nextSelectedItem = (isWithinRange) 4313 ? $selectableItem.eq(elementIndex) 4314 : (direction == 'up') 4315 ? $selectableItem.first() 4316 : $selectableItem.last() 4317 ; 4318 if($nextSelectedItem.length > 0) { 4319 module.debug('Scrolling page', direction, $nextSelectedItem); 4320 $currentItem 4321 .removeClass(className.selected) 4322 ; 4323 $nextSelectedItem 4324 .addClass(className.selected) 4325 ; 4326 if(settings.selectOnKeydown && module.is.single()) { 4327 module.set.selectedItem($nextSelectedItem); 4328 } 4329 $menu 4330 .scrollTop(newScroll) 4331 ; 4332 } 4333 }, 4334 4335 set: { 4336 filtered: function() { 4337 var 4338 isMultiple = module.is.multiple(), 4339 isSearch = module.is.searchSelection(), 4340 isSearchMultiple = (isMultiple && isSearch), 4341 searchValue = (isSearch) 4342 ? module.get.query() 4343 : '', 4344 hasSearchValue = (typeof searchValue === 'string' && searchValue.length > 0), 4345 searchWidth = module.get.searchWidth(), 4346 valueIsSet = searchValue !== '' 4347 ; 4348 if(isMultiple && hasSearchValue) { 4349 module.verbose('Adjusting input width', searchWidth, settings.glyphWidth); 4350 $search.css('width', searchWidth); 4351 } 4352 if(hasSearchValue || (isSearchMultiple && valueIsSet)) { 4353 module.verbose('Hiding placeholder text'); 4354 $text.addClass(className.filtered); 4355 } 4356 else if(!isMultiple || (isSearchMultiple && !valueIsSet)) { 4357 module.verbose('Showing placeholder text'); 4358 $text.removeClass(className.filtered); 4359 } 4360 }, 4361 empty: function() { 4362 $module.addClass(className.empty); 4363 }, 4364 loading: function() { 4365 $module.addClass(className.loading); 4366 }, 4367 placeholderText: function(text) { 4368 text = text || module.get.placeholderText(); 4369 module.debug('Setting placeholder text', text); 4370 module.set.text(text); 4371 $text.addClass(className.placeholder); 4372 }, 4373 tabbable: function() { 4374 if( module.is.searchSelection() ) { 4375 module.debug('Added tabindex to searchable dropdown'); 4376 $search 4377 .val('') 4378 ; 4379 module.check.disabled(); 4380 $menu 4381 .attr('tabindex', -1) 4382 ; 4383 } 4384 else { 4385 module.debug('Added tabindex to dropdown'); 4386 if( $module.attr('tabindex') === undefined) { 4387 $module 4388 .attr('tabindex', 0) 4389 ; 4390 $menu 4391 .attr('tabindex', -1) 4392 ; 4393 } 4394 } 4395 }, 4396 initialLoad: function() { 4397 module.verbose('Setting initial load'); 4398 initialLoad = true; 4399 }, 4400 activeItem: function($item) { 4401 if( settings.allowAdditions && $item.filter(selector.addition).length > 0 ) { 4402 $item.addClass(className.filtered); 4403 } 4404 else { 4405 $item.addClass(className.active); 4406 } 4407 }, 4408 partialSearch: function(text) { 4409 var 4410 length = module.get.query().length 4411 ; 4412 $search.val( text.substr(0, length)); 4413 }, 4414 scrollPosition: function($item, forceScroll) { 4415 var 4416 edgeTolerance = 5, 4417 $menu, 4418 hasActive, 4419 offset, 4420 itemHeight, 4421 itemOffset, 4422 menuOffset, 4423 menuScroll, 4424 menuHeight, 4425 abovePage, 4426 belowPage 4427 ; 4428 4429 $item = $item || module.get.selectedItem(); 4430 $menu = $item.closest(selector.menu); 4431 hasActive = ($item && $item.length > 0); 4432 forceScroll = (forceScroll !== undefined) 4433 ? forceScroll 4434 : false 4435 ; 4436 if(module.get.activeItem().length === 0){ 4437 forceScroll = false; 4438 } 4439 if($item && $menu.length > 0 && hasActive) { 4440 itemOffset = $item.position().top; 4441 4442 $menu.addClass(className.loading); 4443 menuScroll = $menu.scrollTop(); 4444 menuOffset = $menu.offset().top; 4445 itemOffset = $item.offset().top; 4446 offset = menuScroll - menuOffset + itemOffset; 4447 if(!forceScroll) { 4448 menuHeight = $menu.height(); 4449 belowPage = menuScroll + menuHeight < (offset + edgeTolerance); 4450 abovePage = ((offset - edgeTolerance) < menuScroll); 4451 } 4452 module.debug('Scrolling to active item', offset); 4453 if(forceScroll || abovePage || belowPage) { 4454 $menu.scrollTop(offset); 4455 } 4456 $menu.removeClass(className.loading); 4457 } 4458 }, 4459 text: function(text) { 4460 if(settings.action === 'combo') { 4461 module.debug('Changing combo button text', text, $combo); 4462 if(settings.preserveHTML) { 4463 $combo.html(text); 4464 } 4465 else { 4466 $combo.text(text); 4467 } 4468 } 4469 else if(settings.action === 'activate') { 4470 if(text !== module.get.placeholderText()) { 4471 $text.removeClass(className.placeholder); 4472 } 4473 module.debug('Changing text', text, $text); 4474 $text 4475 .removeClass(className.filtered) 4476 ; 4477 if(settings.preserveHTML) { 4478 $text.html(text); 4479 } 4480 else { 4481 $text.text(text); 4482 } 4483 } 4484 }, 4485 selectedItem: function($item) { 4486 var 4487 value = module.get.choiceValue($item), 4488 searchText = module.get.choiceText($item, false), 4489 text = module.get.choiceText($item, true) 4490 ; 4491 module.debug('Setting user selection to item', $item); 4492 module.remove.activeItem(); 4493 module.set.partialSearch(searchText); 4494 module.set.activeItem($item); 4495 module.set.selected(value, $item); 4496 module.set.text(text); 4497 }, 4498 selectedLetter: function(letter) { 4499 var 4500 $selectedItem = $item.filter('.' + className.selected), 4501 alreadySelectedLetter = $selectedItem.length > 0 && module.has.firstLetter($selectedItem, letter), 4502 $nextValue = false, 4503 $nextItem 4504 ; 4505 // check next of same letter 4506 if(alreadySelectedLetter) { 4507 $nextItem = $selectedItem.nextAll($item).eq(0); 4508 if( module.has.firstLetter($nextItem, letter) ) { 4509 $nextValue = $nextItem; 4510 } 4511 } 4512 // check all values 4513 if(!$nextValue) { 4514 $item 4515 .each(function(){ 4516 if(module.has.firstLetter($(this), letter)) { 4517 $nextValue = $(this); 4518 return false; 4519 } 4520 }) 4521 ; 4522 } 4523 // set next value 4524 if($nextValue) { 4525 module.verbose('Scrolling to next value with letter', letter); 4526 module.set.scrollPosition($nextValue); 4527 $selectedItem.removeClass(className.selected); 4528 $nextValue.addClass(className.selected); 4529 if(settings.selectOnKeydown && module.is.single()) { 4530 module.set.selectedItem($nextValue); 4531 } 4532 } 4533 }, 4534 direction: function($menu) { 4535 if(settings.direction == 'auto') { 4536 // reset position, remove upward if it's base menu 4537 if (!$menu) { 4538 module.remove.upward(); 4539 } else if (module.is.upward($menu)) { 4540 //we need make sure when make assertion openDownward for $menu, $menu does not have upward class 4541 module.remove.upward($menu); 4542 } 4543 4544 if(module.can.openDownward($menu)) { 4545 module.remove.upward($menu); 4546 } 4547 else { 4548 module.set.upward($menu); 4549 } 4550 if(!module.is.leftward($menu) && !module.can.openRightward($menu)) { 4551 module.set.leftward($menu); 4552 } 4553 } 4554 else if(settings.direction == 'upward') { 4555 module.set.upward($menu); 4556 } 4557 }, 4558 upward: function($currentMenu) { 4559 var $element = $currentMenu || $module; 4560 $element.addClass(className.upward); 4561 }, 4562 leftward: function($currentMenu) { 4563 var $element = $currentMenu || $menu; 4564 $element.addClass(className.leftward); 4565 }, 4566 value: function(value, text, $selected, preventChangeTrigger) { 4567 if(value !== undefined && value !== '' && !(Array.isArray(value) && value.length === 0)) { 4568 $input.removeClass(className.noselection); 4569 } else { 4570 $input.addClass(className.noselection); 4571 } 4572 var 4573 escapedValue = module.escape.value(value), 4574 hasInput = ($input.length > 0), 4575 currentValue = module.get.values(), 4576 stringValue = (value !== undefined) 4577 ? String(value) 4578 : value, 4579 newValue 4580 ; 4581 if(hasInput) { 4582 if(!settings.allowReselection && stringValue == currentValue) { 4583 module.verbose('Skipping value update already same value', value, currentValue); 4584 if(!module.is.initialLoad()) { 4585 return; 4586 } 4587 } 4588 4589 if( module.is.single() && module.has.selectInput() && module.can.extendSelect() ) { 4590 module.debug('Adding user option', value); 4591 module.add.optionValue(value); 4592 } 4593 module.debug('Updating input value', escapedValue, currentValue); 4594 internalChange = true; 4595 $input 4596 .val(escapedValue) 4597 ; 4598 if(settings.fireOnInit === false && module.is.initialLoad()) { 4599 module.debug('Input native change event ignored on initial load'); 4600 } 4601 else if(preventChangeTrigger !== true) { 4602 module.trigger.change(); 4603 } 4604 internalChange = false; 4605 } 4606 else { 4607 module.verbose('Storing value in metadata', escapedValue, $input); 4608 if(escapedValue !== currentValue) { 4609 $module.data(metadata.value, stringValue); 4610 } 4611 } 4612 if(settings.fireOnInit === false && module.is.initialLoad()) { 4613 module.verbose('No callback on initial load', settings.onChange); 4614 } 4615 else if(preventChangeTrigger !== true) { 4616 settings.onChange.call(element, value, text, $selected); 4617 } 4618 }, 4619 active: function() { 4620 $module 4621 .addClass(className.active) 4622 ; 4623 }, 4624 multiple: function() { 4625 $module.addClass(className.multiple); 4626 }, 4627 visible: function() { 4628 $module.addClass(className.visible); 4629 }, 4630 exactly: function(value, $selectedItem) { 4631 module.debug('Setting selected to exact values'); 4632 module.clear(); 4633 module.set.selected(value, $selectedItem); 4634 }, 4635 selected: function(value, $selectedItem) { 4636 var 4637 isMultiple = module.is.multiple() 4638 ; 4639 $selectedItem = (settings.allowAdditions) 4640 ? $selectedItem || module.get.itemWithAdditions(value) 4641 : $selectedItem || module.get.item(value) 4642 ; 4643 if(!$selectedItem) { 4644 return; 4645 } 4646 module.debug('Setting selected menu item to', $selectedItem); 4647 if(module.is.multiple()) { 4648 module.remove.searchWidth(); 4649 } 4650 if(module.is.single()) { 4651 module.remove.activeItem(); 4652 module.remove.selectedItem(); 4653 } 4654 else if(settings.useLabels) { 4655 module.remove.selectedItem(); 4656 } 4657 // select each item 4658 $selectedItem 4659 .each(function() { 4660 var 4661 $selected = $(this), 4662 selectedText = module.get.choiceText($selected), 4663 selectedValue = module.get.choiceValue($selected, selectedText), 4664 4665 isFiltered = $selected.hasClass(className.filtered), 4666 isActive = $selected.hasClass(className.active), 4667 isUserValue = $selected.hasClass(className.addition), 4668 shouldAnimate = (isMultiple && $selectedItem.length == 1) 4669 ; 4670 if(isMultiple) { 4671 if(!isActive || isUserValue) { 4672 if(settings.apiSettings && settings.saveRemoteData) { 4673 module.save.remoteData(selectedText, selectedValue); 4674 } 4675 if(settings.useLabels) { 4676 module.add.label(selectedValue, selectedText, shouldAnimate); 4677 module.add.value(selectedValue, selectedText, $selected); 4678 module.set.activeItem($selected); 4679 module.filterActive(); 4680 module.select.nextAvailable($selectedItem); 4681 } 4682 else { 4683 module.add.value(selectedValue, selectedText, $selected); 4684 module.set.text(module.add.variables(message.count)); 4685 module.set.activeItem($selected); 4686 } 4687 } 4688 else if(!isFiltered && (settings.useLabels || selectActionActive)) { 4689 module.debug('Selected active value, removing label'); 4690 module.remove.selected(selectedValue); 4691 } 4692 } 4693 else { 4694 if(settings.apiSettings && settings.saveRemoteData) { 4695 module.save.remoteData(selectedText, selectedValue); 4696 } 4697 module.set.text(selectedText); 4698 module.set.value(selectedValue, selectedText, $selected); 4699 $selected 4700 .addClass(className.active) 4701 .addClass(className.selected) 4702 ; 4703 } 4704 }) 4705 ; 4706 module.remove.searchTerm(); 4707 } 4708 }, 4709 4710 add: { 4711 label: function(value, text, shouldAnimate) { 4712 var 4713 $next = module.is.searchSelection() 4714 ? $search 4715 : $text, 4716 escapedValue = module.escape.value(value), 4717 $label 4718 ; 4719 if(settings.ignoreCase) { 4720 escapedValue = escapedValue.toLowerCase(); 4721 } 4722 $label = $('<a />') 4723 .addClass(className.label) 4724 .attr('data-' + metadata.value, escapedValue) 4725 .html(templates.label(escapedValue, text, settings.preserveHTML, settings.className)) 4726 ; 4727 $label = settings.onLabelCreate.call($label, escapedValue, text); 4728 4729 if(module.has.label(value)) { 4730 module.debug('User selection already exists, skipping', escapedValue); 4731 return; 4732 } 4733 if(settings.label.variation) { 4734 $label.addClass(settings.label.variation); 4735 } 4736 if(shouldAnimate === true) { 4737 module.debug('Animating in label', $label); 4738 $label 4739 .addClass(className.hidden) 4740 .insertBefore($next) 4741 .transition({ 4742 animation : settings.label.transition, 4743 debug : settings.debug, 4744 verbose : settings.verbose, 4745 duration : settings.label.duration 4746 }) 4747 ; 4748 } 4749 else { 4750 module.debug('Adding selection label', $label); 4751 $label 4752 .insertBefore($next) 4753 ; 4754 } 4755 }, 4756 message: function(message) { 4757 var 4758 $message = $menu.children(selector.message), 4759 html = settings.templates.message(module.add.variables(message)) 4760 ; 4761 if($message.length > 0) { 4762 $message 4763 .html(html) 4764 ; 4765 } 4766 else { 4767 $message = $('<div/>') 4768 .html(html) 4769 .addClass(className.message) 4770 .appendTo($menu) 4771 ; 4772 } 4773 }, 4774 optionValue: function(value) { 4775 var 4776 escapedValue = module.escape.value(value), 4777 $option = $input.find('option[value="' + module.escape.string(escapedValue) + '"]'), 4778 hasOption = ($option.length > 0) 4779 ; 4780 if(hasOption) { 4781 return; 4782 } 4783 // temporarily disconnect observer 4784 module.disconnect.selectObserver(); 4785 if( module.is.single() ) { 4786 module.verbose('Removing previous user addition'); 4787 $input.find('option.' + className.addition).remove(); 4788 } 4789 $('<option/>') 4790 .prop('value', escapedValue) 4791 .addClass(className.addition) 4792 .html(value) 4793 .appendTo($input) 4794 ; 4795 module.verbose('Adding user addition as an <option>', value); 4796 module.observe.select(); 4797 }, 4798 userSuggestion: function(value) { 4799 var 4800 $addition = $menu.children(selector.addition), 4801 $existingItem = module.get.item(value), 4802 alreadyHasValue = $existingItem && $existingItem.not(selector.addition).length, 4803 hasUserSuggestion = $addition.length > 0, 4804 html 4805 ; 4806 if(settings.useLabels && module.has.maxSelections()) { 4807 return; 4808 } 4809 if(value === '' || alreadyHasValue) { 4810 $addition.remove(); 4811 return; 4812 } 4813 if(hasUserSuggestion) { 4814 $addition 4815 .data(metadata.value, value) 4816 .data(metadata.text, value) 4817 .attr('data-' + metadata.value, value) 4818 .attr('data-' + metadata.text, value) 4819 .removeClass(className.filtered) 4820 ; 4821 if(!settings.hideAdditions) { 4822 html = settings.templates.addition( module.add.variables(message.addResult, value) ); 4823 $addition 4824 .html(html) 4825 ; 4826 } 4827 module.verbose('Replacing user suggestion with new value', $addition); 4828 } 4829 else { 4830 $addition = module.create.userChoice(value); 4831 $addition 4832 .prependTo($menu) 4833 ; 4834 module.verbose('Adding item choice to menu corresponding with user choice addition', $addition); 4835 } 4836 if(!settings.hideAdditions || module.is.allFiltered()) { 4837 $addition 4838 .addClass(className.selected) 4839 .siblings() 4840 .removeClass(className.selected) 4841 ; 4842 } 4843 module.refreshItems(); 4844 }, 4845 variables: function(message, term) { 4846 var 4847 hasCount = (message.search('{count}') !== -1), 4848 hasMaxCount = (message.search('{maxCount}') !== -1), 4849 hasTerm = (message.search('{term}') !== -1), 4850 count, 4851 query 4852 ; 4853 module.verbose('Adding templated variables to message', message); 4854 if(hasCount) { 4855 count = module.get.selectionCount(); 4856 message = message.replace('{count}', count); 4857 } 4858 if(hasMaxCount) { 4859 count = module.get.selectionCount(); 4860 message = message.replace('{maxCount}', settings.maxSelections); 4861 } 4862 if(hasTerm) { 4863 query = term || module.get.query(); 4864 message = message.replace('{term}', query); 4865 } 4866 return message; 4867 }, 4868 value: function(addedValue, addedText, $selectedItem) { 4869 var 4870 currentValue = module.get.values(), 4871 newValue 4872 ; 4873 if(module.has.value(addedValue)) { 4874 module.debug('Value already selected'); 4875 return; 4876 } 4877 if(addedValue === '') { 4878 module.debug('Cannot select blank values from multiselect'); 4879 return; 4880 } 4881 // extend current array 4882 if(Array.isArray(currentValue)) { 4883 newValue = currentValue.concat([addedValue]); 4884 newValue = module.get.uniqueArray(newValue); 4885 } 4886 else { 4887 newValue = [addedValue]; 4888 } 4889 // add values 4890 if( module.has.selectInput() ) { 4891 if(module.can.extendSelect()) { 4892 module.debug('Adding value to select', addedValue, newValue, $input); 4893 module.add.optionValue(addedValue); 4894 } 4895 } 4896 else { 4897 newValue = newValue.join(settings.delimiter); 4898 module.debug('Setting hidden input to delimited value', newValue, $input); 4899 } 4900 4901 if(settings.fireOnInit === false && module.is.initialLoad()) { 4902 module.verbose('Skipping onadd callback on initial load', settings.onAdd); 4903 } 4904 else { 4905 settings.onAdd.call(element, addedValue, addedText, $selectedItem); 4906 } 4907 module.set.value(newValue, addedText, $selectedItem); 4908 module.check.maxSelections(); 4909 }, 4910 }, 4911 4912 remove: { 4913 active: function() { 4914 $module.removeClass(className.active); 4915 }, 4916 activeLabel: function() { 4917 $module.find(selector.label).removeClass(className.active); 4918 }, 4919 empty: function() { 4920 $module.removeClass(className.empty); 4921 }, 4922 loading: function() { 4923 $module.removeClass(className.loading); 4924 }, 4925 initialLoad: function() { 4926 initialLoad = false; 4927 }, 4928 upward: function($currentMenu) { 4929 var $element = $currentMenu || $module; 4930 $element.removeClass(className.upward); 4931 }, 4932 leftward: function($currentMenu) { 4933 var $element = $currentMenu || $menu; 4934 $element.removeClass(className.leftward); 4935 }, 4936 visible: function() { 4937 $module.removeClass(className.visible); 4938 }, 4939 activeItem: function() { 4940 $item.removeClass(className.active); 4941 }, 4942 filteredItem: function() { 4943 if(settings.useLabels && module.has.maxSelections() ) { 4944 return; 4945 } 4946 if(settings.useLabels && module.is.multiple()) { 4947 $item.not('.' + className.active).removeClass(className.filtered); 4948 } 4949 else { 4950 $item.removeClass(className.filtered); 4951 } 4952 if(settings.hideDividers) { 4953 $divider.removeClass(className.hidden); 4954 } 4955 module.remove.empty(); 4956 }, 4957 optionValue: function(value) { 4958 var 4959 escapedValue = module.escape.value(value), 4960 $option = $input.find('option[value="' + module.escape.string(escapedValue) + '"]'), 4961 hasOption = ($option.length > 0) 4962 ; 4963 if(!hasOption || !$option.hasClass(className.addition)) { 4964 return; 4965 } 4966 // temporarily disconnect observer 4967 if(selectObserver) { 4968 selectObserver.disconnect(); 4969 module.verbose('Temporarily disconnecting mutation observer'); 4970 } 4971 $option.remove(); 4972 module.verbose('Removing user addition as an <option>', escapedValue); 4973 if(selectObserver) { 4974 selectObserver.observe($input[0], { 4975 childList : true, 4976 subtree : true 4977 }); 4978 } 4979 }, 4980 message: function() { 4981 $menu.children(selector.message).remove(); 4982 }, 4983 searchWidth: function() { 4984 $search.css('width', ''); 4985 }, 4986 searchTerm: function() { 4987 module.verbose('Cleared search term'); 4988 $search.val(''); 4989 module.set.filtered(); 4990 }, 4991 userAddition: function() { 4992 $item.filter(selector.addition).remove(); 4993 }, 4994 selected: function(value, $selectedItem) { 4995 $selectedItem = (settings.allowAdditions) 4996 ? $selectedItem || module.get.itemWithAdditions(value) 4997 : $selectedItem || module.get.item(value) 4998 ; 4999 5000 if(!$selectedItem) { 5001 return false; 5002 } 5003 5004 $selectedItem 5005 .each(function() { 5006 var 5007 $selected = $(this), 5008 selectedText = module.get.choiceText($selected), 5009 selectedValue = module.get.choiceValue($selected, selectedText) 5010 ; 5011 if(module.is.multiple()) { 5012 if(settings.useLabels) { 5013 module.remove.value(selectedValue, selectedText, $selected); 5014 module.remove.label(selectedValue); 5015 } 5016 else { 5017 module.remove.value(selectedValue, selectedText, $selected); 5018 if(module.get.selectionCount() === 0) { 5019 module.set.placeholderText(); 5020 } 5021 else { 5022 module.set.text(module.add.variables(message.count)); 5023 } 5024 } 5025 } 5026 else { 5027 module.remove.value(selectedValue, selectedText, $selected); 5028 } 5029 $selected 5030 .removeClass(className.filtered) 5031 .removeClass(className.active) 5032 ; 5033 if(settings.useLabels) { 5034 $selected.removeClass(className.selected); 5035 } 5036 }) 5037 ; 5038 }, 5039 selectedItem: function() { 5040 $item.removeClass(className.selected); 5041 }, 5042 value: function(removedValue, removedText, $removedItem) { 5043 var 5044 values = module.get.values(), 5045 newValue 5046 ; 5047 removedValue = module.escape.htmlEntities(removedValue); 5048 if( module.has.selectInput() ) { 5049 module.verbose('Input is <select> removing selected option', removedValue); 5050 newValue = module.remove.arrayValue(removedValue, values); 5051 module.remove.optionValue(removedValue); 5052 } 5053 else { 5054 module.verbose('Removing from delimited values', removedValue); 5055 newValue = module.remove.arrayValue(removedValue, values); 5056 newValue = newValue.join(settings.delimiter); 5057 } 5058 if(settings.fireOnInit === false && module.is.initialLoad()) { 5059 module.verbose('No callback on initial load', settings.onRemove); 5060 } 5061 else { 5062 settings.onRemove.call(element, removedValue, removedText, $removedItem); 5063 } 5064 module.set.value(newValue, removedText, $removedItem); 5065 module.check.maxSelections(); 5066 }, 5067 arrayValue: function(removedValue, values) { 5068 if( !Array.isArray(values) ) { 5069 values = [values]; 5070 } 5071 values = $.grep(values, function(value){ 5072 return (removedValue != value); 5073 }); 5074 module.verbose('Removed value from delimited string', removedValue, values); 5075 return values; 5076 }, 5077 label: function(value, shouldAnimate) { 5078 var 5079 $labels = $module.find(selector.label), 5080 $removedLabel = $labels.filter('[data-' + metadata.value + '="' + module.escape.string(settings.ignoreCase ? value.toLowerCase() : value) +'"]') 5081 ; 5082 module.verbose('Removing label', $removedLabel); 5083 $removedLabel.remove(); 5084 }, 5085 activeLabels: function($activeLabels) { 5086 $activeLabels = $activeLabels || $module.find(selector.label).filter('.' + className.active); 5087 module.verbose('Removing active label selections', $activeLabels); 5088 module.remove.labels($activeLabels); 5089 }, 5090 labels: function($labels) { 5091 $labels = $labels || $module.find(selector.label); 5092 module.verbose('Removing labels', $labels); 5093 $labels 5094 .each(function(){ 5095 var 5096 $label = $(this), 5097 value = $label.data(metadata.value), 5098 stringValue = (value !== undefined) 5099 ? String(value) 5100 : value, 5101 isUserValue = module.is.userValue(stringValue) 5102 ; 5103 if(settings.onLabelRemove.call($label, value) === false) { 5104 module.debug('Label remove callback cancelled removal'); 5105 return; 5106 } 5107 module.remove.message(); 5108 if(isUserValue) { 5109 module.remove.value(stringValue); 5110 module.remove.label(stringValue); 5111 } 5112 else { 5113 // selected will also remove label 5114 module.remove.selected(stringValue); 5115 } 5116 }) 5117 ; 5118 }, 5119 tabbable: function() { 5120 if( module.is.searchSelection() ) { 5121 module.debug('Searchable dropdown initialized'); 5122 $search 5123 .removeAttr('tabindex') 5124 ; 5125 $menu 5126 .removeAttr('tabindex') 5127 ; 5128 } 5129 else { 5130 module.debug('Simple selection dropdown initialized'); 5131 $module 5132 .removeAttr('tabindex') 5133 ; 5134 $menu 5135 .removeAttr('tabindex') 5136 ; 5137 } 5138 }, 5139 diacritics: function(text) { 5140 return settings.ignoreDiacritics ? text.normalize('NFD').replace(/[\u0300-\u036f]/g, '') : text; 5141 } 5142 }, 5143 5144 has: { 5145 menuSearch: function() { 5146 return (module.has.search() && $search.closest($menu).length > 0); 5147 }, 5148 clearItem: function() { 5149 return ($clear.length > 0); 5150 }, 5151 search: function() { 5152 return ($search.length > 0); 5153 }, 5154 sizer: function() { 5155 return ($sizer.length > 0); 5156 }, 5157 selectInput: function() { 5158 return ( $input.is('select') ); 5159 }, 5160 minCharacters: function(searchTerm) { 5161 if(settings.minCharacters && !iconClicked) { 5162 searchTerm = (searchTerm !== undefined) 5163 ? String(searchTerm) 5164 : String(module.get.query()) 5165 ; 5166 return (searchTerm.length >= settings.minCharacters); 5167 } 5168 iconClicked=false; 5169 return true; 5170 }, 5171 firstLetter: function($item, letter) { 5172 var 5173 text, 5174 firstLetter 5175 ; 5176 if(!$item || $item.length === 0 || typeof letter !== 'string') { 5177 return false; 5178 } 5179 text = module.get.choiceText($item, false); 5180 letter = letter.toLowerCase(); 5181 firstLetter = String(text).charAt(0).toLowerCase(); 5182 return (letter == firstLetter); 5183 }, 5184 input: function() { 5185 return ($input.length > 0); 5186 }, 5187 items: function() { 5188 return ($item.length > 0); 5189 }, 5190 menu: function() { 5191 return ($menu.length > 0); 5192 }, 5193 message: function() { 5194 return ($menu.children(selector.message).length !== 0); 5195 }, 5196 label: function(value) { 5197 var 5198 escapedValue = module.escape.value(value), 5199 $labels = $module.find(selector.label) 5200 ; 5201 if(settings.ignoreCase) { 5202 escapedValue = escapedValue.toLowerCase(); 5203 } 5204 return ($labels.filter('[data-' + metadata.value + '="' + module.escape.string(escapedValue) +'"]').length > 0); 5205 }, 5206 maxSelections: function() { 5207 return (settings.maxSelections && module.get.selectionCount() >= settings.maxSelections); 5208 }, 5209 allResultsFiltered: function() { 5210 var 5211 $normalResults = $item.not(selector.addition) 5212 ; 5213 return ($normalResults.filter(selector.unselectable).length === $normalResults.length); 5214 }, 5215 userSuggestion: function() { 5216 return ($menu.children(selector.addition).length > 0); 5217 }, 5218 query: function() { 5219 return (module.get.query() !== ''); 5220 }, 5221 value: function(value) { 5222 return (settings.ignoreCase) 5223 ? module.has.valueIgnoringCase(value) 5224 : module.has.valueMatchingCase(value) 5225 ; 5226 }, 5227 valueMatchingCase: function(value) { 5228 var 5229 values = module.get.values(), 5230 hasValue = Array.isArray(values) 5231 ? values && ($.inArray(value, values) !== -1) 5232 : (values == value) 5233 ; 5234 return (hasValue) 5235 ? true 5236 : false 5237 ; 5238 }, 5239 valueIgnoringCase: function(value) { 5240 var 5241 values = module.get.values(), 5242 hasValue = false 5243 ; 5244 if(!Array.isArray(values)) { 5245 values = [values]; 5246 } 5247 $.each(values, function(index, existingValue) { 5248 if(String(value).toLowerCase() == String(existingValue).toLowerCase()) { 5249 hasValue = true; 5250 return false; 5251 } 5252 }); 5253 return hasValue; 5254 } 5255 }, 5256 5257 is: { 5258 active: function() { 5259 return $module.hasClass(className.active); 5260 }, 5261 animatingInward: function() { 5262 return $menu.transition('is inward'); 5263 }, 5264 animatingOutward: function() { 5265 return $menu.transition('is outward'); 5266 }, 5267 bubbledLabelClick: function(event) { 5268 return $(event.target).is('select, input') && $module.closest('label').length > 0; 5269 }, 5270 bubbledIconClick: function(event) { 5271 return $(event.target).closest($icon).length > 0; 5272 }, 5273 alreadySetup: function() { 5274 return ($module.is('select') && $module.parent(selector.dropdown).data(moduleNamespace) !== undefined && $module.prev().length === 0); 5275 }, 5276 animating: function($subMenu) { 5277 return ($subMenu) 5278 ? $subMenu.transition && $subMenu.transition('is animating') 5279 : $menu.transition && $menu.transition('is animating') 5280 ; 5281 }, 5282 leftward: function($subMenu) { 5283 var $selectedMenu = $subMenu || $menu; 5284 return $selectedMenu.hasClass(className.leftward); 5285 }, 5286 clearable: function() { 5287 return ($module.hasClass(className.clearable) || settings.clearable); 5288 }, 5289 disabled: function() { 5290 return $module.hasClass(className.disabled); 5291 }, 5292 focused: function() { 5293 return (document.activeElement === $module[0]); 5294 }, 5295 focusedOnSearch: function() { 5296 return (document.activeElement === $search[0]); 5297 }, 5298 allFiltered: function() { 5299 return( (module.is.multiple() || module.has.search()) && !(settings.hideAdditions == false && module.has.userSuggestion()) && !module.has.message() && module.has.allResultsFiltered() ); 5300 }, 5301 hidden: function($subMenu) { 5302 return !module.is.visible($subMenu); 5303 }, 5304 initialLoad: function() { 5305 return initialLoad; 5306 }, 5307 inObject: function(needle, object) { 5308 var 5309 found = false 5310 ; 5311 $.each(object, function(index, property) { 5312 if(property == needle) { 5313 found = true; 5314 return true; 5315 } 5316 }); 5317 return found; 5318 }, 5319 multiple: function() { 5320 return $module.hasClass(className.multiple); 5321 }, 5322 remote: function() { 5323 return settings.apiSettings && module.can.useAPI(); 5324 }, 5325 single: function() { 5326 return !module.is.multiple(); 5327 }, 5328 selectMutation: function(mutations) { 5329 var 5330 selectChanged = false 5331 ; 5332 $.each(mutations, function(index, mutation) { 5333 if($(mutation.target).is('select') || $(mutation.addedNodes).is('select')) { 5334 selectChanged = true; 5335 return false; 5336 } 5337 }); 5338 return selectChanged; 5339 }, 5340 search: function() { 5341 return $module.hasClass(className.search); 5342 }, 5343 searchSelection: function() { 5344 return ( module.has.search() && $search.parent(selector.dropdown).length === 1 ); 5345 }, 5346 selection: function() { 5347 return $module.hasClass(className.selection); 5348 }, 5349 userValue: function(value) { 5350 return ($.inArray(value, module.get.userValues()) !== -1); 5351 }, 5352 upward: function($menu) { 5353 var $element = $menu || $module; 5354 return $element.hasClass(className.upward); 5355 }, 5356 visible: function($subMenu) { 5357 return ($subMenu) 5358 ? $subMenu.hasClass(className.visible) 5359 : $menu.hasClass(className.visible) 5360 ; 5361 }, 5362 verticallyScrollableContext: function() { 5363 var 5364 overflowY = ($context.get(0) !== window) 5365 ? $context.css('overflow-y') 5366 : false 5367 ; 5368 return (overflowY == 'auto' || overflowY == 'scroll'); 5369 }, 5370 horizontallyScrollableContext: function() { 5371 var 5372 overflowX = ($context.get(0) !== window) 5373 ? $context.css('overflow-X') 5374 : false 5375 ; 5376 return (overflowX == 'auto' || overflowX == 'scroll'); 5377 } 5378 }, 5379 5380 can: { 5381 activate: function($item) { 5382 if(settings.useLabels) { 5383 return true; 5384 } 5385 if(!module.has.maxSelections()) { 5386 return true; 5387 } 5388 if(module.has.maxSelections() && $item.hasClass(className.active)) { 5389 return true; 5390 } 5391 return false; 5392 }, 5393 openDownward: function($subMenu) { 5394 var 5395 $currentMenu = $subMenu || $menu, 5396 canOpenDownward = true, 5397 onScreen = {}, 5398 calculations 5399 ; 5400 $currentMenu 5401 .addClass(className.loading) 5402 ; 5403 calculations = { 5404 context: { 5405 offset : ($context.get(0) === window) 5406 ? { top: 0, left: 0} 5407 : $context.offset(), 5408 scrollTop : $context.scrollTop(), 5409 height : $context.outerHeight() 5410 }, 5411 menu : { 5412 offset: $currentMenu.offset(), 5413 height: $currentMenu.outerHeight() 5414 } 5415 }; 5416 if(module.is.verticallyScrollableContext()) { 5417 calculations.menu.offset.top += calculations.context.scrollTop; 5418 } 5419 onScreen = { 5420 above : (calculations.context.scrollTop) <= calculations.menu.offset.top - calculations.context.offset.top - calculations.menu.height, 5421 below : (calculations.context.scrollTop + calculations.context.height) >= calculations.menu.offset.top - calculations.context.offset.top + calculations.menu.height 5422 }; 5423 if(onScreen.below) { 5424 module.verbose('Dropdown can fit in context downward', onScreen); 5425 canOpenDownward = true; 5426 } 5427 else if(!onScreen.below && !onScreen.above) { 5428 module.verbose('Dropdown cannot fit in either direction, favoring downward', onScreen); 5429 canOpenDownward = true; 5430 } 5431 else { 5432 module.verbose('Dropdown cannot fit below, opening upward', onScreen); 5433 canOpenDownward = false; 5434 } 5435 $currentMenu.removeClass(className.loading); 5436 return canOpenDownward; 5437 }, 5438 openRightward: function($subMenu) { 5439 var 5440 $currentMenu = $subMenu || $menu, 5441 canOpenRightward = true, 5442 isOffscreenRight = false, 5443 calculations 5444 ; 5445 $currentMenu 5446 .addClass(className.loading) 5447 ; 5448 calculations = { 5449 context: { 5450 offset : ($context.get(0) === window) 5451 ? { top: 0, left: 0} 5452 : $context.offset(), 5453 scrollLeft : $context.scrollLeft(), 5454 width : $context.outerWidth() 5455 }, 5456 menu: { 5457 offset : $currentMenu.offset(), 5458 width : $currentMenu.outerWidth() 5459 } 5460 }; 5461 if(module.is.horizontallyScrollableContext()) { 5462 calculations.menu.offset.left += calculations.context.scrollLeft; 5463 } 5464 isOffscreenRight = (calculations.menu.offset.left - calculations.context.offset.left + calculations.menu.width >= calculations.context.scrollLeft + calculations.context.width); 5465 if(isOffscreenRight) { 5466 module.verbose('Dropdown cannot fit in context rightward', isOffscreenRight); 5467 canOpenRightward = false; 5468 } 5469 $currentMenu.removeClass(className.loading); 5470 return canOpenRightward; 5471 }, 5472 click: function() { 5473 return (hasTouch || settings.on == 'click'); 5474 }, 5475 extendSelect: function() { 5476 return settings.allowAdditions || settings.apiSettings; 5477 }, 5478 show: function() { 5479 return !module.is.disabled() && (module.has.items() || module.has.message()); 5480 }, 5481 useAPI: function() { 5482 return $.fn.api !== undefined; 5483 } 5484 }, 5485 5486 animate: { 5487 show: function(callback, $subMenu) { 5488 var 5489 $currentMenu = $subMenu || $menu, 5490 start = ($subMenu) 5491 ? function() {} 5492 : function() { 5493 module.hideSubMenus(); 5494 module.hideOthers(); 5495 module.set.active(); 5496 }, 5497 transition 5498 ; 5499 callback = $.isFunction(callback) 5500 ? callback 5501 : function(){} 5502 ; 5503 module.verbose('Doing menu show animation', $currentMenu); 5504 module.set.direction($subMenu); 5505 transition = module.get.transition($subMenu); 5506 if( module.is.selection() ) { 5507 module.set.scrollPosition(module.get.selectedItem(), true); 5508 } 5509 if( module.is.hidden($currentMenu) || module.is.animating($currentMenu) ) { 5510 var displayType = $module.hasClass('column') ? 'flex' : false; 5511 if(transition == 'none') { 5512 start(); 5513 $currentMenu.transition({ 5514 displayType: displayType 5515 }).transition('show'); 5516 callback.call(element); 5517 } 5518 else if($.fn.transition !== undefined && $module.transition('is supported')) { 5519 $currentMenu 5520 .transition({ 5521 animation : transition + ' in', 5522 debug : settings.debug, 5523 verbose : settings.verbose, 5524 duration : settings.duration, 5525 queue : true, 5526 onStart : start, 5527 displayType: displayType, 5528 onComplete : function() { 5529 callback.call(element); 5530 } 5531 }) 5532 ; 5533 } 5534 else { 5535 module.error(error.noTransition, transition); 5536 } 5537 } 5538 }, 5539 hide: function(callback, $subMenu) { 5540 var 5541 $currentMenu = $subMenu || $menu, 5542 start = ($subMenu) 5543 ? function() {} 5544 : function() { 5545 if( module.can.click() ) { 5546 module.unbind.intent(); 5547 } 5548 module.remove.active(); 5549 }, 5550 transition = module.get.transition($subMenu) 5551 ; 5552 callback = $.isFunction(callback) 5553 ? callback 5554 : function(){} 5555 ; 5556 if( module.is.visible($currentMenu) || module.is.animating($currentMenu) ) { 5557 module.verbose('Doing menu hide animation', $currentMenu); 5558 5559 if(transition == 'none') { 5560 start(); 5561 $currentMenu.transition('hide'); 5562 callback.call(element); 5563 } 5564 else if($.fn.transition !== undefined && $module.transition('is supported')) { 5565 $currentMenu 5566 .transition({ 5567 animation : transition + ' out', 5568 duration : settings.duration, 5569 debug : settings.debug, 5570 verbose : settings.verbose, 5571 queue : false, 5572 onStart : start, 5573 onComplete : function() { 5574 callback.call(element); 5575 } 5576 }) 5577 ; 5578 } 5579 else { 5580 module.error(error.transition); 5581 } 5582 } 5583 } 5584 }, 5585 5586 hideAndClear: function() { 5587 module.remove.searchTerm(); 5588 if( module.has.maxSelections() ) { 5589 return; 5590 } 5591 if(module.has.search()) { 5592 module.hide(function() { 5593 module.remove.filteredItem(); 5594 }); 5595 } 5596 else { 5597 module.hide(); 5598 } 5599 }, 5600 5601 delay: { 5602 show: function() { 5603 module.verbose('Delaying show event to ensure user intent'); 5604 clearTimeout(module.timer); 5605 module.timer = setTimeout(module.show, settings.delay.show); 5606 }, 5607 hide: function() { 5608 module.verbose('Delaying hide event to ensure user intent'); 5609 clearTimeout(module.timer); 5610 module.timer = setTimeout(module.hide, settings.delay.hide); 5611 } 5612 }, 5613 5614 escape: { 5615 value: function(value) { 5616 var 5617 multipleValues = Array.isArray(value), 5618 stringValue = (typeof value === 'string'), 5619 isUnparsable = (!stringValue && !multipleValues), 5620 hasQuotes = (stringValue && value.search(regExp.quote) !== -1), 5621 values = [] 5622 ; 5623 if(isUnparsable || !hasQuotes) { 5624 return value; 5625 } 5626 module.debug('Encoding quote values for use in select', value); 5627 if(multipleValues) { 5628 $.each(value, function(index, value){ 5629 values.push(value.replace(regExp.quote, '"')); 5630 }); 5631 return values; 5632 } 5633 return value.replace(regExp.quote, '"'); 5634 }, 5635 string: function(text) { 5636 text = String(text); 5637 return text.replace(regExp.escape, '\\$&'); 5638 }, 5639 htmlEntities: function(string) { 5640 var 5641 badChars = /[<>"'`]/g, 5642 shouldEscape = /[&<>"'`]/, 5643 escape = { 5644 "<": "<", 5645 ">": ">", 5646 '"': """, 5647 "'": "'", 5648 "`": "`" 5649 }, 5650 escapedChar = function(chr) { 5651 return escape[chr]; 5652 } 5653 ; 5654 if(shouldEscape.test(string)) { 5655 string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&"); 5656 return string.replace(badChars, escapedChar); 5657 } 5658 return string; 5659 } 5660 }, 5661 5662 setting: function(name, value) { 5663 module.debug('Changing setting', name, value); 5664 if( $.isPlainObject(name) ) { 5665 $.extend(true, settings, name); 5666 } 5667 else if(value !== undefined) { 5668 if($.isPlainObject(settings[name])) { 5669 $.extend(true, settings[name], value); 5670 } 5671 else { 5672 settings[name] = value; 5673 } 5674 } 5675 else { 5676 return settings[name]; 5677 } 5678 }, 5679 internal: function(name, value) { 5680 if( $.isPlainObject(name) ) { 5681 $.extend(true, module, name); 5682 } 5683 else if(value !== undefined) { 5684 module[name] = value; 5685 } 5686 else { 5687 return module[name]; 5688 } 5689 }, 5690 debug: function() { 5691 if(!settings.silent && settings.debug) { 5692 if(settings.performance) { 5693 module.performance.log(arguments); 5694 } 5695 else { 5696 module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); 5697 module.debug.apply(console, arguments); 5698 } 5699 } 5700 }, 5701 verbose: function() { 5702 if(!settings.silent && settings.verbose && settings.debug) { 5703 if(settings.performance) { 5704 module.performance.log(arguments); 5705 } 5706 else { 5707 module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); 5708 module.verbose.apply(console, arguments); 5709 } 5710 } 5711 }, 5712 error: function() { 5713 if(!settings.silent) { 5714 module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); 5715 module.error.apply(console, arguments); 5716 } 5717 }, 5718 performance: { 5719 log: function(message) { 5720 var 5721 currentTime, 5722 executionTime, 5723 previousTime 5724 ; 5725 if(settings.performance) { 5726 currentTime = new Date().getTime(); 5727 previousTime = time || currentTime; 5728 executionTime = currentTime - previousTime; 5729 time = currentTime; 5730 performance.push({ 5731 'Name' : message[0], 5732 'Arguments' : [].slice.call(message, 1) || '', 5733 'Element' : element, 5734 'Execution Time' : executionTime 5735 }); 5736 } 5737 clearTimeout(module.performance.timer); 5738 module.performance.timer = setTimeout(module.performance.display, 500); 5739 }, 5740 display: function() { 5741 var 5742 title = settings.name + ':', 5743 totalTime = 0 5744 ; 5745 time = false; 5746 clearTimeout(module.performance.timer); 5747 $.each(performance, function(index, data) { 5748 totalTime += data['Execution Time']; 5749 }); 5750 title += ' ' + totalTime + 'ms'; 5751 if(moduleSelector) { 5752 title += ' \'' + moduleSelector + '\''; 5753 } 5754 if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { 5755 console.groupCollapsed(title); 5756 if(console.table) { 5757 console.table(performance); 5758 } 5759 else { 5760 $.each(performance, function(index, data) { 5761 console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); 5762 }); 5763 } 5764 console.groupEnd(); 5765 } 5766 performance = []; 5767 } 5768 }, 5769 invoke: function(query, passedArguments, context) { 5770 var 5771 object = instance, 5772 maxDepth, 5773 found, 5774 response 5775 ; 5776 passedArguments = passedArguments || queryArguments; 5777 context = element || context; 5778 if(typeof query == 'string' && object !== undefined) { 5779 query = query.split(/[\. ]/); 5780 maxDepth = query.length - 1; 5781 $.each(query, function(depth, value) { 5782 var camelCaseValue = (depth != maxDepth) 5783 ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) 5784 : query 5785 ; 5786 if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { 5787 object = object[camelCaseValue]; 5788 } 5789 else if( object[camelCaseValue] !== undefined ) { 5790 found = object[camelCaseValue]; 5791 return false; 5792 } 5793 else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { 5794 object = object[value]; 5795 } 5796 else if( object[value] !== undefined ) { 5797 found = object[value]; 5798 return false; 5799 } 5800 else { 5801 module.error(error.method, query); 5802 return false; 5803 } 5804 }); 5805 } 5806 if ( $.isFunction( found ) ) { 5807 response = found.apply(context, passedArguments); 5808 } 5809 else if(found !== undefined) { 5810 response = found; 5811 } 5812 if(Array.isArray(returnedValue)) { 5813 returnedValue.push(response); 5814 } 5815 else if(returnedValue !== undefined) { 5816 returnedValue = [returnedValue, response]; 5817 } 5818 else if(response !== undefined) { 5819 returnedValue = response; 5820 } 5821 return found; 5822 } 5823 }; 5824 5825 if(methodInvoked) { 5826 if(instance === undefined) { 5827 module.initialize(); 5828 } 5829 module.invoke(query); 5830 } 5831 else { 5832 if(instance !== undefined) { 5833 instance.invoke('destroy'); 5834 } 5835 module.initialize(); 5836 } 5837 }) 5838 ; 5839 return (returnedValue !== undefined) 5840 ? returnedValue 5841 : $allModules 5842 ; 5843 }; 5844 5845 $.fn.dropdown.settings = { 5846 5847 silent : false, 5848 debug : false, 5849 verbose : false, 5850 performance : true, 5851 5852 on : 'click', // what event should show menu action on item selection 5853 action : 'activate', // action on item selection (nothing, activate, select, combo, hide, function(){}) 5854 5855 values : false, // specify values to use for dropdown 5856 5857 clearable : false, // whether the value of the dropdown can be cleared 5858 5859 apiSettings : false, 5860 selectOnKeydown : true, // Whether selection should occur automatically when keyboard shortcuts used 5861 minCharacters : 0, // Minimum characters required to trigger API call 5862 5863 filterRemoteData : false, // Whether API results should be filtered after being returned for query term 5864 saveRemoteData : true, // Whether remote name/value pairs should be stored in sessionStorage to allow remote data to be restored on page refresh 5865 5866 throttle : 200, // How long to wait after last user input to search remotely 5867 5868 context : window, // Context to use when determining if on screen 5869 direction : 'auto', // Whether dropdown should always open in one direction 5870 keepOnScreen : true, // Whether dropdown should check whether it is on screen before showing 5871 5872 match : 'both', // what to match against with search selection (both, text, or label) 5873 fullTextSearch : false, // search anywhere in value (set to 'exact' to require exact matches) 5874 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...) 5875 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) 5876 5877 placeholder : 'auto', // whether to convert blank <select> values to placeholder text 5878 preserveHTML : true, // preserve html when selecting value 5879 sortSelect : false, // sort selection on init 5880 5881 forceSelection : true, // force a choice on blur with search selection 5882 5883 allowAdditions : false, // whether multiple select should allow user added values 5884 ignoreCase : false, // whether to consider case sensitivity when creating labels 5885 ignoreSearchCase : true, // whether to consider case sensitivity when filtering items 5886 hideAdditions : true, // whether or not to hide special message prompting a user they can enter a value 5887 5888 maxSelections : false, // When set to a number limits the number of selections to this count 5889 useLabels : true, // whether multiple select should filter currently active selections from choices 5890 delimiter : ',', // when multiselect uses normal <input> the values will be delimited with this character 5891 5892 showOnFocus : true, // show menu on focus 5893 allowReselection : false, // whether current value should trigger callbacks when reselected 5894 allowTab : true, // add tabindex to element 5895 allowCategorySelection : false, // allow elements with sub-menus to be selected 5896 5897 fireOnInit : false, // Whether callbacks should fire when initializing dropdown values 5898 5899 transition : 'auto', // auto transition will slide down or up based on direction 5900 duration : 200, // duration of transition 5901 5902 glyphWidth : 1.037, // widest glyph width in em (W is 1.037 em) used to calculate multiselect input width 5903 5904 headerDivider : true, // whether option headers should have an additional divider line underneath when converted from <select> <optgroup> 5905 5906 // label settings on multi-select 5907 label: { 5908 transition : 'scale', 5909 duration : 200, 5910 variation : false 5911 }, 5912 5913 // delay before event 5914 delay : { 5915 hide : 300, 5916 show : 200, 5917 search : 20, 5918 touch : 50 5919 }, 5920 5921 /* Callbacks */ 5922 onChange : function(value, text, $selected){}, 5923 onAdd : function(value, text, $selected){}, 5924 onRemove : function(value, text, $selected){}, 5925 5926 onLabelSelect : function($selectedLabels){}, 5927 onLabelCreate : function(value, text) { return $(this); }, 5928 onLabelRemove : function(value) { return true; }, 5929 onNoResults : function(searchTerm) { return true; }, 5930 onShow : function(){}, 5931 onHide : function(){}, 5932 5933 /* Component */ 5934 name : 'Dropdown', 5935 namespace : 'dropdown', 5936 5937 message: { 5938 addResult : 'Add <b>{term}</b>', 5939 count : '{count} selected', 5940 maxSelections : 'Max {maxCount} selections', 5941 noResults : 'No results found.', 5942 serverError : 'There was an error contacting the server' 5943 }, 5944 5945 error : { 5946 action : 'You called a dropdown action that was not defined', 5947 alreadySetup : 'Once a select has been initialized behaviors must be called on the created ui dropdown', 5948 labels : 'Allowing user additions currently requires the use of labels.', 5949 missingMultiple : '<select> requires multiple property to be set to correctly preserve multiple values', 5950 method : 'The method you called is not defined.', 5951 noAPI : 'The API module is required to load resources remotely', 5952 noStorage : 'Saving remote data requires session storage', 5953 noTransition : 'This module requires ui transitions <https://github.com/Semantic-Org/UI-Transition>', 5954 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.' 5955 }, 5956 5957 regExp : { 5958 escape : /[-[\]{}()*+?.,\\^$|#\s:=@]/g, 5959 quote : /"/g 5960 }, 5961 5962 metadata : { 5963 defaultText : 'defaultText', 5964 defaultValue : 'defaultValue', 5965 placeholderText : 'placeholder', 5966 text : 'text', 5967 value : 'value' 5968 }, 5969 5970 // property names for remote query 5971 fields: { 5972 remoteValues : 'results', // grouping for api results 5973 values : 'values', // grouping for all dropdown values 5974 disabled : 'disabled', // whether value should be disabled 5975 name : 'name', // displayed dropdown text 5976 value : 'value', // actual dropdown value 5977 text : 'text', // displayed text when selected 5978 type : 'type', // type of dropdown element 5979 image : 'image', // optional image path 5980 imageClass : 'imageClass', // optional individual class for image 5981 icon : 'icon', // optional icon name 5982 iconClass : 'iconClass', // optional individual class for icon (for example to use flag instead) 5983 class : 'class', // optional individual class for item/header 5984 divider : 'divider' // optional divider append for group headers 5985 }, 5986 5987 keys : { 5988 backspace : 8, 5989 delimiter : 188, // comma 5990 deleteKey : 46, 5991 enter : 13, 5992 escape : 27, 5993 pageUp : 33, 5994 pageDown : 34, 5995 leftArrow : 37, 5996 upArrow : 38, 5997 rightArrow : 39, 5998 downArrow : 40 5999 }, 6000 6001 selector : { 6002 addition : '.addition', 6003 divider : '.divider, .header', 6004 dropdown : '.ui.dropdown', 6005 hidden : '.hidden', 6006 icon : '> .dropdown.icon', 6007 input : '> input[type="hidden"], > select', 6008 item : '.item', 6009 label : '> .label', 6010 remove : '> .label > .delete.icon', 6011 siblingLabel : '.label', 6012 menu : '.menu', 6013 message : '.message', 6014 menuIcon : '.dropdown.icon', 6015 search : 'input.search, .menu > .search > input, .menu input.search', 6016 sizer : '> span.sizer', 6017 text : '> .text:not(.icon)', 6018 unselectable : '.disabled, .filtered', 6019 clearIcon : '> .remove.icon' 6020 }, 6021 6022 className : { 6023 active : 'active', 6024 addition : 'addition', 6025 animating : 'animating', 6026 disabled : 'disabled', 6027 empty : 'empty', 6028 dropdown : 'ui dropdown', 6029 filtered : 'filtered', 6030 hidden : 'hidden transition', 6031 icon : 'icon', 6032 image : 'image', 6033 item : 'item', 6034 label : 'ui label', 6035 loading : 'loading', 6036 menu : 'menu', 6037 message : 'message', 6038 multiple : 'multiple', 6039 placeholder : 'default', 6040 sizer : 'sizer', 6041 search : 'search', 6042 selected : 'selected', 6043 selection : 'selection', 6044 upward : 'upward', 6045 leftward : 'left', 6046 visible : 'visible', 6047 clearable : 'clearable', 6048 noselection : 'noselection', 6049 delete : 'delete', 6050 header : 'header', 6051 divider : 'divider', 6052 groupIcon : '', 6053 unfilterable : 'unfilterable' 6054 } 6055 6056 }; 6057 6058 /* Templates */ 6059 $.fn.dropdown.settings.templates = { 6060 deQuote: function(string) { 6061 return String(string).replace(/"/g,""); 6062 }, 6063 escape: function(string, preserveHTML) { 6064 if (preserveHTML){ 6065 return string; 6066 } 6067 var 6068 badChars = /[<>"'`]/g, 6069 shouldEscape = /[&<>"'`]/, 6070 escape = { 6071 "<": "<", 6072 ">": ">", 6073 '"': """, 6074 "'": "'", 6075 "`": "`" 6076 }, 6077 escapedChar = function(chr) { 6078 return escape[chr]; 6079 } 6080 ; 6081 if(shouldEscape.test(string)) { 6082 string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&"); 6083 return string.replace(badChars, escapedChar); 6084 } 6085 return string; 6086 }, 6087 // generates dropdown from select values 6088 dropdown: function(select, fields, preserveHTML, className) { 6089 var 6090 placeholder = select.placeholder || false, 6091 html = '', 6092 escape = $.fn.dropdown.settings.templates.escape 6093 ; 6094 html += '<i class="dropdown icon"></i>'; 6095 if(placeholder) { 6096 html += '<div class="default text">' + escape(placeholder,preserveHTML) + '</div>'; 6097 } 6098 else { 6099 html += '<div class="text"></div>'; 6100 } 6101 html += '<div class="'+className.menu+'">'; 6102 html += $.fn.dropdown.settings.templates.menu(select, fields, preserveHTML,className); 6103 html += '</div>'; 6104 return html; 6105 }, 6106 6107 // generates just menu from select 6108 menu: function(response, fields, preserveHTML, className) { 6109 var 6110 values = response[fields.values] || [], 6111 html = '', 6112 escape = $.fn.dropdown.settings.templates.escape, 6113 deQuote = $.fn.dropdown.settings.templates.deQuote 6114 ; 6115 $.each(values, function(index, option) { 6116 var 6117 itemType = (option[fields.type]) 6118 ? option[fields.type] 6119 : 'item' 6120 ; 6121 6122 if( itemType === 'item' ) { 6123 var 6124 maybeText = (option[fields.text]) 6125 ? ' data-text="' + deQuote(option[fields.text]) + '"' 6126 : '', 6127 maybeDisabled = (option[fields.disabled]) 6128 ? className.disabled+' ' 6129 : '' 6130 ; 6131 html += '<div class="'+ maybeDisabled + (option[fields.class] ? deQuote(option[fields.class]) : className.item)+'" data-value="' + deQuote(option[fields.value]) + '"' + maybeText + '>'; 6132 if(option[fields.image]) { 6133 html += '<img class="'+(option[fields.imageClass] ? deQuote(option[fields.imageClass]) : className.image)+'" src="' + deQuote(option[fields.image]) + '">'; 6134 } 6135 if(option[fields.icon]) { 6136 html += '<i class="'+deQuote(option[fields.icon])+' '+(option[fields.iconClass] ? deQuote(option[fields.iconClass]) : className.icon)+'"></i>'; 6137 } 6138 html += escape(option[fields.name] || '', preserveHTML); 6139 html += '</div>'; 6140 } else if (itemType === 'header') { 6141 var groupName = escape(option[fields.name] || '', preserveHTML), 6142 groupIcon = option[fields.icon] ? deQuote(option[fields.icon]) : className.groupIcon 6143 ; 6144 if(groupName !== '' || groupIcon !== '') { 6145 html += '<div class="' + (option[fields.class] ? deQuote(option[fields.class]) : className.header) + '">'; 6146 if (groupIcon !== '') { 6147 html += '<i class="' + groupIcon + ' ' + (option[fields.iconClass] ? deQuote(option[fields.iconClass]) : className.icon) + '"></i>'; 6148 } 6149 html += groupName; 6150 html += '</div>'; 6151 } 6152 if(option[fields.divider]){ 6153 html += '<div class="'+className.divider+'"></div>'; 6154 } 6155 } 6156 }); 6157 return html; 6158 }, 6159 6160 // generates label for multiselect 6161 label: function(value, text, preserveHTML, className) { 6162 var 6163 escape = $.fn.dropdown.settings.templates.escape; 6164 return escape(text,preserveHTML) + '<i class="'+className.delete+' icon"></i>'; 6165 }, 6166 6167 6168 // generates messages like "No results" 6169 message: function(message) { 6170 return message; 6171 }, 6172 6173 // generates user addition to selection menu 6174 addition: function(choice) { 6175 return choice; 6176 } 6177 6178 }; 6179 6180 })( jQuery, window, document ); 6181 6182 /*! 6183 * # Fomantic-UI - Form Validation 6184 * http://github.com/fomantic/Fomantic-UI/ 6185 * 6186 * 6187 * Released under the MIT license 6188 * http://opensource.org/licenses/MIT 6189 * 6190 */ 6191 6192 ;(function ($, window, document, undefined) { 6193 6194 'use strict'; 6195 6196 $.isFunction = $.isFunction || function(obj) { 6197 return typeof obj === "function" && typeof obj.nodeType !== "number"; 6198 }; 6199 6200 window = (typeof window != 'undefined' && window.Math == Math) 6201 ? window 6202 : (typeof self != 'undefined' && self.Math == Math) 6203 ? self 6204 : Function('return this')() 6205 ; 6206 6207 $.fn.form = function(parameters) { 6208 var 6209 $allModules = $(this), 6210 moduleSelector = $allModules.selector || '', 6211 6212 time = new Date().getTime(), 6213 performance = [], 6214 6215 query = arguments[0], 6216 legacyParameters = arguments[1], 6217 methodInvoked = (typeof query == 'string'), 6218 queryArguments = [].slice.call(arguments, 1), 6219 returnedValue 6220 ; 6221 $allModules 6222 .each(function() { 6223 var 6224 $module = $(this), 6225 element = this, 6226 6227 formErrors = [], 6228 keyHeldDown = false, 6229 6230 // set at run-time 6231 $field, 6232 $group, 6233 $message, 6234 $prompt, 6235 $submit, 6236 $clear, 6237 $reset, 6238 6239 settings, 6240 validation, 6241 6242 metadata, 6243 selector, 6244 className, 6245 regExp, 6246 error, 6247 6248 namespace, 6249 moduleNamespace, 6250 eventNamespace, 6251 6252 submitting = false, 6253 dirty = false, 6254 history = ['clean', 'clean'], 6255 6256 instance, 6257 module 6258 ; 6259 6260 module = { 6261 6262 initialize: function() { 6263 6264 // settings grabbed at run time 6265 module.get.settings(); 6266 if(methodInvoked) { 6267 if(instance === undefined) { 6268 module.instantiate(); 6269 } 6270 module.invoke(query); 6271 } 6272 else { 6273 if(instance !== undefined) { 6274 instance.invoke('destroy'); 6275 } 6276 module.verbose('Initializing form validation', $module, settings); 6277 module.bindEvents(); 6278 module.set.defaults(); 6279 if (settings.autoCheckRequired) { 6280 module.set.autoCheck(); 6281 } 6282 module.instantiate(); 6283 } 6284 }, 6285 6286 instantiate: function() { 6287 module.verbose('Storing instance of module', module); 6288 instance = module; 6289 $module 6290 .data(moduleNamespace, module) 6291 ; 6292 }, 6293 6294 destroy: function() { 6295 module.verbose('Destroying previous module', instance); 6296 module.removeEvents(); 6297 $module 6298 .removeData(moduleNamespace) 6299 ; 6300 }, 6301 6302 refresh: function() { 6303 module.verbose('Refreshing selector cache'); 6304 $field = $module.find(selector.field); 6305 $group = $module.find(selector.group); 6306 $message = $module.find(selector.message); 6307 $prompt = $module.find(selector.prompt); 6308 6309 $submit = $module.find(selector.submit); 6310 $clear = $module.find(selector.clear); 6311 $reset = $module.find(selector.reset); 6312 }, 6313 6314 submit: function() { 6315 module.verbose('Submitting form', $module); 6316 submitting = true; 6317 $module.submit(); 6318 }, 6319 6320 attachEvents: function(selector, action) { 6321 action = action || 'submit'; 6322 $(selector).on('click' + eventNamespace, function(event) { 6323 module[action](); 6324 event.preventDefault(); 6325 }); 6326 }, 6327 6328 bindEvents: function() { 6329 module.verbose('Attaching form events'); 6330 $module 6331 .on('submit' + eventNamespace, module.validate.form) 6332 .on('blur' + eventNamespace, selector.field, module.event.field.blur) 6333 .on('click' + eventNamespace, selector.submit, module.submit) 6334 .on('click' + eventNamespace, selector.reset, module.reset) 6335 .on('click' + eventNamespace, selector.clear, module.clear) 6336 ; 6337 if(settings.keyboardShortcuts) { 6338 $module.on('keydown' + eventNamespace, selector.field, module.event.field.keydown); 6339 } 6340 $field.each(function(index, el) { 6341 var 6342 $input = $(el), 6343 type = $input.prop('type'), 6344 inputEvent = module.get.changeEvent(type, $input) 6345 ; 6346 $input.on(inputEvent + eventNamespace, module.event.field.change); 6347 }); 6348 6349 // Dirty events 6350 if (settings.preventLeaving) { 6351 $(window).on('beforeunload' + eventNamespace, module.event.beforeUnload); 6352 } 6353 6354 $field.on('change click keyup keydown blur', function(e) { 6355 $(this).triggerHandler(e.type + ".dirty"); 6356 }); 6357 6358 $field.on('change.dirty click.dirty keyup.dirty keydown.dirty blur.dirty', module.determine.isDirty); 6359 6360 $module.on('dirty' + eventNamespace, function(e) { 6361 settings.onDirty.call(); 6362 }); 6363 6364 $module.on('clean' + eventNamespace, function(e) { 6365 settings.onClean.call(); 6366 }) 6367 }, 6368 6369 clear: function() { 6370 $field.each(function (index, el) { 6371 var 6372 $field = $(el), 6373 $element = $field.parent(), 6374 $fieldGroup = $field.closest($group), 6375 $prompt = $fieldGroup.find(selector.prompt), 6376 $calendar = $field.closest(selector.uiCalendar), 6377 defaultValue = $field.data(metadata.defaultValue) || '', 6378 isCheckbox = $element.is(selector.uiCheckbox), 6379 isDropdown = $element.is(selector.uiDropdown) && module.can.useElement('dropdown'), 6380 isCalendar = ($calendar.length > 0 && module.can.useElement('calendar')), 6381 isErrored = $fieldGroup.hasClass(className.error) 6382 ; 6383 if(isErrored) { 6384 module.verbose('Resetting error on field', $fieldGroup); 6385 $fieldGroup.removeClass(className.error); 6386 $prompt.remove(); 6387 } 6388 if(isDropdown) { 6389 module.verbose('Resetting dropdown value', $element, defaultValue); 6390 $element.dropdown('clear', true); 6391 } 6392 else if(isCheckbox) { 6393 $field.prop('checked', false); 6394 } 6395 else if (isCalendar) { 6396 $calendar.calendar('clear'); 6397 } 6398 else { 6399 module.verbose('Resetting field value', $field, defaultValue); 6400 $field.val(''); 6401 } 6402 }); 6403 module.remove.states(); 6404 }, 6405 6406 reset: function() { 6407 $field.each(function (index, el) { 6408 var 6409 $field = $(el), 6410 $element = $field.parent(), 6411 $fieldGroup = $field.closest($group), 6412 $calendar = $field.closest(selector.uiCalendar), 6413 $prompt = $fieldGroup.find(selector.prompt), 6414 defaultValue = $field.data(metadata.defaultValue), 6415 isCheckbox = $element.is(selector.uiCheckbox), 6416 isDropdown = $element.is(selector.uiDropdown) && module.can.useElement('dropdown'), 6417 isCalendar = ($calendar.length > 0 && module.can.useElement('calendar')), 6418 isErrored = $fieldGroup.hasClass(className.error) 6419 ; 6420 if(defaultValue === undefined) { 6421 return; 6422 } 6423 if(isErrored) { 6424 module.verbose('Resetting error on field', $fieldGroup); 6425 $fieldGroup.removeClass(className.error); 6426 $prompt.remove(); 6427 } 6428 if(isDropdown) { 6429 module.verbose('Resetting dropdown value', $element, defaultValue); 6430 $element.dropdown('restore defaults', true); 6431 } 6432 else if(isCheckbox) { 6433 module.verbose('Resetting checkbox value', $element, defaultValue); 6434 $field.prop('checked', defaultValue); 6435 } 6436 else if (isCalendar) { 6437 $calendar.calendar('set date', defaultValue); 6438 } 6439 else { 6440 module.verbose('Resetting field value', $field, defaultValue); 6441 $field.val(defaultValue); 6442 } 6443 }); 6444 module.remove.states(); 6445 }, 6446 6447 determine: { 6448 isValid: function() { 6449 var 6450 allValid = true 6451 ; 6452 $.each(validation, function(fieldName, field) { 6453 if( !( module.validate.field(field, fieldName, true) ) ) { 6454 allValid = false; 6455 } 6456 }); 6457 return allValid; 6458 }, 6459 isDirty: function(e) { 6460 var formIsDirty = false; 6461 6462 $field.each(function(index, el) { 6463 var 6464 $el = $(el), 6465 isCheckbox = ($el.filter(selector.checkbox).length > 0), 6466 isDirty 6467 ; 6468 6469 if (isCheckbox) { 6470 isDirty = module.is.checkboxDirty($el); 6471 } else { 6472 isDirty = module.is.fieldDirty($el); 6473 } 6474 6475 $el.data(settings.metadata.isDirty, isDirty); 6476 6477 formIsDirty |= isDirty; 6478 }); 6479 6480 if (formIsDirty) { 6481 module.set.dirty(); 6482 } else { 6483 module.set.clean(); 6484 } 6485 6486 if (e && e.namespace === 'dirty') { 6487 e.stopImmediatePropagation(); 6488 e.preventDefault(); 6489 } 6490 } 6491 }, 6492 6493 is: { 6494 bracketedRule: function(rule) { 6495 return (rule.type && rule.type.match(settings.regExp.bracket)); 6496 }, 6497 shorthandFields: function(fields) { 6498 var 6499 fieldKeys = Object.keys(fields), 6500 firstRule = fields[fieldKeys[0]] 6501 ; 6502 return module.is.shorthandRules(firstRule); 6503 }, 6504 // duck type rule test 6505 shorthandRules: function(rules) { 6506 return (typeof rules == 'string' || Array.isArray(rules)); 6507 }, 6508 empty: function($field) { 6509 if(!$field || $field.length === 0) { 6510 return true; 6511 } 6512 else if($field.is(selector.checkbox)) { 6513 return !$field.is(':checked'); 6514 } 6515 else { 6516 return module.is.blank($field); 6517 } 6518 }, 6519 blank: function($field) { 6520 return String($field.val()).trim() === ''; 6521 }, 6522 valid: function(field, showErrors) { 6523 var 6524 allValid = true 6525 ; 6526 if(field) { 6527 module.verbose('Checking if field is valid', field); 6528 return module.validate.field(validation[field], field, !!showErrors); 6529 } 6530 else { 6531 module.verbose('Checking if form is valid'); 6532 $.each(validation, function(fieldName, field) { 6533 if( !module.is.valid(fieldName, showErrors) ) { 6534 allValid = false; 6535 } 6536 }); 6537 return allValid; 6538 } 6539 }, 6540 dirty: function() { 6541 return dirty; 6542 }, 6543 clean: function() { 6544 return !dirty; 6545 }, 6546 fieldDirty: function($el) { 6547 var initialValue = $el.data(metadata.defaultValue); 6548 // Explicitly check for null/undefined here as value may be `false`, so ($el.data(dataInitialValue) || '') would not work 6549 if (initialValue == null) { initialValue = ''; } 6550 else if(Array.isArray(initialValue)) { 6551 initialValue = initialValue.toString(); 6552 } 6553 var currentValue = $el.val(); 6554 if (currentValue == null) { currentValue = ''; } 6555 // multiple select values are returned as arrays which are never equal, so do string conversion first 6556 else if(Array.isArray(currentValue)) { 6557 currentValue = currentValue.toString(); 6558 } 6559 // Boolean values can be encoded as "true/false" or "True/False" depending on underlying frameworks so we need a case insensitive comparison 6560 var boolRegex = /^(true|false)$/i; 6561 var isBoolValue = boolRegex.test(initialValue) && boolRegex.test(currentValue); 6562 if (isBoolValue) { 6563 var regex = new RegExp("^" + initialValue + "$", "i"); 6564 return !regex.test(currentValue); 6565 } 6566 6567 return currentValue !== initialValue; 6568 }, 6569 checkboxDirty: function($el) { 6570 var initialValue = $el.data(metadata.defaultValue); 6571 var currentValue = $el.is(":checked"); 6572 6573 return initialValue !== currentValue; 6574 }, 6575 justDirty: function() { 6576 return (history[0] === 'dirty'); 6577 }, 6578 justClean: function() { 6579 return (history[0] === 'clean'); 6580 } 6581 }, 6582 6583 removeEvents: function() { 6584 $module.off(eventNamespace); 6585 $field.off(eventNamespace); 6586 $submit.off(eventNamespace); 6587 $field.off(eventNamespace); 6588 }, 6589 6590 event: { 6591 field: { 6592 keydown: function(event) { 6593 var 6594 $field = $(this), 6595 key = event.which, 6596 isInput = $field.is(selector.input), 6597 isCheckbox = $field.is(selector.checkbox), 6598 isInDropdown = ($field.closest(selector.uiDropdown).length > 0), 6599 keyCode = { 6600 enter : 13, 6601 escape : 27 6602 } 6603 ; 6604 if( key == keyCode.escape) { 6605 module.verbose('Escape key pressed blurring field'); 6606 $field 6607 .blur() 6608 ; 6609 } 6610 if(!event.ctrlKey && key == keyCode.enter && isInput && !isInDropdown && !isCheckbox) { 6611 if(!keyHeldDown) { 6612 $field.one('keyup' + eventNamespace, module.event.field.keyup); 6613 module.submit(); 6614 module.debug('Enter pressed on input submitting form'); 6615 } 6616 keyHeldDown = true; 6617 } 6618 }, 6619 keyup: function() { 6620 keyHeldDown = false; 6621 }, 6622 blur: function(event) { 6623 var 6624 $field = $(this), 6625 $fieldGroup = $field.closest($group), 6626 validationRules = module.get.validation($field) 6627 ; 6628 if( $fieldGroup.hasClass(className.error) ) { 6629 module.debug('Revalidating field', $field, validationRules); 6630 if(validationRules) { 6631 module.validate.field( validationRules ); 6632 } 6633 } 6634 else if(settings.on == 'blur') { 6635 if(validationRules) { 6636 module.validate.field( validationRules ); 6637 } 6638 } 6639 }, 6640 change: function(event) { 6641 var 6642 $field = $(this), 6643 $fieldGroup = $field.closest($group), 6644 validationRules = module.get.validation($field) 6645 ; 6646 if(validationRules && (settings.on == 'change' || ( $fieldGroup.hasClass(className.error) && settings.revalidate) )) { 6647 clearTimeout(module.timer); 6648 module.timer = setTimeout(function() { 6649 module.debug('Revalidating field', $field, module.get.validation($field)); 6650 module.validate.field( validationRules ); 6651 if(!settings.inline) { 6652 module.validate.form(false,true); 6653 } 6654 }, settings.delay); 6655 } 6656 } 6657 }, 6658 beforeUnload: function(event) { 6659 if (module.is.dirty() && !submitting) { 6660 var event = event || window.event; 6661 6662 // For modern browsers 6663 if (event) { 6664 event.returnValue = settings.text.leavingMessage; 6665 } 6666 6667 // For olders... 6668 return settings.text.leavingMessage; 6669 } 6670 } 6671 6672 }, 6673 6674 get: { 6675 ancillaryValue: function(rule) { 6676 if(!rule.type || (!rule.value && !module.is.bracketedRule(rule))) { 6677 return false; 6678 } 6679 return (rule.value !== undefined) 6680 ? rule.value 6681 : rule.type.match(settings.regExp.bracket)[1] + '' 6682 ; 6683 }, 6684 ruleName: function(rule) { 6685 if( module.is.bracketedRule(rule) ) { 6686 return rule.type.replace(rule.type.match(settings.regExp.bracket)[0], ''); 6687 } 6688 return rule.type; 6689 }, 6690 changeEvent: function(type, $input) { 6691 if(type == 'checkbox' || type == 'radio' || type == 'hidden' || $input.is('select')) { 6692 return 'change'; 6693 } 6694 else { 6695 return module.get.inputEvent(); 6696 } 6697 }, 6698 inputEvent: function() { 6699 return (document.createElement('input').oninput !== undefined) 6700 ? 'input' 6701 : (document.createElement('input').onpropertychange !== undefined) 6702 ? 'propertychange' 6703 : 'keyup' 6704 ; 6705 }, 6706 fieldsFromShorthand: function(fields) { 6707 var 6708 fullFields = {} 6709 ; 6710 $.each(fields, function(name, rules) { 6711 if(typeof rules == 'string') { 6712 rules = [rules]; 6713 } 6714 fullFields[name] = { 6715 rules: [] 6716 }; 6717 $.each(rules, function(index, rule) { 6718 fullFields[name].rules.push({ type: rule }); 6719 }); 6720 }); 6721 return fullFields; 6722 }, 6723 prompt: function(rule, field) { 6724 var 6725 ruleName = module.get.ruleName(rule), 6726 ancillary = module.get.ancillaryValue(rule), 6727 $field = module.get.field(field.identifier), 6728 value = $field.val(), 6729 prompt = $.isFunction(rule.prompt) 6730 ? rule.prompt(value) 6731 : rule.prompt || settings.prompt[ruleName] || settings.text.unspecifiedRule, 6732 requiresValue = (prompt.search('{value}') !== -1), 6733 requiresName = (prompt.search('{name}') !== -1), 6734 $label, 6735 name 6736 ; 6737 if(requiresValue) { 6738 prompt = prompt.replace(/\{value\}/g, $field.val()); 6739 } 6740 if(requiresName) { 6741 $label = $field.closest(selector.group).find('label').eq(0); 6742 name = ($label.length == 1) 6743 ? $label.text() 6744 : $field.prop('placeholder') || settings.text.unspecifiedField 6745 ; 6746 prompt = prompt.replace(/\{name\}/g, name); 6747 } 6748 prompt = prompt.replace(/\{identifier\}/g, field.identifier); 6749 prompt = prompt.replace(/\{ruleValue\}/g, ancillary); 6750 if(!rule.prompt) { 6751 module.verbose('Using default validation prompt for type', prompt, ruleName); 6752 } 6753 return prompt; 6754 }, 6755 settings: function() { 6756 if($.isPlainObject(parameters)) { 6757 var 6758 keys = Object.keys(parameters), 6759 isLegacySettings = (keys.length > 0) 6760 ? (parameters[keys[0]].identifier !== undefined && parameters[keys[0]].rules !== undefined) 6761 : false 6762 ; 6763 if(isLegacySettings) { 6764 // 1.x (ducktyped) 6765 settings = $.extend(true, {}, $.fn.form.settings, legacyParameters); 6766 validation = $.extend({}, $.fn.form.settings.defaults, parameters); 6767 module.error(settings.error.oldSyntax, element); 6768 module.verbose('Extending settings from legacy parameters', validation, settings); 6769 } 6770 else { 6771 // 2.x 6772 if(parameters.fields && module.is.shorthandFields(parameters.fields)) { 6773 parameters.fields = module.get.fieldsFromShorthand(parameters.fields); 6774 } 6775 settings = $.extend(true, {}, $.fn.form.settings, parameters); 6776 validation = $.extend({}, $.fn.form.settings.defaults, settings.fields); 6777 module.verbose('Extending settings', validation, settings); 6778 } 6779 } 6780 else { 6781 settings = $.fn.form.settings; 6782 validation = $.fn.form.settings.defaults; 6783 module.verbose('Using default form validation', validation, settings); 6784 } 6785 6786 // shorthand 6787 namespace = settings.namespace; 6788 metadata = settings.metadata; 6789 selector = settings.selector; 6790 className = settings.className; 6791 regExp = settings.regExp; 6792 error = settings.error; 6793 moduleNamespace = 'module-' + namespace; 6794 eventNamespace = '.' + namespace; 6795 6796 // grab instance 6797 instance = $module.data(moduleNamespace); 6798 6799 // refresh selector cache 6800 module.refresh(); 6801 }, 6802 field: function(identifier) { 6803 module.verbose('Finding field with identifier', identifier); 6804 identifier = module.escape.string(identifier); 6805 var t; 6806 if((t=$field.filter('#' + identifier)).length > 0 ) { 6807 return t; 6808 } 6809 if((t=$field.filter('[name="' + identifier +'"]')).length > 0 ) { 6810 return t; 6811 } 6812 if((t=$field.filter('[name="' + identifier +'[]"]')).length > 0 ) { 6813 return t; 6814 } 6815 if((t=$field.filter('[data-' + metadata.validate + '="'+ identifier +'"]')).length > 0 ) { 6816 return t; 6817 } 6818 return $('<input/>'); 6819 }, 6820 fields: function(fields) { 6821 var 6822 $fields = $() 6823 ; 6824 $.each(fields, function(index, name) { 6825 $fields = $fields.add( module.get.field(name) ); 6826 }); 6827 return $fields; 6828 }, 6829 validation: function($field) { 6830 var 6831 fieldValidation, 6832 identifier 6833 ; 6834 if(!validation) { 6835 return false; 6836 } 6837 $.each(validation, function(fieldName, field) { 6838 identifier = field.identifier || fieldName; 6839 $.each(module.get.field(identifier), function(index, groupField) { 6840 if(groupField == $field[0]) { 6841 field.identifier = identifier; 6842 fieldValidation = field; 6843 return false; 6844 } 6845 }); 6846 }); 6847 return fieldValidation || false; 6848 }, 6849 value: function (field) { 6850 var 6851 fields = [], 6852 results 6853 ; 6854 fields.push(field); 6855 results = module.get.values.call(element, fields); 6856 return results[field]; 6857 }, 6858 values: function (fields) { 6859 var 6860 $fields = Array.isArray(fields) 6861 ? module.get.fields(fields) 6862 : $field, 6863 values = {} 6864 ; 6865 $fields.each(function(index, field) { 6866 var 6867 $field = $(field), 6868 $calendar = $field.closest(selector.uiCalendar), 6869 name = $field.prop('name'), 6870 value = $field.val(), 6871 isCheckbox = $field.is(selector.checkbox), 6872 isRadio = $field.is(selector.radio), 6873 isMultiple = (name.indexOf('[]') !== -1), 6874 isCalendar = ($calendar.length > 0 && module.can.useElement('calendar')), 6875 isChecked = (isCheckbox) 6876 ? $field.is(':checked') 6877 : false 6878 ; 6879 if(name) { 6880 if(isMultiple) { 6881 name = name.replace('[]', ''); 6882 if(!values[name]) { 6883 values[name] = []; 6884 } 6885 if(isCheckbox) { 6886 if(isChecked) { 6887 values[name].push(value || true); 6888 } 6889 else { 6890 values[name].push(false); 6891 } 6892 } 6893 else { 6894 values[name].push(value); 6895 } 6896 } 6897 else { 6898 if(isRadio) { 6899 if(values[name] === undefined || values[name] === false) { 6900 values[name] = (isChecked) 6901 ? value || true 6902 : false 6903 ; 6904 } 6905 } 6906 else if(isCheckbox) { 6907 if(isChecked) { 6908 values[name] = value || true; 6909 } 6910 else { 6911 values[name] = false; 6912 } 6913 } 6914 else if(isCalendar) { 6915 var date = $calendar.calendar('get date'); 6916 6917 if (date !== null) { 6918 if (settings.dateHandling == 'date') { 6919 values[name] = date; 6920 } else if(settings.dateHandling == 'input') { 6921 values[name] = $calendar.calendar('get input date') 6922 } else if (settings.dateHandling == 'formatter') { 6923 var type = $calendar.calendar('setting', 'type'); 6924 6925 switch(type) { 6926 case 'date': 6927 values[name] = settings.formatter.date(date); 6928 break; 6929 6930 case 'datetime': 6931 values[name] = settings.formatter.datetime(date); 6932 break; 6933 6934 case 'time': 6935 values[name] = settings.formatter.time(date); 6936 break; 6937 6938 case 'month': 6939 values[name] = settings.formatter.month(date); 6940 break; 6941 6942 case 'year': 6943 values[name] = settings.formatter.year(date); 6944 break; 6945 6946 default: 6947 module.debug('Wrong calendar mode', $calendar, type); 6948 values[name] = ''; 6949 } 6950 } 6951 } else { 6952 values[name] = ''; 6953 } 6954 } else { 6955 values[name] = value; 6956 } 6957 } 6958 } 6959 }); 6960 return values; 6961 }, 6962 dirtyFields: function() { 6963 return $field.filter(function(index, e) { 6964 return $(e).data(metadata.isDirty); 6965 }); 6966 } 6967 }, 6968 6969 has: { 6970 6971 field: function(identifier) { 6972 module.verbose('Checking for existence of a field with identifier', identifier); 6973 identifier = module.escape.string(identifier); 6974 if(typeof identifier !== 'string') { 6975 module.error(error.identifier, identifier); 6976 } 6977 if($field.filter('#' + identifier).length > 0 ) { 6978 return true; 6979 } 6980 else if( $field.filter('[name="' + identifier +'"]').length > 0 ) { 6981 return true; 6982 } 6983 else if( $field.filter('[data-' + metadata.validate + '="'+ identifier +'"]').length > 0 ) { 6984 return true; 6985 } 6986 return false; 6987 } 6988 6989 }, 6990 6991 can: { 6992 useElement: function(element){ 6993 if ($.fn[element] !== undefined) { 6994 return true; 6995 } 6996 module.error(error.noElement.replace('{element}',element)); 6997 return false; 6998 } 6999 }, 7000 7001 escape: { 7002 string: function(text) { 7003 text = String(text); 7004 return text.replace(regExp.escape, '\\$&'); 7005 } 7006 }, 7007 7008 add: { 7009 // alias 7010 rule: function(name, rules) { 7011 module.add.field(name, rules); 7012 }, 7013 field: function(name, rules) { 7014 // Validation should have at least a standard format 7015 if(validation[name] === undefined || validation[name].rules === undefined) { 7016 validation[name] = { 7017 rules: [] 7018 }; 7019 } 7020 var 7021 newValidation = { 7022 rules: [] 7023 } 7024 ; 7025 if(module.is.shorthandRules(rules)) { 7026 rules = Array.isArray(rules) 7027 ? rules 7028 : [rules] 7029 ; 7030 $.each(rules, function(_index, rule) { 7031 newValidation.rules.push({ type: rule }); 7032 }); 7033 } 7034 else { 7035 newValidation.rules = rules.rules; 7036 } 7037 // For each new rule, check if there's not already one with the same type 7038 $.each(newValidation.rules, function (_index, rule) { 7039 if ($.grep(validation[name].rules, function(item){ return item.type == rule.type; }).length == 0) { 7040 validation[name].rules.push(rule); 7041 } 7042 }); 7043 module.debug('Adding rules', newValidation.rules, validation); 7044 }, 7045 fields: function(fields) { 7046 var 7047 newValidation 7048 ; 7049 if(fields && module.is.shorthandFields(fields)) { 7050 newValidation = module.get.fieldsFromShorthand(fields); 7051 } 7052 else { 7053 newValidation = fields; 7054 } 7055 validation = $.extend({}, validation, newValidation); 7056 }, 7057 prompt: function(identifier, errors, internal) { 7058 var 7059 $field = module.get.field(identifier), 7060 $fieldGroup = $field.closest($group), 7061 $prompt = $fieldGroup.children(selector.prompt), 7062 promptExists = ($prompt.length !== 0) 7063 ; 7064 errors = (typeof errors == 'string') 7065 ? [errors] 7066 : errors 7067 ; 7068 module.verbose('Adding field error state', identifier); 7069 if(!internal) { 7070 $fieldGroup 7071 .addClass(className.error) 7072 ; 7073 } 7074 if(settings.inline) { 7075 if(!promptExists) { 7076 $prompt = settings.templates.prompt(errors, className.label); 7077 $prompt 7078 .appendTo($fieldGroup) 7079 ; 7080 } 7081 $prompt 7082 .html(errors[0]) 7083 ; 7084 if(!promptExists) { 7085 if(settings.transition && module.can.useElement('transition') && $module.transition('is supported')) { 7086 module.verbose('Displaying error with css transition', settings.transition); 7087 $prompt.transition(settings.transition + ' in', settings.duration); 7088 } 7089 else { 7090 module.verbose('Displaying error with fallback javascript animation'); 7091 $prompt 7092 .fadeIn(settings.duration) 7093 ; 7094 } 7095 } 7096 else { 7097 module.verbose('Inline errors are disabled, no inline error added', identifier); 7098 } 7099 } 7100 }, 7101 errors: function(errors) { 7102 module.debug('Adding form error messages', errors); 7103 module.set.error(); 7104 $message 7105 .html( settings.templates.error(errors) ) 7106 ; 7107 } 7108 }, 7109 7110 remove: { 7111 errors: function() { 7112 module.debug('Removing form error messages'); 7113 $message.empty(); 7114 }, 7115 states: function() { 7116 $module.removeClass(className.error).removeClass(className.success); 7117 if(!settings.inline) { 7118 module.remove.errors(); 7119 } 7120 module.determine.isDirty(); 7121 }, 7122 rule: function(field, rule) { 7123 var 7124 rules = Array.isArray(rule) 7125 ? rule 7126 : [rule] 7127 ; 7128 if(validation[field] === undefined || !Array.isArray(validation[field].rules)) { 7129 return; 7130 } 7131 if(rule === undefined) { 7132 module.debug('Removed all rules'); 7133 validation[field].rules = []; 7134 return; 7135 } 7136 $.each(validation[field].rules, function(index, rule) { 7137 if(rule && rules.indexOf(rule.type) !== -1) { 7138 module.debug('Removed rule', rule.type); 7139 validation[field].rules.splice(index, 1); 7140 } 7141 }); 7142 }, 7143 field: function(field) { 7144 var 7145 fields = Array.isArray(field) 7146 ? field 7147 : [field] 7148 ; 7149 $.each(fields, function(index, field) { 7150 module.remove.rule(field); 7151 }); 7152 }, 7153 // alias 7154 rules: function(field, rules) { 7155 if(Array.isArray(field)) { 7156 $.each(field, function(index, field) { 7157 module.remove.rule(field, rules); 7158 }); 7159 } 7160 else { 7161 module.remove.rule(field, rules); 7162 } 7163 }, 7164 fields: function(fields) { 7165 module.remove.field(fields); 7166 }, 7167 prompt: function(identifier) { 7168 var 7169 $field = module.get.field(identifier), 7170 $fieldGroup = $field.closest($group), 7171 $prompt = $fieldGroup.children(selector.prompt) 7172 ; 7173 $fieldGroup 7174 .removeClass(className.error) 7175 ; 7176 if(settings.inline && $prompt.is(':visible')) { 7177 module.verbose('Removing prompt for field', identifier); 7178 if(settings.transition && module.can.useElement('transition') && $module.transition('is supported')) { 7179 $prompt.transition(settings.transition + ' out', settings.duration, function() { 7180 $prompt.remove(); 7181 }); 7182 } 7183 else { 7184 $prompt 7185 .fadeOut(settings.duration, function(){ 7186 $prompt.remove(); 7187 }) 7188 ; 7189 } 7190 } 7191 } 7192 }, 7193 7194 set: { 7195 success: function() { 7196 $module 7197 .removeClass(className.error) 7198 .addClass(className.success) 7199 ; 7200 }, 7201 defaults: function () { 7202 $field.each(function (index, el) { 7203 var 7204 $el = $(el), 7205 $parent = $el.parent(), 7206 isCheckbox = ($el.filter(selector.checkbox).length > 0), 7207 isDropdown = $parent.is(selector.uiDropdown) && module.can.useElement('dropdown'), 7208 $calendar = $el.closest(selector.uiCalendar), 7209 isCalendar = ($calendar.length > 0 && module.can.useElement('calendar')), 7210 value = (isCheckbox) 7211 ? $el.is(':checked') 7212 : $el.val() 7213 ; 7214 if (isDropdown) { 7215 $parent.dropdown('save defaults'); 7216 } 7217 else if (isCalendar) { 7218 $calendar.calendar('refresh'); 7219 } 7220 $el.data(metadata.defaultValue, value); 7221 $el.data(metadata.isDirty, false); 7222 }); 7223 }, 7224 error: function() { 7225 $module 7226 .removeClass(className.success) 7227 .addClass(className.error) 7228 ; 7229 }, 7230 value: function (field, value) { 7231 var 7232 fields = {} 7233 ; 7234 fields[field] = value; 7235 return module.set.values.call(element, fields); 7236 }, 7237 values: function (fields) { 7238 if($.isEmptyObject(fields)) { 7239 return; 7240 } 7241 $.each(fields, function(key, value) { 7242 var 7243 $field = module.get.field(key), 7244 $element = $field.parent(), 7245 $calendar = $field.closest(selector.uiCalendar), 7246 isMultiple = Array.isArray(value), 7247 isCheckbox = $element.is(selector.uiCheckbox) && module.can.useElement('checkbox'), 7248 isDropdown = $element.is(selector.uiDropdown) && module.can.useElement('dropdown'), 7249 isRadio = ($field.is(selector.radio) && isCheckbox), 7250 isCalendar = ($calendar.length > 0 && module.can.useElement('calendar')), 7251 fieldExists = ($field.length > 0), 7252 $multipleField 7253 ; 7254 if(fieldExists) { 7255 if(isMultiple && isCheckbox) { 7256 module.verbose('Selecting multiple', value, $field); 7257 $element.checkbox('uncheck'); 7258 $.each(value, function(index, value) { 7259 $multipleField = $field.filter('[value="' + value + '"]'); 7260 $element = $multipleField.parent(); 7261 if($multipleField.length > 0) { 7262 $element.checkbox('check'); 7263 } 7264 }); 7265 } 7266 else if(isRadio) { 7267 module.verbose('Selecting radio value', value, $field); 7268 $field.filter('[value="' + value + '"]') 7269 .parent(selector.uiCheckbox) 7270 .checkbox('check') 7271 ; 7272 } 7273 else if(isCheckbox) { 7274 module.verbose('Setting checkbox value', value, $element); 7275 if(value === true || value === 1) { 7276 $element.checkbox('check'); 7277 } 7278 else { 7279 $element.checkbox('uncheck'); 7280 } 7281 } 7282 else if(isDropdown) { 7283 module.verbose('Setting dropdown value', value, $element); 7284 $element.dropdown('set selected', value); 7285 } 7286 else if (isCalendar) { 7287 $calendar.calendar('set date',value); 7288 } 7289 else { 7290 module.verbose('Setting field value', value, $field); 7291 $field.val(value); 7292 } 7293 } 7294 }); 7295 }, 7296 dirty: function() { 7297 module.verbose('Setting state dirty'); 7298 dirty = true; 7299 history[0] = history[1]; 7300 history[1] = 'dirty'; 7301 7302 if (module.is.justClean()) { 7303 $module.trigger('dirty'); 7304 } 7305 }, 7306 clean: function() { 7307 module.verbose('Setting state clean'); 7308 dirty = false; 7309 history[0] = history[1]; 7310 history[1] = 'clean'; 7311 7312 if (module.is.justDirty()) { 7313 $module.trigger('clean'); 7314 } 7315 }, 7316 asClean: function() { 7317 module.set.defaults(); 7318 module.set.clean(); 7319 }, 7320 asDirty: function() { 7321 module.set.defaults(); 7322 module.set.dirty(); 7323 }, 7324 autoCheck: function() { 7325 module.debug('Enabling auto check on required fields'); 7326 $field.each(function (_index, el) { 7327 var 7328 $el = $(el), 7329 $elGroup = $(el).closest($group), 7330 isCheckbox = ($el.filter(selector.checkbox).length > 0), 7331 isRequired = $el.prop('required') || $elGroup.hasClass(className.required) || $elGroup.parent().hasClass(className.required), 7332 isDisabled = $el.is(':disabled') || $elGroup.hasClass(className.disabled) || $elGroup.parent().hasClass(className.disabled), 7333 validation = module.get.validation($el), 7334 hasEmptyRule = validation 7335 ? $.grep(validation.rules, function(rule) { return rule.type == "empty" }) !== 0 7336 : false, 7337 identifier = validation.identifier || $el.attr('id') || $el.attr('name') || $el.data(metadata.validate) 7338 ; 7339 if (isRequired && !isDisabled && !hasEmptyRule && identifier !== undefined) { 7340 if (isCheckbox) { 7341 module.verbose("Adding 'checked' rule on field", identifier); 7342 module.add.rule(identifier, "checked"); 7343 } else { 7344 module.verbose("Adding 'empty' rule on field", identifier); 7345 module.add.rule(identifier, "empty"); 7346 } 7347 } 7348 }); 7349 } 7350 }, 7351 7352 validate: { 7353 7354 form: function(event, ignoreCallbacks) { 7355 var values = module.get.values(); 7356 7357 // input keydown event will fire submit repeatedly by browser default 7358 if(keyHeldDown) { 7359 return false; 7360 } 7361 7362 // reset errors 7363 formErrors = []; 7364 if( module.determine.isValid() ) { 7365 module.debug('Form has no validation errors, submitting'); 7366 module.set.success(); 7367 if(!settings.inline) { 7368 module.remove.errors(); 7369 } 7370 if(ignoreCallbacks !== true) { 7371 return settings.onSuccess.call(element, event, values); 7372 } 7373 } 7374 else { 7375 module.debug('Form has errors'); 7376 submitting = false; 7377 module.set.error(); 7378 if(!settings.inline) { 7379 module.add.errors(formErrors); 7380 } 7381 // prevent ajax submit 7382 if(event && $module.data('moduleApi') !== undefined) { 7383 event.stopImmediatePropagation(); 7384 } 7385 if(ignoreCallbacks !== true) { 7386 return settings.onFailure.call(element, formErrors, values); 7387 } 7388 } 7389 }, 7390 7391 // takes a validation object and returns whether field passes validation 7392 field: function(field, fieldName, showErrors) { 7393 showErrors = (showErrors !== undefined) 7394 ? showErrors 7395 : true 7396 ; 7397 if(typeof field == 'string') { 7398 module.verbose('Validating field', field); 7399 fieldName = field; 7400 field = validation[field]; 7401 } 7402 var 7403 identifier = field.identifier || fieldName, 7404 $field = module.get.field(identifier), 7405 $dependsField = (field.depends) 7406 ? module.get.field(field.depends) 7407 : false, 7408 fieldValid = true, 7409 fieldErrors = [] 7410 ; 7411 if(!field.identifier) { 7412 module.debug('Using field name as identifier', identifier); 7413 field.identifier = identifier; 7414 } 7415 var isDisabled = !$field.filter(':not(:disabled)').length; 7416 if(isDisabled) { 7417 module.debug('Field is disabled. Skipping', identifier); 7418 } 7419 else if(field.optional && module.is.blank($field)){ 7420 module.debug('Field is optional and blank. Skipping', identifier); 7421 } 7422 else if(field.depends && module.is.empty($dependsField)) { 7423 module.debug('Field depends on another value that is not present or empty. Skipping', $dependsField); 7424 } 7425 else if(field.rules !== undefined) { 7426 if(showErrors) { 7427 $field.closest($group).removeClass(className.error); 7428 } 7429 $.each(field.rules, function(index, rule) { 7430 if( module.has.field(identifier)) { 7431 var invalidFields = module.validate.rule(field, rule,true) || []; 7432 if (invalidFields.length>0){ 7433 module.debug('Field is invalid', identifier, rule.type); 7434 fieldErrors.push(module.get.prompt(rule, field)); 7435 fieldValid = false; 7436 if(showErrors){ 7437 $(invalidFields).closest($group).addClass(className.error); 7438 } 7439 } 7440 } 7441 }); 7442 } 7443 if(fieldValid) { 7444 if(showErrors) { 7445 module.remove.prompt(identifier, fieldErrors); 7446 settings.onValid.call($field); 7447 } 7448 } 7449 else { 7450 if(showErrors) { 7451 formErrors = formErrors.concat(fieldErrors); 7452 module.add.prompt(identifier, fieldErrors, true); 7453 settings.onInvalid.call($field, fieldErrors); 7454 } 7455 return false; 7456 } 7457 return true; 7458 }, 7459 7460 // takes validation rule and returns whether field passes rule 7461 rule: function(field, rule, internal) { 7462 var 7463 $field = module.get.field(field.identifier), 7464 ancillary = module.get.ancillaryValue(rule), 7465 ruleName = module.get.ruleName(rule), 7466 ruleFunction = settings.rules[ruleName], 7467 invalidFields = [], 7468 isCheckbox = $field.is(selector.checkbox), 7469 isValid = function(field){ 7470 var value = (isCheckbox ? $(field).filter(':checked').val() : $(field).val()); 7471 // cast to string avoiding encoding special values 7472 value = (value === undefined || value === '' || value === null) 7473 ? '' 7474 : (settings.shouldTrim) ? String(value + '').trim() : String(value + '') 7475 ; 7476 return ruleFunction.call(field, value, ancillary, $module); 7477 } 7478 ; 7479 if( !$.isFunction(ruleFunction) ) { 7480 module.error(error.noRule, ruleName); 7481 return; 7482 } 7483 if(isCheckbox) { 7484 if (!isValid($field)) { 7485 invalidFields = $field; 7486 } 7487 } else { 7488 $.each($field, function (index, field) { 7489 if (!isValid(field)) { 7490 invalidFields.push(field); 7491 } 7492 }); 7493 } 7494 return internal ? invalidFields : !(invalidFields.length>0); 7495 } 7496 }, 7497 7498 setting: function(name, value) { 7499 if( $.isPlainObject(name) ) { 7500 $.extend(true, settings, name); 7501 } 7502 else if(value !== undefined) { 7503 settings[name] = value; 7504 } 7505 else { 7506 return settings[name]; 7507 } 7508 }, 7509 internal: function(name, value) { 7510 if( $.isPlainObject(name) ) { 7511 $.extend(true, module, name); 7512 } 7513 else if(value !== undefined) { 7514 module[name] = value; 7515 } 7516 else { 7517 return module[name]; 7518 } 7519 }, 7520 debug: function() { 7521 if(!settings.silent && settings.debug) { 7522 if(settings.performance) { 7523 module.performance.log(arguments); 7524 } 7525 else { 7526 module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); 7527 module.debug.apply(console, arguments); 7528 } 7529 } 7530 }, 7531 verbose: function() { 7532 if(!settings.silent && settings.verbose && settings.debug) { 7533 if(settings.performance) { 7534 module.performance.log(arguments); 7535 } 7536 else { 7537 module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); 7538 module.verbose.apply(console, arguments); 7539 } 7540 } 7541 }, 7542 error: function() { 7543 if(!settings.silent) { 7544 module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); 7545 module.error.apply(console, arguments); 7546 } 7547 }, 7548 performance: { 7549 log: function(message) { 7550 var 7551 currentTime, 7552 executionTime, 7553 previousTime 7554 ; 7555 if(settings.performance) { 7556 currentTime = new Date().getTime(); 7557 previousTime = time || currentTime; 7558 executionTime = currentTime - previousTime; 7559 time = currentTime; 7560 performance.push({ 7561 'Name' : message[0], 7562 'Arguments' : [].slice.call(message, 1) || '', 7563 'Element' : element, 7564 'Execution Time' : executionTime 7565 }); 7566 } 7567 clearTimeout(module.performance.timer); 7568 module.performance.timer = setTimeout(module.performance.display, 500); 7569 }, 7570 display: function() { 7571 var 7572 title = settings.name + ':', 7573 totalTime = 0 7574 ; 7575 time = false; 7576 clearTimeout(module.performance.timer); 7577 $.each(performance, function(index, data) { 7578 totalTime += data['Execution Time']; 7579 }); 7580 title += ' ' + totalTime + 'ms'; 7581 if(moduleSelector) { 7582 title += ' \'' + moduleSelector + '\''; 7583 } 7584 if($allModules.length > 1) { 7585 title += ' ' + '(' + $allModules.length + ')'; 7586 } 7587 if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { 7588 console.groupCollapsed(title); 7589 if(console.table) { 7590 console.table(performance); 7591 } 7592 else { 7593 $.each(performance, function(index, data) { 7594 console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); 7595 }); 7596 } 7597 console.groupEnd(); 7598 } 7599 performance = []; 7600 } 7601 }, 7602 invoke: function(query, passedArguments, context) { 7603 var 7604 object = instance, 7605 maxDepth, 7606 found, 7607 response 7608 ; 7609 passedArguments = passedArguments || queryArguments; 7610 context = element || context; 7611 if(typeof query == 'string' && object !== undefined) { 7612 query = query.split(/[\. ]/); 7613 maxDepth = query.length - 1; 7614 $.each(query, function(depth, value) { 7615 var camelCaseValue = (depth != maxDepth) 7616 ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) 7617 : query 7618 ; 7619 if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { 7620 object = object[camelCaseValue]; 7621 } 7622 else if( object[camelCaseValue] !== undefined ) { 7623 found = object[camelCaseValue]; 7624 return false; 7625 } 7626 else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { 7627 object = object[value]; 7628 } 7629 else if( object[value] !== undefined ) { 7630 found = object[value]; 7631 return false; 7632 } 7633 else { 7634 return false; 7635 } 7636 }); 7637 } 7638 if( $.isFunction( found ) ) { 7639 response = found.apply(context, passedArguments); 7640 } 7641 else if(found !== undefined) { 7642 response = found; 7643 } 7644 if(Array.isArray(returnedValue)) { 7645 returnedValue.push(response); 7646 } 7647 else if(returnedValue !== undefined) { 7648 returnedValue = [returnedValue, response]; 7649 } 7650 else if(response !== undefined) { 7651 returnedValue = response; 7652 } 7653 return found; 7654 } 7655 }; 7656 module.initialize(); 7657 }) 7658 ; 7659 7660 return (returnedValue !== undefined) 7661 ? returnedValue 7662 : this 7663 ; 7664 }; 7665 7666 $.fn.form.settings = { 7667 7668 name : 'Form', 7669 namespace : 'form', 7670 7671 debug : false, 7672 verbose : false, 7673 performance : true, 7674 7675 fields : false, 7676 7677 keyboardShortcuts : true, 7678 on : 'submit', 7679 inline : false, 7680 7681 delay : 200, 7682 revalidate : true, 7683 shouldTrim : true, 7684 7685 transition : 'scale', 7686 duration : 200, 7687 7688 autoCheckRequired : false, 7689 preventLeaving : false, 7690 dateHandling : 'date', // 'date', 'input', 'formatter' 7691 7692 onValid : function() {}, 7693 onInvalid : function() {}, 7694 onSuccess : function() { return true; }, 7695 onFailure : function() { return false; }, 7696 onDirty : function() {}, 7697 onClean : function() {}, 7698 7699 metadata : { 7700 defaultValue : 'default', 7701 validate : 'validate', 7702 isDirty : 'isDirty' 7703 }, 7704 7705 regExp: { 7706 htmlID : /^[a-zA-Z][\w:.-]*$/g, 7707 bracket : /\[(.*)\]/i, 7708 decimal : /^\d+\.?\d*$/, 7709 email : /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i, 7710 escape : /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|:,=@]/g, 7711 flags : /^\/(.*)\/(.*)?/, 7712 integer : /^\-?\d+$/, 7713 number : /^\-?\d*(\.\d+)?$/, 7714 url : /(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/i 7715 }, 7716 7717 text: { 7718 unspecifiedRule : 'Please enter a valid value', 7719 unspecifiedField : 'This field', 7720 leavingMessage : 'There are unsaved changes on this page which will be discarded if you continue.' 7721 }, 7722 7723 prompt: { 7724 empty : '{name} must have a value', 7725 checked : '{name} must be checked', 7726 email : '{name} must be a valid e-mail', 7727 url : '{name} must be a valid url', 7728 regExp : '{name} is not formatted correctly', 7729 integer : '{name} must be an integer', 7730 decimal : '{name} must be a decimal number', 7731 number : '{name} must be set to a number', 7732 is : '{name} must be "{ruleValue}"', 7733 isExactly : '{name} must be exactly "{ruleValue}"', 7734 not : '{name} cannot be set to "{ruleValue}"', 7735 notExactly : '{name} cannot be set to exactly "{ruleValue}"', 7736 contain : '{name} must contain "{ruleValue}"', 7737 containExactly : '{name} must contain exactly "{ruleValue}"', 7738 doesntContain : '{name} cannot contain "{ruleValue}"', 7739 doesntContainExactly : '{name} cannot contain exactly "{ruleValue}"', 7740 minLength : '{name} must be at least {ruleValue} characters', 7741 length : '{name} must be at least {ruleValue} characters', 7742 exactLength : '{name} must be exactly {ruleValue} characters', 7743 maxLength : '{name} cannot be longer than {ruleValue} characters', 7744 match : '{name} must match {ruleValue} field', 7745 different : '{name} must have a different value than {ruleValue} field', 7746 creditCard : '{name} must be a valid credit card number', 7747 minCount : '{name} must have at least {ruleValue} choices', 7748 exactCount : '{name} must have exactly {ruleValue} choices', 7749 maxCount : '{name} must have {ruleValue} or less choices' 7750 }, 7751 7752 selector : { 7753 checkbox : 'input[type="checkbox"], input[type="radio"]', 7754 clear : '.clear', 7755 field : 'input:not(.search), textarea, select', 7756 group : '.field', 7757 input : 'input', 7758 message : '.error.message', 7759 prompt : '.prompt.label', 7760 radio : 'input[type="radio"]', 7761 reset : '.reset:not([type="reset"])', 7762 submit : '.submit:not([type="submit"])', 7763 uiCheckbox : '.ui.checkbox', 7764 uiDropdown : '.ui.dropdown', 7765 uiCalendar : '.ui.calendar' 7766 }, 7767 7768 className : { 7769 error : 'error', 7770 label : 'ui basic red pointing prompt label', 7771 pressed : 'down', 7772 success : 'success', 7773 required : 'required', 7774 disabled : 'disabled' 7775 }, 7776 7777 error: { 7778 identifier : 'You must specify a string identifier for each field', 7779 method : 'The method you called is not defined.', 7780 noRule : 'There is no rule matching the one you specified', 7781 oldSyntax : 'Starting in 2.0 forms now only take a single settings object. Validation settings converted to new syntax automatically.', 7782 noElement : 'This module requires ui {element}' 7783 }, 7784 7785 templates: { 7786 7787 // template that produces error message 7788 error: function(errors) { 7789 var 7790 html = '<ul class="list">' 7791 ; 7792 $.each(errors, function(index, value) { 7793 html += '<li>' + value + '</li>'; 7794 }); 7795 html += '</ul>'; 7796 return $(html); 7797 }, 7798 7799 // template that produces label 7800 prompt: function(errors, labelClasses) { 7801 return $('<div/>') 7802 .addClass(labelClasses) 7803 .html(errors[0]) 7804 ; 7805 } 7806 }, 7807 7808 formatter: { 7809 date: function(date) { 7810 return Intl.DateTimeFormat('en-GB').format(date); 7811 }, 7812 datetime: function(date) { 7813 return Intl.DateTimeFormat('en-GB', { 7814 year: "numeric", 7815 month: "2-digit", 7816 day: "2-digit", 7817 hour: '2-digit', 7818 minute: '2-digit', 7819 second: '2-digit' 7820 }).format(date); 7821 }, 7822 time: function(date) { 7823 return Intl.DateTimeFormat('en-GB', { 7824 hour: '2-digit', 7825 minute: '2-digit', 7826 second: '2-digit' 7827 }).format(date); 7828 }, 7829 month: function(date) { 7830 return Intl.DateTimeFormat('en-GB', { 7831 month: '2-digit', 7832 year: 'numeric' 7833 }).format(date); 7834 }, 7835 year: function(date) { 7836 return Intl.DateTimeFormat('en-GB', { 7837 year: 'numeric' 7838 }).format(date); 7839 } 7840 }, 7841 7842 rules: { 7843 7844 // is not empty or blank string 7845 empty: function(value) { 7846 return !(value === undefined || '' === value || Array.isArray(value) && value.length === 0); 7847 }, 7848 7849 // checkbox checked 7850 checked: function() { 7851 return ($(this).filter(':checked').length > 0); 7852 }, 7853 7854 // is most likely an email 7855 email: function(value){ 7856 return $.fn.form.settings.regExp.email.test(value); 7857 }, 7858 7859 // value is most likely url 7860 url: function(value) { 7861 return $.fn.form.settings.regExp.url.test(value); 7862 }, 7863 7864 // matches specified regExp 7865 regExp: function(value, regExp) { 7866 if(regExp instanceof RegExp) { 7867 return value.match(regExp); 7868 } 7869 var 7870 regExpParts = regExp.match($.fn.form.settings.regExp.flags), 7871 flags 7872 ; 7873 // regular expression specified as /baz/gi (flags) 7874 if(regExpParts) { 7875 regExp = (regExpParts.length >= 2) 7876 ? regExpParts[1] 7877 : regExp 7878 ; 7879 flags = (regExpParts.length >= 3) 7880 ? regExpParts[2] 7881 : '' 7882 ; 7883 } 7884 return value.match( new RegExp(regExp, flags) ); 7885 }, 7886 7887 // is valid integer or matches range 7888 integer: function(value, range) { 7889 var 7890 intRegExp = $.fn.form.settings.regExp.integer, 7891 min, 7892 max, 7893 parts 7894 ; 7895 if( !range || ['', '..'].indexOf(range) !== -1) { 7896 // do nothing 7897 } 7898 else if(range.indexOf('..') == -1) { 7899 if(intRegExp.test(range)) { 7900 min = max = range - 0; 7901 } 7902 } 7903 else { 7904 parts = range.split('..', 2); 7905 if(intRegExp.test(parts[0])) { 7906 min = parts[0] - 0; 7907 } 7908 if(intRegExp.test(parts[1])) { 7909 max = parts[1] - 0; 7910 } 7911 } 7912 return ( 7913 intRegExp.test(value) && 7914 (min === undefined || value >= min) && 7915 (max === undefined || value <= max) 7916 ); 7917 }, 7918 7919 // is valid number (with decimal) 7920 decimal: function(value) { 7921 return $.fn.form.settings.regExp.decimal.test(value); 7922 }, 7923 7924 // is valid number 7925 number: function(value) { 7926 return $.fn.form.settings.regExp.number.test(value); 7927 }, 7928 7929 // is value (case insensitive) 7930 is: function(value, text) { 7931 text = (typeof text == 'string') 7932 ? text.toLowerCase() 7933 : text 7934 ; 7935 value = (typeof value == 'string') 7936 ? value.toLowerCase() 7937 : value 7938 ; 7939 return (value == text); 7940 }, 7941 7942 // is value 7943 isExactly: function(value, text) { 7944 return (value == text); 7945 }, 7946 7947 // value is not another value (case insensitive) 7948 not: function(value, notValue) { 7949 value = (typeof value == 'string') 7950 ? value.toLowerCase() 7951 : value 7952 ; 7953 notValue = (typeof notValue == 'string') 7954 ? notValue.toLowerCase() 7955 : notValue 7956 ; 7957 return (value != notValue); 7958 }, 7959 7960 // value is not another value (case sensitive) 7961 notExactly: function(value, notValue) { 7962 return (value != notValue); 7963 }, 7964 7965 // value contains text (insensitive) 7966 contains: function(value, text) { 7967 // escape regex characters 7968 text = text.replace($.fn.form.settings.regExp.escape, "\\$&"); 7969 return (value.search( new RegExp(text, 'i') ) !== -1); 7970 }, 7971 7972 // value contains text (case sensitive) 7973 containsExactly: function(value, text) { 7974 // escape regex characters 7975 text = text.replace($.fn.form.settings.regExp.escape, "\\$&"); 7976 return (value.search( new RegExp(text) ) !== -1); 7977 }, 7978 7979 // value contains text (insensitive) 7980 doesntContain: function(value, text) { 7981 // escape regex characters 7982 text = text.replace($.fn.form.settings.regExp.escape, "\\$&"); 7983 return (value.search( new RegExp(text, 'i') ) === -1); 7984 }, 7985 7986 // value contains text (case sensitive) 7987 doesntContainExactly: function(value, text) { 7988 // escape regex characters 7989 text = text.replace($.fn.form.settings.regExp.escape, "\\$&"); 7990 return (value.search( new RegExp(text) ) === -1); 7991 }, 7992 7993 // is at least string length 7994 minLength: function(value, requiredLength) { 7995 return (value !== undefined) 7996 ? (value.length >= requiredLength) 7997 : false 7998 ; 7999 }, 8000 8001 // see rls notes for 2.0.6 (this is a duplicate of minLength) 8002 length: function(value, requiredLength) { 8003 return (value !== undefined) 8004 ? (value.length >= requiredLength) 8005 : false 8006 ; 8007 }, 8008 8009 // is exactly length 8010 exactLength: function(value, requiredLength) { 8011 return (value !== undefined) 8012 ? (value.length == requiredLength) 8013 : false 8014 ; 8015 }, 8016 8017 // is less than length 8018 maxLength: function(value, maxLength) { 8019 return (value !== undefined) 8020 ? (value.length <= maxLength) 8021 : false 8022 ; 8023 }, 8024 8025 // matches another field 8026 match: function(value, identifier, $module) { 8027 var 8028 matchingValue, 8029 matchingElement 8030 ; 8031 if((matchingElement = $module.find('[data-validate="'+ identifier +'"]')).length > 0 ) { 8032 matchingValue = matchingElement.val(); 8033 } 8034 else if((matchingElement = $module.find('#' + identifier)).length > 0) { 8035 matchingValue = matchingElement.val(); 8036 } 8037 else if((matchingElement = $module.find('[name="' + identifier +'"]')).length > 0) { 8038 matchingValue = matchingElement.val(); 8039 } 8040 else if((matchingElement = $module.find('[name="' + identifier +'[]"]')).length > 0 ) { 8041 matchingValue = matchingElement; 8042 } 8043 return (matchingValue !== undefined) 8044 ? ( value.toString() == matchingValue.toString() ) 8045 : false 8046 ; 8047 }, 8048 8049 // different than another field 8050 different: function(value, identifier, $module) { 8051 // use either id or name of field 8052 var 8053 matchingValue, 8054 matchingElement 8055 ; 8056 if((matchingElement = $module.find('[data-validate="'+ identifier +'"]')).length > 0 ) { 8057 matchingValue = matchingElement.val(); 8058 } 8059 else if((matchingElement = $module.find('#' + identifier)).length > 0) { 8060 matchingValue = matchingElement.val(); 8061 } 8062 else if((matchingElement = $module.find('[name="' + identifier +'"]')).length > 0) { 8063 matchingValue = matchingElement.val(); 8064 } 8065 else if((matchingElement = $module.find('[name="' + identifier +'[]"]')).length > 0 ) { 8066 matchingValue = matchingElement; 8067 } 8068 return (matchingValue !== undefined) 8069 ? ( value.toString() !== matchingValue.toString() ) 8070 : false 8071 ; 8072 }, 8073 8074 creditCard: function(cardNumber, cardTypes) { 8075 var 8076 cards = { 8077 visa: { 8078 pattern : /^4/, 8079 length : [16] 8080 }, 8081 amex: { 8082 pattern : /^3[47]/, 8083 length : [15] 8084 }, 8085 mastercard: { 8086 pattern : /^5[1-5]/, 8087 length : [16] 8088 }, 8089 discover: { 8090 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)/, 8091 length : [16] 8092 }, 8093 unionPay: { 8094 pattern : /^(62|88)/, 8095 length : [16, 17, 18, 19] 8096 }, 8097 jcb: { 8098 pattern : /^35(2[89]|[3-8][0-9])/, 8099 length : [16] 8100 }, 8101 maestro: { 8102 pattern : /^(5018|5020|5038|6304|6759|676[1-3])/, 8103 length : [12, 13, 14, 15, 16, 17, 18, 19] 8104 }, 8105 dinersClub: { 8106 pattern : /^(30[0-5]|^36)/, 8107 length : [14] 8108 }, 8109 laser: { 8110 pattern : /^(6304|670[69]|6771)/, 8111 length : [16, 17, 18, 19] 8112 }, 8113 visaElectron: { 8114 pattern : /^(4026|417500|4508|4844|491(3|7))/, 8115 length : [16] 8116 } 8117 }, 8118 valid = {}, 8119 validCard = false, 8120 requiredTypes = (typeof cardTypes == 'string') 8121 ? cardTypes.split(',') 8122 : false, 8123 unionPay, 8124 validation 8125 ; 8126 8127 if(typeof cardNumber !== 'string' || cardNumber.length === 0) { 8128 return; 8129 } 8130 8131 // allow dashes in card 8132 cardNumber = cardNumber.replace(/[\-]/g, ''); 8133 8134 // verify card types 8135 if(requiredTypes) { 8136 $.each(requiredTypes, function(index, type){ 8137 // verify each card type 8138 validation = cards[type]; 8139 if(validation) { 8140 valid = { 8141 length : ($.inArray(cardNumber.length, validation.length) !== -1), 8142 pattern : (cardNumber.search(validation.pattern) !== -1) 8143 }; 8144 if(valid.length && valid.pattern) { 8145 validCard = true; 8146 } 8147 } 8148 }); 8149 8150 if(!validCard) { 8151 return false; 8152 } 8153 } 8154 8155 // skip luhn for UnionPay 8156 unionPay = { 8157 number : ($.inArray(cardNumber.length, cards.unionPay.length) !== -1), 8158 pattern : (cardNumber.search(cards.unionPay.pattern) !== -1) 8159 }; 8160 if(unionPay.number && unionPay.pattern) { 8161 return true; 8162 } 8163 8164 // verify luhn, adapted from <https://gist.github.com/2134376> 8165 var 8166 length = cardNumber.length, 8167 multiple = 0, 8168 producedValue = [ 8169 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 8170 [0, 2, 4, 6, 8, 1, 3, 5, 7, 9] 8171 ], 8172 sum = 0 8173 ; 8174 while (length--) { 8175 sum += producedValue[multiple][parseInt(cardNumber.charAt(length), 10)]; 8176 multiple ^= 1; 8177 } 8178 return (sum % 10 === 0 && sum > 0); 8179 }, 8180 8181 minCount: function(value, minCount) { 8182 if(minCount == 0) { 8183 return true; 8184 } 8185 if(minCount == 1) { 8186 return (value !== ''); 8187 } 8188 return (value.split(',').length >= minCount); 8189 }, 8190 8191 exactCount: function(value, exactCount) { 8192 if(exactCount == 0) { 8193 return (value === ''); 8194 } 8195 if(exactCount == 1) { 8196 return (value !== '' && value.search(',') === -1); 8197 } 8198 return (value.split(',').length == exactCount); 8199 }, 8200 8201 maxCount: function(value, maxCount) { 8202 if(maxCount == 0) { 8203 return false; 8204 } 8205 if(maxCount == 1) { 8206 return (value.search(',') === -1); 8207 } 8208 return (value.split(',').length <= maxCount); 8209 } 8210 } 8211 8212 }; 8213 8214 })( jQuery, window, document ); 8215 8216 /*! 8217 * # Fomantic-UI - Modal 8218 * http://github.com/fomantic/Fomantic-UI/ 8219 * 8220 * 8221 * Released under the MIT license 8222 * http://opensource.org/licenses/MIT 8223 * 8224 */ 8225 8226 ;(function ($, window, document, undefined) { 8227 8228 'use strict'; 8229 8230 $.isFunction = $.isFunction || function(obj) { 8231 return typeof obj === "function" && typeof obj.nodeType !== "number"; 8232 }; 8233 8234 window = (typeof window != 'undefined' && window.Math == Math) 8235 ? window 8236 : (typeof self != 'undefined' && self.Math == Math) 8237 ? self 8238 : Function('return this')() 8239 ; 8240 8241 $.fn.modal = function(parameters) { 8242 var 8243 $allModules = $(this), 8244 $window = $(window), 8245 $document = $(document), 8246 $body = $('body'), 8247 8248 moduleSelector = $allModules.selector || '', 8249 8250 time = new Date().getTime(), 8251 performance = [], 8252 8253 query = arguments[0], 8254 methodInvoked = (typeof query == 'string'), 8255 queryArguments = [].slice.call(arguments, 1), 8256 8257 requestAnimationFrame = window.requestAnimationFrame 8258 || window.mozRequestAnimationFrame 8259 || window.webkitRequestAnimationFrame 8260 || window.msRequestAnimationFrame 8261 || function(callback) { setTimeout(callback, 0); }, 8262 8263 returnedValue 8264 ; 8265 8266 $allModules 8267 .each(function() { 8268 var 8269 settings = ( $.isPlainObject(parameters) ) 8270 ? $.extend(true, {}, $.fn.modal.settings, parameters) 8271 : $.extend({}, $.fn.modal.settings), 8272 8273 selector = settings.selector, 8274 className = settings.className, 8275 namespace = settings.namespace, 8276 error = settings.error, 8277 8278 eventNamespace = '.' + namespace, 8279 moduleNamespace = 'module-' + namespace, 8280 8281 $module = $(this), 8282 $context = $(settings.context), 8283 $close = $module.find(selector.close), 8284 8285 $allModals, 8286 $otherModals, 8287 $focusedElement, 8288 $dimmable, 8289 $dimmer, 8290 8291 element = this, 8292 instance = $module.data(moduleNamespace), 8293 8294 ignoreRepeatedEvents = false, 8295 8296 initialMouseDownInModal, 8297 initialMouseDownInScrollbar, 8298 initialBodyMargin = '', 8299 tempBodyMargin = '', 8300 8301 elementEventNamespace, 8302 id, 8303 observer, 8304 module 8305 ; 8306 module = { 8307 8308 initialize: function() { 8309 module.cache = {}; 8310 module.verbose('Initializing dimmer', $context); 8311 8312 module.create.id(); 8313 module.create.dimmer(); 8314 8315 if ( settings.allowMultiple ) { 8316 module.create.innerDimmer(); 8317 } 8318 if (!settings.centered){ 8319 $module.addClass('top aligned'); 8320 } 8321 module.refreshModals(); 8322 8323 module.bind.events(); 8324 if(settings.observeChanges) { 8325 module.observeChanges(); 8326 } 8327 module.instantiate(); 8328 }, 8329 8330 instantiate: function() { 8331 module.verbose('Storing instance of modal'); 8332 instance = module; 8333 $module 8334 .data(moduleNamespace, instance) 8335 ; 8336 }, 8337 8338 create: { 8339 dimmer: function() { 8340 var 8341 defaultSettings = { 8342 debug : settings.debug, 8343 dimmerName : 'modals' 8344 }, 8345 dimmerSettings = $.extend(true, defaultSettings, settings.dimmerSettings) 8346 ; 8347 if($.fn.dimmer === undefined) { 8348 module.error(error.dimmer); 8349 return; 8350 } 8351 module.debug('Creating dimmer'); 8352 $dimmable = $context.dimmer(dimmerSettings); 8353 if(settings.detachable) { 8354 module.verbose('Modal is detachable, moving content into dimmer'); 8355 $dimmable.dimmer('add content', $module); 8356 } 8357 else { 8358 module.set.undetached(); 8359 } 8360 $dimmer = $dimmable.dimmer('get dimmer'); 8361 }, 8362 id: function() { 8363 id = (Math.random().toString(16) + '000000000').substr(2, 8); 8364 elementEventNamespace = '.' + id; 8365 module.verbose('Creating unique id for element', id); 8366 }, 8367 innerDimmer: function() { 8368 if ( $module.find(selector.dimmer).length == 0 ) { 8369 $module.prepend('<div class="ui inverted dimmer"></div>'); 8370 } 8371 } 8372 }, 8373 8374 destroy: function() { 8375 if (observer) { 8376 observer.disconnect(); 8377 } 8378 module.verbose('Destroying previous modal'); 8379 $module 8380 .removeData(moduleNamespace) 8381 .off(eventNamespace) 8382 ; 8383 $window.off(elementEventNamespace); 8384 $dimmer.off(elementEventNamespace); 8385 $close.off(eventNamespace); 8386 $context.dimmer('destroy'); 8387 }, 8388 8389 observeChanges: function() { 8390 if('MutationObserver' in window) { 8391 observer = new MutationObserver(function(mutations) { 8392 module.debug('DOM tree modified, refreshing'); 8393 module.refresh(); 8394 }); 8395 observer.observe(element, { 8396 childList : true, 8397 subtree : true 8398 }); 8399 module.debug('Setting up mutation observer', observer); 8400 } 8401 }, 8402 8403 refresh: function() { 8404 module.remove.scrolling(); 8405 module.cacheSizes(); 8406 if(!module.can.useFlex()) { 8407 module.set.modalOffset(); 8408 } 8409 module.set.screenHeight(); 8410 module.set.type(); 8411 }, 8412 8413 refreshModals: function() { 8414 $otherModals = $module.siblings(selector.modal); 8415 $allModals = $otherModals.add($module); 8416 }, 8417 8418 attachEvents: function(selector, event) { 8419 var 8420 $toggle = $(selector) 8421 ; 8422 event = $.isFunction(module[event]) 8423 ? module[event] 8424 : module.toggle 8425 ; 8426 if($toggle.length > 0) { 8427 module.debug('Attaching modal events to element', selector, event); 8428 $toggle 8429 .off(eventNamespace) 8430 .on('click' + eventNamespace, event) 8431 ; 8432 } 8433 else { 8434 module.error(error.notFound, selector); 8435 } 8436 }, 8437 8438 bind: { 8439 events: function() { 8440 module.verbose('Attaching events'); 8441 $module 8442 .on('click' + eventNamespace, selector.close, module.event.close) 8443 .on('click' + eventNamespace, selector.approve, module.event.approve) 8444 .on('click' + eventNamespace, selector.deny, module.event.deny) 8445 ; 8446 $window 8447 .on('resize' + elementEventNamespace, module.event.resize) 8448 ; 8449 }, 8450 scrollLock: function() { 8451 // touch events default to passive, due to changes in chrome to optimize mobile perf 8452 $dimmable.get(0).addEventListener('touchmove', module.event.preventScroll, { passive: false }); 8453 } 8454 }, 8455 8456 unbind: { 8457 scrollLock: function() { 8458 $dimmable.get(0).removeEventListener('touchmove', module.event.preventScroll, { passive: false }); 8459 } 8460 }, 8461 8462 get: { 8463 id: function() { 8464 return (Math.random().toString(16) + '000000000').substr(2, 8); 8465 } 8466 }, 8467 8468 event: { 8469 approve: function() { 8470 if(ignoreRepeatedEvents || settings.onApprove.call(element, $(this)) === false) { 8471 module.verbose('Approve callback returned false cancelling hide'); 8472 return; 8473 } 8474 ignoreRepeatedEvents = true; 8475 module.hide(function() { 8476 ignoreRepeatedEvents = false; 8477 }); 8478 }, 8479 preventScroll: function(event) { 8480 if(event.target.className.indexOf('dimmer') !== -1) { 8481 event.preventDefault(); 8482 } 8483 }, 8484 deny: function() { 8485 if(ignoreRepeatedEvents || settings.onDeny.call(element, $(this)) === false) { 8486 module.verbose('Deny callback returned false cancelling hide'); 8487 return; 8488 } 8489 ignoreRepeatedEvents = true; 8490 module.hide(function() { 8491 ignoreRepeatedEvents = false; 8492 }); 8493 }, 8494 close: function() { 8495 module.hide(); 8496 }, 8497 mousedown: function(event) { 8498 var 8499 $target = $(event.target), 8500 isRtl = module.is.rtl(); 8501 ; 8502 initialMouseDownInModal = ($target.closest(selector.modal).length > 0); 8503 if(initialMouseDownInModal) { 8504 module.verbose('Mouse down event registered inside the modal'); 8505 } 8506 initialMouseDownInScrollbar = module.is.scrolling() && ((!isRtl && $(window).outerWidth() - settings.scrollbarWidth <= event.clientX) || (isRtl && settings.scrollbarWidth >= event.clientX)); 8507 if(initialMouseDownInScrollbar) { 8508 module.verbose('Mouse down event registered inside the scrollbar'); 8509 } 8510 }, 8511 mouseup: function(event) { 8512 if(!settings.closable) { 8513 module.verbose('Dimmer clicked but closable setting is disabled'); 8514 return; 8515 } 8516 if(initialMouseDownInModal) { 8517 module.debug('Dimmer clicked but mouse down was initially registered inside the modal'); 8518 return; 8519 } 8520 if(initialMouseDownInScrollbar){ 8521 module.debug('Dimmer clicked but mouse down was initially registered inside the scrollbar'); 8522 return; 8523 } 8524 var 8525 $target = $(event.target), 8526 isInModal = ($target.closest(selector.modal).length > 0), 8527 isInDOM = $.contains(document.documentElement, event.target) 8528 ; 8529 if(!isInModal && isInDOM && module.is.active() && $module.hasClass(className.front) ) { 8530 module.debug('Dimmer clicked, hiding all modals'); 8531 if(settings.allowMultiple) { 8532 if(!module.hideAll()) { 8533 return; 8534 } 8535 } 8536 else if(!module.hide()){ 8537 return; 8538 } 8539 module.remove.clickaway(); 8540 } 8541 }, 8542 debounce: function(method, delay) { 8543 clearTimeout(module.timer); 8544 module.timer = setTimeout(method, delay); 8545 }, 8546 keyboard: function(event) { 8547 var 8548 keyCode = event.which, 8549 escapeKey = 27 8550 ; 8551 if(keyCode == escapeKey) { 8552 if(settings.closable) { 8553 module.debug('Escape key pressed hiding modal'); 8554 if ( $module.hasClass(className.front) ) { 8555 module.hide(); 8556 } 8557 } 8558 else { 8559 module.debug('Escape key pressed, but closable is set to false'); 8560 } 8561 event.preventDefault(); 8562 } 8563 }, 8564 resize: function() { 8565 if( $dimmable.dimmer('is active') && ( module.is.animating() || module.is.active() ) ) { 8566 requestAnimationFrame(module.refresh); 8567 } 8568 } 8569 }, 8570 8571 toggle: function() { 8572 if( module.is.active() || module.is.animating() ) { 8573 module.hide(); 8574 } 8575 else { 8576 module.show(); 8577 } 8578 }, 8579 8580 show: function(callback) { 8581 callback = $.isFunction(callback) 8582 ? callback 8583 : function(){} 8584 ; 8585 module.refreshModals(); 8586 module.set.dimmerSettings(); 8587 module.set.dimmerStyles(); 8588 8589 module.showModal(callback); 8590 }, 8591 8592 hide: function(callback) { 8593 callback = $.isFunction(callback) 8594 ? callback 8595 : function(){} 8596 ; 8597 module.refreshModals(); 8598 return module.hideModal(callback); 8599 }, 8600 8601 showModal: function(callback) { 8602 callback = $.isFunction(callback) 8603 ? callback 8604 : function(){} 8605 ; 8606 if( module.is.animating() || !module.is.active() ) { 8607 module.showDimmer(); 8608 module.cacheSizes(); 8609 module.set.bodyMargin(); 8610 if(module.can.useFlex()) { 8611 module.remove.legacy(); 8612 } 8613 else { 8614 module.set.legacy(); 8615 module.set.modalOffset(); 8616 module.debug('Using non-flex legacy modal positioning.'); 8617 } 8618 module.set.screenHeight(); 8619 module.set.type(); 8620 module.set.clickaway(); 8621 8622 if( !settings.allowMultiple && module.others.active() ) { 8623 module.hideOthers(module.showModal); 8624 } 8625 else { 8626 ignoreRepeatedEvents = false; 8627 if( settings.allowMultiple ) { 8628 if ( module.others.active() ) { 8629 $otherModals.filter('.' + className.active).find(selector.dimmer).addClass('active'); 8630 } 8631 8632 if ( settings.detachable ) { 8633 $module.detach().appendTo($dimmer); 8634 } 8635 } 8636 settings.onShow.call(element); 8637 if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) { 8638 module.debug('Showing modal with css animations'); 8639 $module 8640 .transition({ 8641 debug : settings.debug, 8642 animation : settings.transition + ' in', 8643 queue : settings.queue, 8644 duration : settings.duration, 8645 useFailSafe : true, 8646 onComplete : function() { 8647 settings.onVisible.apply(element); 8648 if(settings.keyboardShortcuts) { 8649 module.add.keyboardShortcuts(); 8650 } 8651 module.save.focus(); 8652 module.set.active(); 8653 if(settings.autofocus) { 8654 module.set.autofocus(); 8655 } 8656 callback(); 8657 } 8658 }) 8659 ; 8660 } 8661 else { 8662 module.error(error.noTransition); 8663 } 8664 } 8665 } 8666 else { 8667 module.debug('Modal is already visible'); 8668 } 8669 }, 8670 8671 hideModal: function(callback, keepDimmed, hideOthersToo) { 8672 var 8673 $previousModal = $otherModals.filter('.' + className.active).last() 8674 ; 8675 callback = $.isFunction(callback) 8676 ? callback 8677 : function(){} 8678 ; 8679 module.debug('Hiding modal'); 8680 if(settings.onHide.call(element, $(this)) === false) { 8681 module.verbose('Hide callback returned false cancelling hide'); 8682 ignoreRepeatedEvents = false; 8683 return false; 8684 } 8685 8686 if( module.is.animating() || module.is.active() ) { 8687 if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) { 8688 module.remove.active(); 8689 $module 8690 .transition({ 8691 debug : settings.debug, 8692 animation : settings.transition + ' out', 8693 queue : settings.queue, 8694 duration : settings.duration, 8695 useFailSafe : true, 8696 onStart : function() { 8697 if(!module.others.active() && !module.others.animating() && !keepDimmed) { 8698 module.hideDimmer(); 8699 } 8700 if( settings.keyboardShortcuts && !module.others.active() ) { 8701 module.remove.keyboardShortcuts(); 8702 } 8703 }, 8704 onComplete : function() { 8705 module.unbind.scrollLock(); 8706 if ( settings.allowMultiple ) { 8707 $previousModal.addClass(className.front); 8708 $module.removeClass(className.front); 8709 8710 if ( hideOthersToo ) { 8711 $allModals.find(selector.dimmer).removeClass('active'); 8712 } 8713 else { 8714 $previousModal.find(selector.dimmer).removeClass('active'); 8715 } 8716 } 8717 settings.onHidden.call(element); 8718 module.remove.dimmerStyles(); 8719 module.restore.focus(); 8720 callback(); 8721 } 8722 }) 8723 ; 8724 } 8725 else { 8726 module.error(error.noTransition); 8727 } 8728 } 8729 }, 8730 8731 showDimmer: function() { 8732 if($dimmable.dimmer('is animating') || !$dimmable.dimmer('is active') ) { 8733 module.save.bodyMargin(); 8734 module.debug('Showing dimmer'); 8735 $dimmable.dimmer('show'); 8736 } 8737 else { 8738 module.debug('Dimmer already visible'); 8739 } 8740 }, 8741 8742 hideDimmer: function() { 8743 if( $dimmable.dimmer('is animating') || ($dimmable.dimmer('is active')) ) { 8744 module.unbind.scrollLock(); 8745 $dimmable.dimmer('hide', function() { 8746 module.restore.bodyMargin(); 8747 module.remove.clickaway(); 8748 module.remove.screenHeight(); 8749 }); 8750 } 8751 else { 8752 module.debug('Dimmer is not visible cannot hide'); 8753 return; 8754 } 8755 }, 8756 8757 hideAll: function(callback) { 8758 var 8759 $visibleModals = $allModals.filter('.' + className.active + ', .' + className.animating) 8760 ; 8761 callback = $.isFunction(callback) 8762 ? callback 8763 : function(){} 8764 ; 8765 if( $visibleModals.length > 0 ) { 8766 module.debug('Hiding all visible modals'); 8767 var hideOk = true; 8768 //check in reverse order trying to hide most top displayed modal first 8769 $($visibleModals.get().reverse()).each(function(index,element){ 8770 if(hideOk){ 8771 hideOk = $(element).modal('hide modal', callback, false, true); 8772 } 8773 }); 8774 if(hideOk) { 8775 module.hideDimmer(); 8776 } 8777 return hideOk; 8778 } 8779 }, 8780 8781 hideOthers: function(callback) { 8782 var 8783 $visibleModals = $otherModals.filter('.' + className.active + ', .' + className.animating) 8784 ; 8785 callback = $.isFunction(callback) 8786 ? callback 8787 : function(){} 8788 ; 8789 if( $visibleModals.length > 0 ) { 8790 module.debug('Hiding other modals', $otherModals); 8791 $visibleModals 8792 .modal('hide modal', callback, true) 8793 ; 8794 } 8795 }, 8796 8797 others: { 8798 active: function() { 8799 return ($otherModals.filter('.' + className.active).length > 0); 8800 }, 8801 animating: function() { 8802 return ($otherModals.filter('.' + className.animating).length > 0); 8803 } 8804 }, 8805 8806 8807 add: { 8808 keyboardShortcuts: function() { 8809 module.verbose('Adding keyboard shortcuts'); 8810 $document 8811 .on('keyup' + eventNamespace, module.event.keyboard) 8812 ; 8813 } 8814 }, 8815 8816 save: { 8817 focus: function() { 8818 var 8819 $activeElement = $(document.activeElement), 8820 inCurrentModal = $activeElement.closest($module).length > 0 8821 ; 8822 if(!inCurrentModal) { 8823 $focusedElement = $(document.activeElement).blur(); 8824 } 8825 }, 8826 bodyMargin: function() { 8827 initialBodyMargin = $body.css('margin-'+(module.can.leftBodyScrollbar() ? 'left':'right')); 8828 var bodyMarginRightPixel = parseInt(initialBodyMargin.replace(/[^\d.]/g, '')), 8829 bodyScrollbarWidth = window.innerWidth - document.documentElement.clientWidth; 8830 tempBodyMargin = bodyMarginRightPixel + bodyScrollbarWidth; 8831 } 8832 }, 8833 8834 restore: { 8835 focus: function() { 8836 if($focusedElement && $focusedElement.length > 0 && settings.restoreFocus) { 8837 $focusedElement.focus(); 8838 } 8839 }, 8840 bodyMargin: function() { 8841 var position = module.can.leftBodyScrollbar() ? 'left':'right'; 8842 $body.css('margin-'+position, initialBodyMargin); 8843 $body.find(selector.bodyFixed.replace('right',position)).css('padding-'+position, initialBodyMargin); 8844 } 8845 }, 8846 8847 remove: { 8848 active: function() { 8849 $module.removeClass(className.active); 8850 }, 8851 legacy: function() { 8852 $module.removeClass(className.legacy); 8853 }, 8854 clickaway: function() { 8855 if (!settings.detachable) { 8856 $module 8857 .off('mousedown' + elementEventNamespace) 8858 ; 8859 } 8860 $dimmer 8861 .off('mousedown' + elementEventNamespace) 8862 ; 8863 $dimmer 8864 .off('mouseup' + elementEventNamespace) 8865 ; 8866 }, 8867 dimmerStyles: function() { 8868 $dimmer.removeClass(className.inverted); 8869 $dimmable.removeClass(className.blurring); 8870 }, 8871 bodyStyle: function() { 8872 if($body.attr('style') === '') { 8873 module.verbose('Removing style attribute'); 8874 $body.removeAttr('style'); 8875 } 8876 }, 8877 screenHeight: function() { 8878 module.debug('Removing page height'); 8879 $body 8880 .css('height', '') 8881 ; 8882 }, 8883 keyboardShortcuts: function() { 8884 module.verbose('Removing keyboard shortcuts'); 8885 $document 8886 .off('keyup' + eventNamespace) 8887 ; 8888 }, 8889 scrolling: function() { 8890 $dimmable.removeClass(className.scrolling); 8891 $module.removeClass(className.scrolling); 8892 } 8893 }, 8894 8895 cacheSizes: function() { 8896 $module.addClass(className.loading); 8897 var 8898 scrollHeight = $module.prop('scrollHeight'), 8899 modalWidth = $module.outerWidth(), 8900 modalHeight = $module.outerHeight() 8901 ; 8902 if(module.cache.pageHeight === undefined || modalHeight !== 0) { 8903 $.extend(module.cache, { 8904 pageHeight : $(document).outerHeight(), 8905 width : modalWidth, 8906 height : modalHeight + settings.offset, 8907 scrollHeight : scrollHeight + settings.offset, 8908 contextHeight : (settings.context == 'body') 8909 ? $(window).height() 8910 : $dimmable.height(), 8911 }); 8912 module.cache.topOffset = -(module.cache.height / 2); 8913 } 8914 $module.removeClass(className.loading); 8915 module.debug('Caching modal and container sizes', module.cache); 8916 }, 8917 8918 can: { 8919 leftBodyScrollbar: function(){ 8920 if(module.cache.leftBodyScrollbar === undefined) { 8921 module.cache.leftBodyScrollbar = module.is.rtl() && ((module.is.iframe && !module.is.firefox()) || module.is.safari() || module.is.edge() || module.is.ie()); 8922 } 8923 return module.cache.leftBodyScrollbar; 8924 }, 8925 useFlex: function() { 8926 if (settings.useFlex === 'auto') { 8927 return settings.detachable && !module.is.ie(); 8928 } 8929 if(settings.useFlex && module.is.ie()) { 8930 module.debug('useFlex true is not supported in IE'); 8931 } else if(settings.useFlex && !settings.detachable) { 8932 module.debug('useFlex true in combination with detachable false is not supported'); 8933 } 8934 return settings.useFlex; 8935 }, 8936 fit: function() { 8937 var 8938 contextHeight = module.cache.contextHeight, 8939 verticalCenter = module.cache.contextHeight / 2, 8940 topOffset = module.cache.topOffset, 8941 scrollHeight = module.cache.scrollHeight, 8942 height = module.cache.height, 8943 paddingHeight = settings.padding, 8944 startPosition = (verticalCenter + topOffset) 8945 ; 8946 return (scrollHeight > height) 8947 ? (startPosition + scrollHeight + paddingHeight < contextHeight) 8948 : (height + (paddingHeight * 2) < contextHeight) 8949 ; 8950 } 8951 }, 8952 8953 is: { 8954 active: function() { 8955 return $module.hasClass(className.active); 8956 }, 8957 ie: function() { 8958 if(module.cache.isIE === undefined) { 8959 var 8960 isIE11 = (!(window.ActiveXObject) && 'ActiveXObject' in window), 8961 isIE = ('ActiveXObject' in window) 8962 ; 8963 module.cache.isIE = (isIE11 || isIE); 8964 } 8965 return module.cache.isIE; 8966 }, 8967 animating: function() { 8968 return $module.transition('is supported') 8969 ? $module.transition('is animating') 8970 : $module.is(':visible') 8971 ; 8972 }, 8973 scrolling: function() { 8974 return $dimmable.hasClass(className.scrolling); 8975 }, 8976 modernBrowser: function() { 8977 // appName for IE11 reports 'Netscape' can no longer use 8978 return !(window.ActiveXObject || 'ActiveXObject' in window); 8979 }, 8980 rtl: function() { 8981 if(module.cache.isRTL === undefined) { 8982 module.cache.isRTL = $body.attr('dir') === 'rtl' || $body.css('direction') === 'rtl'; 8983 } 8984 return module.cache.isRTL; 8985 }, 8986 safari: function() { 8987 if(module.cache.isSafari === undefined) { 8988 module.cache.isSafari = /constructor/i.test(window.HTMLElement) || !!window.ApplePaySession; 8989 } 8990 return module.cache.isSafari; 8991 }, 8992 edge: function(){ 8993 if(module.cache.isEdge === undefined) { 8994 module.cache.isEdge = !!window.setImmediate && !module.is.ie(); 8995 } 8996 return module.cache.isEdge; 8997 }, 8998 firefox: function(){ 8999 if(module.cache.isFirefox === undefined) { 9000 module.cache.isFirefox = !!window.InstallTrigger; 9001 } 9002 return module.cache.isFirefox; 9003 }, 9004 iframe: function() { 9005 return !(self === top); 9006 } 9007 }, 9008 9009 set: { 9010 autofocus: function() { 9011 var 9012 $inputs = $module.find('[tabindex], :input').filter(':visible').filter(function() { 9013 return $(this).closest('.disabled').length === 0; 9014 }), 9015 $autofocus = $inputs.filter('[autofocus]'), 9016 $input = ($autofocus.length > 0) 9017 ? $autofocus.first() 9018 : $inputs.first() 9019 ; 9020 if($input.length > 0) { 9021 $input.focus(); 9022 } 9023 }, 9024 bodyMargin: function() { 9025 var position = module.can.leftBodyScrollbar() ? 'left':'right'; 9026 if(settings.detachable || module.can.fit()) { 9027 $body.css('margin-'+position, tempBodyMargin + 'px'); 9028 } 9029 $body.find(selector.bodyFixed.replace('right',position)).css('padding-'+position, tempBodyMargin + 'px'); 9030 }, 9031 clickaway: function() { 9032 if (!settings.detachable) { 9033 $module 9034 .on('mousedown' + elementEventNamespace, module.event.mousedown) 9035 ; 9036 } 9037 $dimmer 9038 .on('mousedown' + elementEventNamespace, module.event.mousedown) 9039 ; 9040 $dimmer 9041 .on('mouseup' + elementEventNamespace, module.event.mouseup) 9042 ; 9043 }, 9044 dimmerSettings: function() { 9045 if($.fn.dimmer === undefined) { 9046 module.error(error.dimmer); 9047 return; 9048 } 9049 var 9050 defaultSettings = { 9051 debug : settings.debug, 9052 dimmerName : 'modals', 9053 closable : 'auto', 9054 useFlex : module.can.useFlex(), 9055 duration : { 9056 show : settings.duration, 9057 hide : settings.duration 9058 } 9059 }, 9060 dimmerSettings = $.extend(true, defaultSettings, settings.dimmerSettings) 9061 ; 9062 if(settings.inverted) { 9063 dimmerSettings.variation = (dimmerSettings.variation !== undefined) 9064 ? dimmerSettings.variation + ' inverted' 9065 : 'inverted' 9066 ; 9067 } 9068 $context.dimmer('setting', dimmerSettings); 9069 }, 9070 dimmerStyles: function() { 9071 if(settings.inverted) { 9072 $dimmer.addClass(className.inverted); 9073 } 9074 else { 9075 $dimmer.removeClass(className.inverted); 9076 } 9077 if(settings.blurring) { 9078 $dimmable.addClass(className.blurring); 9079 } 9080 else { 9081 $dimmable.removeClass(className.blurring); 9082 } 9083 }, 9084 modalOffset: function() { 9085 if (!settings.detachable) { 9086 var canFit = module.can.fit(); 9087 $module 9088 .css({ 9089 top: (!$module.hasClass('aligned') && canFit) 9090 ? $(document).scrollTop() + (module.cache.contextHeight - module.cache.height) / 2 9091 : !canFit || $module.hasClass('top') 9092 ? $(document).scrollTop() + settings.padding 9093 : $(document).scrollTop() + (module.cache.contextHeight - module.cache.height - settings.padding), 9094 marginLeft: -(module.cache.width / 2) 9095 }) 9096 ; 9097 } else { 9098 $module 9099 .css({ 9100 marginTop: (!$module.hasClass('aligned') && module.can.fit()) 9101 ? -(module.cache.height / 2) 9102 : settings.padding / 2, 9103 marginLeft: -(module.cache.width / 2) 9104 }) 9105 ; 9106 } 9107 module.verbose('Setting modal offset for legacy mode'); 9108 }, 9109 screenHeight: function() { 9110 if( module.can.fit() ) { 9111 $body.css('height', ''); 9112 } 9113 else if(!$module.hasClass('bottom')) { 9114 module.debug('Modal is taller than page content, resizing page height'); 9115 $body 9116 .css('height', module.cache.height + (settings.padding * 2) ) 9117 ; 9118 } 9119 }, 9120 active: function() { 9121 $module.addClass(className.active + ' ' + className.front); 9122 $otherModals.filter('.' + className.active).removeClass(className.front); 9123 }, 9124 scrolling: function() { 9125 $dimmable.addClass(className.scrolling); 9126 $module.addClass(className.scrolling); 9127 module.unbind.scrollLock(); 9128 }, 9129 legacy: function() { 9130 $module.addClass(className.legacy); 9131 }, 9132 type: function() { 9133 if(module.can.fit()) { 9134 module.verbose('Modal fits on screen'); 9135 if(!module.others.active() && !module.others.animating()) { 9136 module.remove.scrolling(); 9137 module.bind.scrollLock(); 9138 } 9139 } 9140 else if (!$module.hasClass('bottom')){ 9141 module.verbose('Modal cannot fit on screen setting to scrolling'); 9142 module.set.scrolling(); 9143 } else { 9144 module.verbose('Bottom aligned modal not fitting on screen is unsupported for scrolling'); 9145 } 9146 }, 9147 undetached: function() { 9148 $dimmable.addClass(className.undetached); 9149 } 9150 }, 9151 9152 setting: function(name, value) { 9153 module.debug('Changing setting', name, value); 9154 if( $.isPlainObject(name) ) { 9155 $.extend(true, settings, name); 9156 } 9157 else if(value !== undefined) { 9158 if($.isPlainObject(settings[name])) { 9159 $.extend(true, settings[name], value); 9160 } 9161 else { 9162 settings[name] = value; 9163 } 9164 } 9165 else { 9166 return settings[name]; 9167 } 9168 }, 9169 internal: function(name, value) { 9170 if( $.isPlainObject(name) ) { 9171 $.extend(true, module, name); 9172 } 9173 else if(value !== undefined) { 9174 module[name] = value; 9175 } 9176 else { 9177 return module[name]; 9178 } 9179 }, 9180 debug: function() { 9181 if(!settings.silent && settings.debug) { 9182 if(settings.performance) { 9183 module.performance.log(arguments); 9184 } 9185 else { 9186 module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); 9187 module.debug.apply(console, arguments); 9188 } 9189 } 9190 }, 9191 verbose: function() { 9192 if(!settings.silent && settings.verbose && settings.debug) { 9193 if(settings.performance) { 9194 module.performance.log(arguments); 9195 } 9196 else { 9197 module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); 9198 module.verbose.apply(console, arguments); 9199 } 9200 } 9201 }, 9202 error: function() { 9203 if(!settings.silent) { 9204 module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); 9205 module.error.apply(console, arguments); 9206 } 9207 }, 9208 performance: { 9209 log: function(message) { 9210 var 9211 currentTime, 9212 executionTime, 9213 previousTime 9214 ; 9215 if(settings.performance) { 9216 currentTime = new Date().getTime(); 9217 previousTime = time || currentTime; 9218 executionTime = currentTime - previousTime; 9219 time = currentTime; 9220 performance.push({ 9221 'Name' : message[0], 9222 'Arguments' : [].slice.call(message, 1) || '', 9223 'Element' : element, 9224 'Execution Time' : executionTime 9225 }); 9226 } 9227 clearTimeout(module.performance.timer); 9228 module.performance.timer = setTimeout(module.performance.display, 500); 9229 }, 9230 display: function() { 9231 var 9232 title = settings.name + ':', 9233 totalTime = 0 9234 ; 9235 time = false; 9236 clearTimeout(module.performance.timer); 9237 $.each(performance, function(index, data) { 9238 totalTime += data['Execution Time']; 9239 }); 9240 title += ' ' + totalTime + 'ms'; 9241 if(moduleSelector) { 9242 title += ' \'' + moduleSelector + '\''; 9243 } 9244 if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { 9245 console.groupCollapsed(title); 9246 if(console.table) { 9247 console.table(performance); 9248 } 9249 else { 9250 $.each(performance, function(index, data) { 9251 console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); 9252 }); 9253 } 9254 console.groupEnd(); 9255 } 9256 performance = []; 9257 } 9258 }, 9259 invoke: function(query, passedArguments, context) { 9260 var 9261 object = instance, 9262 maxDepth, 9263 found, 9264 response 9265 ; 9266 passedArguments = passedArguments || queryArguments; 9267 context = element || context; 9268 if(typeof query == 'string' && object !== undefined) { 9269 query = query.split(/[\. ]/); 9270 maxDepth = query.length - 1; 9271 $.each(query, function(depth, value) { 9272 var camelCaseValue = (depth != maxDepth) 9273 ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) 9274 : query 9275 ; 9276 if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { 9277 object = object[camelCaseValue]; 9278 } 9279 else if( object[camelCaseValue] !== undefined ) { 9280 found = object[camelCaseValue]; 9281 return false; 9282 } 9283 else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { 9284 object = object[value]; 9285 } 9286 else if( object[value] !== undefined ) { 9287 found = object[value]; 9288 return false; 9289 } 9290 else { 9291 return false; 9292 } 9293 }); 9294 } 9295 if ( $.isFunction( found ) ) { 9296 response = found.apply(context, passedArguments); 9297 } 9298 else if(found !== undefined) { 9299 response = found; 9300 } 9301 if(Array.isArray(returnedValue)) { 9302 returnedValue.push(response); 9303 } 9304 else if(returnedValue !== undefined) { 9305 returnedValue = [returnedValue, response]; 9306 } 9307 else if(response !== undefined) { 9308 returnedValue = response; 9309 } 9310 return found; 9311 } 9312 }; 9313 9314 if(methodInvoked) { 9315 if(instance === undefined) { 9316 module.initialize(); 9317 } 9318 module.invoke(query); 9319 } 9320 else { 9321 if(instance !== undefined) { 9322 instance.invoke('destroy'); 9323 } 9324 module.initialize(); 9325 } 9326 }) 9327 ; 9328 9329 return (returnedValue !== undefined) 9330 ? returnedValue 9331 : this 9332 ; 9333 }; 9334 9335 $.fn.modal.settings = { 9336 9337 name : 'Modal', 9338 namespace : 'modal', 9339 9340 useFlex : 'auto', 9341 offset : 0, 9342 9343 silent : false, 9344 debug : false, 9345 verbose : false, 9346 performance : true, 9347 9348 observeChanges : false, 9349 9350 allowMultiple : false, 9351 detachable : true, 9352 closable : true, 9353 autofocus : true, 9354 restoreFocus : true, 9355 9356 inverted : false, 9357 blurring : false, 9358 9359 centered : true, 9360 9361 dimmerSettings : { 9362 closable : false, 9363 useCSS : true 9364 }, 9365 9366 // whether to use keyboard shortcuts 9367 keyboardShortcuts: true, 9368 9369 context : 'body', 9370 9371 queue : false, 9372 duration : 500, 9373 transition : 'scale', 9374 9375 // padding with edge of page 9376 padding : 50, 9377 scrollbarWidth: 10, 9378 9379 // called before show animation 9380 onShow : function(){}, 9381 9382 // called after show animation 9383 onVisible : function(){}, 9384 9385 // called before hide animation 9386 onHide : function(){ return true; }, 9387 9388 // called after hide animation 9389 onHidden : function(){}, 9390 9391 // called after approve selector match 9392 onApprove : function(){ return true; }, 9393 9394 // called after deny selector match 9395 onDeny : function(){ return true; }, 9396 9397 selector : { 9398 close : '> .close', 9399 approve : '.actions .positive, .actions .approve, .actions .ok', 9400 deny : '.actions .negative, .actions .deny, .actions .cancel', 9401 modal : '.ui.modal', 9402 dimmer : '> .ui.dimmer', 9403 bodyFixed: '> .ui.fixed.menu, > .ui.right.toast-container, > .ui.right.sidebar' 9404 }, 9405 error : { 9406 dimmer : 'UI Dimmer, a required component is not included in this page', 9407 method : 'The method you called is not defined.', 9408 notFound : 'The element you specified could not be found' 9409 }, 9410 className : { 9411 active : 'active', 9412 animating : 'animating', 9413 blurring : 'blurring', 9414 inverted : 'inverted', 9415 legacy : 'legacy', 9416 loading : 'loading', 9417 scrolling : 'scrolling', 9418 undetached : 'undetached', 9419 front : 'front' 9420 } 9421 }; 9422 9423 9424 })( jQuery, window, document ); 9425 9426 /*! 9427 * # Fomantic-UI - Search 9428 * http://github.com/fomantic/Fomantic-UI/ 9429 * 9430 * 9431 * Released under the MIT license 9432 * http://opensource.org/licenses/MIT 9433 * 9434 */ 9435 9436 ;(function ($, window, document, undefined) { 9437 9438 'use strict'; 9439 9440 $.isFunction = $.isFunction || function(obj) { 9441 return typeof obj === "function" && typeof obj.nodeType !== "number"; 9442 }; 9443 9444 window = (typeof window != 'undefined' && window.Math == Math) 9445 ? window 9446 : (typeof self != 'undefined' && self.Math == Math) 9447 ? self 9448 : Function('return this')() 9449 ; 9450 9451 $.fn.search = function(parameters) { 9452 var 9453 $allModules = $(this), 9454 moduleSelector = $allModules.selector || '', 9455 9456 time = new Date().getTime(), 9457 performance = [], 9458 9459 query = arguments[0], 9460 methodInvoked = (typeof query == 'string'), 9461 queryArguments = [].slice.call(arguments, 1), 9462 returnedValue 9463 ; 9464 $(this) 9465 .each(function() { 9466 var 9467 settings = ( $.isPlainObject(parameters) ) 9468 ? $.extend(true, {}, $.fn.search.settings, parameters) 9469 : $.extend({}, $.fn.search.settings), 9470 9471 className = settings.className, 9472 metadata = settings.metadata, 9473 regExp = settings.regExp, 9474 fields = settings.fields, 9475 selector = settings.selector, 9476 error = settings.error, 9477 namespace = settings.namespace, 9478 9479 eventNamespace = '.' + namespace, 9480 moduleNamespace = namespace + '-module', 9481 9482 $module = $(this), 9483 $prompt = $module.find(selector.prompt), 9484 $searchButton = $module.find(selector.searchButton), 9485 $results = $module.find(selector.results), 9486 $result = $module.find(selector.result), 9487 $category = $module.find(selector.category), 9488 9489 element = this, 9490 instance = $module.data(moduleNamespace), 9491 9492 disabledBubbled = false, 9493 resultsDismissed = false, 9494 9495 module 9496 ; 9497 9498 module = { 9499 9500 initialize: function() { 9501 module.verbose('Initializing module'); 9502 module.get.settings(); 9503 module.determine.searchFields(); 9504 module.bind.events(); 9505 module.set.type(); 9506 module.create.results(); 9507 module.instantiate(); 9508 }, 9509 instantiate: function() { 9510 module.verbose('Storing instance of module', module); 9511 instance = module; 9512 $module 9513 .data(moduleNamespace, module) 9514 ; 9515 }, 9516 destroy: function() { 9517 module.verbose('Destroying instance'); 9518 $module 9519 .off(eventNamespace) 9520 .removeData(moduleNamespace) 9521 ; 9522 }, 9523 9524 refresh: function() { 9525 module.debug('Refreshing selector cache'); 9526 $prompt = $module.find(selector.prompt); 9527 $searchButton = $module.find(selector.searchButton); 9528 $category = $module.find(selector.category); 9529 $results = $module.find(selector.results); 9530 $result = $module.find(selector.result); 9531 }, 9532 9533 refreshResults: function() { 9534 $results = $module.find(selector.results); 9535 $result = $module.find(selector.result); 9536 }, 9537 9538 bind: { 9539 events: function() { 9540 module.verbose('Binding events to search'); 9541 if(settings.automatic) { 9542 $module 9543 .on(module.get.inputEvent() + eventNamespace, selector.prompt, module.event.input) 9544 ; 9545 $prompt 9546 .attr('autocomplete', 'off') 9547 ; 9548 } 9549 $module 9550 // prompt 9551 .on('focus' + eventNamespace, selector.prompt, module.event.focus) 9552 .on('blur' + eventNamespace, selector.prompt, module.event.blur) 9553 .on('keydown' + eventNamespace, selector.prompt, module.handleKeyboard) 9554 // search button 9555 .on('click' + eventNamespace, selector.searchButton, module.query) 9556 // results 9557 .on('mousedown' + eventNamespace, selector.results, module.event.result.mousedown) 9558 .on('mouseup' + eventNamespace, selector.results, module.event.result.mouseup) 9559 .on('click' + eventNamespace, selector.result, module.event.result.click) 9560 ; 9561 } 9562 }, 9563 9564 determine: { 9565 searchFields: function() { 9566 // this makes sure $.extend does not add specified search fields to default fields 9567 // this is the only setting which should not extend defaults 9568 if(parameters && parameters.searchFields !== undefined) { 9569 settings.searchFields = parameters.searchFields; 9570 } 9571 } 9572 }, 9573 9574 event: { 9575 input: function() { 9576 if(settings.searchDelay) { 9577 clearTimeout(module.timer); 9578 module.timer = setTimeout(function() { 9579 if(module.is.focused()) { 9580 module.query(); 9581 } 9582 }, settings.searchDelay); 9583 } 9584 else { 9585 module.query(); 9586 } 9587 }, 9588 focus: function() { 9589 module.set.focus(); 9590 if(settings.searchOnFocus && module.has.minimumCharacters() ) { 9591 module.query(function() { 9592 if(module.can.show() ) { 9593 module.showResults(); 9594 } 9595 }); 9596 } 9597 }, 9598 blur: function(event) { 9599 var 9600 pageLostFocus = (document.activeElement === this), 9601 callback = function() { 9602 module.cancel.query(); 9603 module.remove.focus(); 9604 module.timer = setTimeout(module.hideResults, settings.hideDelay); 9605 } 9606 ; 9607 if(pageLostFocus) { 9608 return; 9609 } 9610 resultsDismissed = false; 9611 if(module.resultsClicked) { 9612 module.debug('Determining if user action caused search to close'); 9613 $module 9614 .one('click.close' + eventNamespace, selector.results, function(event) { 9615 if(module.is.inMessage(event) || disabledBubbled) { 9616 $prompt.focus(); 9617 return; 9618 } 9619 disabledBubbled = false; 9620 if( !module.is.animating() && !module.is.hidden()) { 9621 callback(); 9622 } 9623 }) 9624 ; 9625 } 9626 else { 9627 module.debug('Input blurred without user action, closing results'); 9628 callback(); 9629 } 9630 }, 9631 result: { 9632 mousedown: function() { 9633 module.resultsClicked = true; 9634 }, 9635 mouseup: function() { 9636 module.resultsClicked = false; 9637 }, 9638 click: function(event) { 9639 module.debug('Search result selected'); 9640 var 9641 $result = $(this), 9642 $title = $result.find(selector.title).eq(0), 9643 $link = $result.is('a[href]') 9644 ? $result 9645 : $result.find('a[href]').eq(0), 9646 href = $link.attr('href') || false, 9647 target = $link.attr('target') || false, 9648 // title is used for result lookup 9649 value = ($title.length > 0) 9650 ? $title.text() 9651 : false, 9652 results = module.get.results(), 9653 result = $result.data(metadata.result) || module.get.result(value, results) 9654 ; 9655 if(value) { 9656 module.set.value(value); 9657 } 9658 if( $.isFunction(settings.onSelect) ) { 9659 if(settings.onSelect.call(element, result, results) === false) { 9660 module.debug('Custom onSelect callback cancelled default select action'); 9661 disabledBubbled = true; 9662 return; 9663 } 9664 } 9665 module.hideResults(); 9666 if(href) { 9667 event.preventDefault(); 9668 module.verbose('Opening search link found in result', $link); 9669 if(target == '_blank' || event.ctrlKey) { 9670 window.open(href); 9671 } 9672 else { 9673 window.location.href = (href); 9674 } 9675 } 9676 } 9677 } 9678 }, 9679 ensureVisible: function ensureVisible($el) { 9680 var elTop, elBottom, resultsScrollTop, resultsHeight; 9681 9682 elTop = $el.position().top; 9683 elBottom = elTop + $el.outerHeight(true); 9684 9685 resultsScrollTop = $results.scrollTop(); 9686 resultsHeight = $results.height() 9687 parseInt($results.css('paddingTop'), 0) + 9688 parseInt($results.css('paddingBottom'), 0); 9689 9690 if (elTop < 0) { 9691 $results.scrollTop(resultsScrollTop + elTop); 9692 } 9693 9694 else if (resultsHeight < elBottom) { 9695 $results.scrollTop(resultsScrollTop + (elBottom - resultsHeight)); 9696 } 9697 }, 9698 handleKeyboard: function(event) { 9699 var 9700 // force selector refresh 9701 $result = $module.find(selector.result), 9702 $category = $module.find(selector.category), 9703 $activeResult = $result.filter('.' + className.active), 9704 currentIndex = $result.index( $activeResult ), 9705 resultSize = $result.length, 9706 hasActiveResult = $activeResult.length > 0, 9707 9708 keyCode = event.which, 9709 keys = { 9710 backspace : 8, 9711 enter : 13, 9712 escape : 27, 9713 upArrow : 38, 9714 downArrow : 40 9715 }, 9716 newIndex 9717 ; 9718 // search shortcuts 9719 if(keyCode == keys.escape) { 9720 module.verbose('Escape key pressed, blurring search field'); 9721 module.hideResults(); 9722 resultsDismissed = true; 9723 } 9724 if( module.is.visible() ) { 9725 if(keyCode == keys.enter) { 9726 module.verbose('Enter key pressed, selecting active result'); 9727 if( $result.filter('.' + className.active).length > 0 ) { 9728 module.event.result.click.call($result.filter('.' + className.active), event); 9729 event.preventDefault(); 9730 return false; 9731 } 9732 } 9733 else if(keyCode == keys.upArrow && hasActiveResult) { 9734 module.verbose('Up key pressed, changing active result'); 9735 newIndex = (currentIndex - 1 < 0) 9736 ? currentIndex 9737 : currentIndex - 1 9738 ; 9739 $category 9740 .removeClass(className.active) 9741 ; 9742 $result 9743 .removeClass(className.active) 9744 .eq(newIndex) 9745 .addClass(className.active) 9746 .closest($category) 9747 .addClass(className.active) 9748 ; 9749 module.ensureVisible($result.eq(newIndex)); 9750 event.preventDefault(); 9751 } 9752 else if(keyCode == keys.downArrow) { 9753 module.verbose('Down key pressed, changing active result'); 9754 newIndex = (currentIndex + 1 >= resultSize) 9755 ? currentIndex 9756 : currentIndex + 1 9757 ; 9758 $category 9759 .removeClass(className.active) 9760 ; 9761 $result 9762 .removeClass(className.active) 9763 .eq(newIndex) 9764 .addClass(className.active) 9765 .closest($category) 9766 .addClass(className.active) 9767 ; 9768 module.ensureVisible($result.eq(newIndex)); 9769 event.preventDefault(); 9770 } 9771 } 9772 else { 9773 // query shortcuts 9774 if(keyCode == keys.enter) { 9775 module.verbose('Enter key pressed, executing query'); 9776 module.query(); 9777 module.set.buttonPressed(); 9778 $prompt.one('keyup', module.remove.buttonFocus); 9779 } 9780 } 9781 }, 9782 9783 setup: { 9784 api: function(searchTerm, callback) { 9785 var 9786 apiSettings = { 9787 debug : settings.debug, 9788 on : false, 9789 cache : settings.cache, 9790 action : 'search', 9791 urlData : { 9792 query : searchTerm 9793 }, 9794 onSuccess : function(response) { 9795 module.parse.response.call(element, response, searchTerm); 9796 callback(); 9797 }, 9798 onFailure : function() { 9799 module.displayMessage(error.serverError); 9800 callback(); 9801 }, 9802 onAbort : function(response) { 9803 }, 9804 onError : module.error 9805 } 9806 ; 9807 $.extend(true, apiSettings, settings.apiSettings); 9808 module.verbose('Setting up API request', apiSettings); 9809 $module.api(apiSettings); 9810 } 9811 }, 9812 9813 can: { 9814 useAPI: function() { 9815 return $.fn.api !== undefined; 9816 }, 9817 show: function() { 9818 return module.is.focused() && !module.is.visible() && !module.is.empty(); 9819 }, 9820 transition: function() { 9821 return settings.transition && $.fn.transition !== undefined && $module.transition('is supported'); 9822 } 9823 }, 9824 9825 is: { 9826 animating: function() { 9827 return $results.hasClass(className.animating); 9828 }, 9829 hidden: function() { 9830 return $results.hasClass(className.hidden); 9831 }, 9832 inMessage: function(event) { 9833 if(!event.target) { 9834 return; 9835 } 9836 var 9837 $target = $(event.target), 9838 isInDOM = $.contains(document.documentElement, event.target) 9839 ; 9840 return (isInDOM && $target.closest(selector.message).length > 0); 9841 }, 9842 empty: function() { 9843 return ($results.html() === ''); 9844 }, 9845 visible: function() { 9846 return ($results.filter(':visible').length > 0); 9847 }, 9848 focused: function() { 9849 return ($prompt.filter(':focus').length > 0); 9850 } 9851 }, 9852 9853 get: { 9854 settings: function() { 9855 if($.isPlainObject(parameters) && parameters.searchFullText) { 9856 settings.fullTextSearch = parameters.searchFullText; 9857 module.error(settings.error.oldSearchSyntax, element); 9858 } 9859 if (settings.ignoreDiacritics && !String.prototype.normalize) { 9860 settings.ignoreDiacritics = false; 9861 module.error(error.noNormalize, element); 9862 } 9863 }, 9864 inputEvent: function() { 9865 var 9866 prompt = $prompt[0], 9867 inputEvent = (prompt !== undefined && prompt.oninput !== undefined) 9868 ? 'input' 9869 : (prompt !== undefined && prompt.onpropertychange !== undefined) 9870 ? 'propertychange' 9871 : 'keyup' 9872 ; 9873 return inputEvent; 9874 }, 9875 value: function() { 9876 return $prompt.val(); 9877 }, 9878 results: function() { 9879 var 9880 results = $module.data(metadata.results) 9881 ; 9882 return results; 9883 }, 9884 result: function(value, results) { 9885 var 9886 result = false 9887 ; 9888 value = (value !== undefined) 9889 ? value 9890 : module.get.value() 9891 ; 9892 results = (results !== undefined) 9893 ? results 9894 : module.get.results() 9895 ; 9896 if(settings.type === 'category') { 9897 module.debug('Finding result that matches', value); 9898 $.each(results, function(index, category) { 9899 if(Array.isArray(category.results)) { 9900 result = module.search.object(value, category.results)[0]; 9901 // don't continue searching if a result is found 9902 if(result) { 9903 return false; 9904 } 9905 } 9906 }); 9907 } 9908 else { 9909 module.debug('Finding result in results object', value); 9910 result = module.search.object(value, results)[0]; 9911 } 9912 return result || false; 9913 }, 9914 }, 9915 9916 select: { 9917 firstResult: function() { 9918 module.verbose('Selecting first result'); 9919 $result.first().addClass(className.active); 9920 } 9921 }, 9922 9923 set: { 9924 focus: function() { 9925 $module.addClass(className.focus); 9926 }, 9927 loading: function() { 9928 $module.addClass(className.loading); 9929 }, 9930 value: function(value) { 9931 module.verbose('Setting search input value', value); 9932 $prompt 9933 .val(value) 9934 ; 9935 }, 9936 type: function(type) { 9937 type = type || settings.type; 9938 if(settings.type == 'category') { 9939 $module.addClass(settings.type); 9940 } 9941 }, 9942 buttonPressed: function() { 9943 $searchButton.addClass(className.pressed); 9944 } 9945 }, 9946 9947 remove: { 9948 loading: function() { 9949 $module.removeClass(className.loading); 9950 }, 9951 focus: function() { 9952 $module.removeClass(className.focus); 9953 }, 9954 buttonPressed: function() { 9955 $searchButton.removeClass(className.pressed); 9956 }, 9957 diacritics: function(text) { 9958 return settings.ignoreDiacritics ? text.normalize('NFD').replace(/[\u0300-\u036f]/g, '') : text; 9959 } 9960 }, 9961 9962 query: function(callback) { 9963 callback = $.isFunction(callback) 9964 ? callback 9965 : function(){} 9966 ; 9967 var 9968 searchTerm = module.get.value(), 9969 cache = module.read.cache(searchTerm) 9970 ; 9971 callback = callback || function() {}; 9972 if( module.has.minimumCharacters() ) { 9973 if(cache) { 9974 module.debug('Reading result from cache', searchTerm); 9975 module.save.results(cache.results); 9976 module.addResults(cache.html); 9977 module.inject.id(cache.results); 9978 callback(); 9979 } 9980 else { 9981 module.debug('Querying for', searchTerm); 9982 if($.isPlainObject(settings.source) || Array.isArray(settings.source)) { 9983 module.search.local(searchTerm); 9984 callback(); 9985 } 9986 else if( module.can.useAPI() ) { 9987 module.search.remote(searchTerm, callback); 9988 } 9989 else { 9990 module.error(error.source); 9991 callback(); 9992 } 9993 } 9994 settings.onSearchQuery.call(element, searchTerm); 9995 } 9996 else { 9997 module.hideResults(); 9998 } 9999 }, 10000 10001 search: { 10002 local: function(searchTerm) { 10003 var 10004 results = module.search.object(searchTerm, settings.source), 10005 searchHTML 10006 ; 10007 module.set.loading(); 10008 module.save.results(results); 10009 module.debug('Returned full local search results', results); 10010 if(settings.maxResults > 0) { 10011 module.debug('Using specified max results', results); 10012 results = results.slice(0, settings.maxResults); 10013 } 10014 if(settings.type == 'category') { 10015 results = module.create.categoryResults(results); 10016 } 10017 searchHTML = module.generateResults({ 10018 results: results 10019 }); 10020 module.remove.loading(); 10021 module.addResults(searchHTML); 10022 module.inject.id(results); 10023 module.write.cache(searchTerm, { 10024 html : searchHTML, 10025 results : results 10026 }); 10027 }, 10028 remote: function(searchTerm, callback) { 10029 callback = $.isFunction(callback) 10030 ? callback 10031 : function(){} 10032 ; 10033 if($module.api('is loading')) { 10034 $module.api('abort'); 10035 } 10036 module.setup.api(searchTerm, callback); 10037 $module 10038 .api('query') 10039 ; 10040 }, 10041 object: function(searchTerm, source, searchFields) { 10042 searchTerm = module.remove.diacritics(String(searchTerm)); 10043 var 10044 results = [], 10045 exactResults = [], 10046 fuzzyResults = [], 10047 searchExp = searchTerm.replace(regExp.escape, '\\$&'), 10048 matchRegExp = new RegExp(regExp.beginsWith + searchExp, 'i'), 10049 10050 // avoid duplicates when pushing results 10051 addResult = function(array, result) { 10052 var 10053 notResult = ($.inArray(result, results) == -1), 10054 notFuzzyResult = ($.inArray(result, fuzzyResults) == -1), 10055 notExactResults = ($.inArray(result, exactResults) == -1) 10056 ; 10057 if(notResult && notFuzzyResult && notExactResults) { 10058 array.push(result); 10059 } 10060 } 10061 ; 10062 source = source || settings.source; 10063 searchFields = (searchFields !== undefined) 10064 ? searchFields 10065 : settings.searchFields 10066 ; 10067 10068 // search fields should be array to loop correctly 10069 if(!Array.isArray(searchFields)) { 10070 searchFields = [searchFields]; 10071 } 10072 10073 // exit conditions if no source 10074 if(source === undefined || source === false) { 10075 module.error(error.source); 10076 return []; 10077 } 10078 // iterate through search fields looking for matches 10079 $.each(searchFields, function(index, field) { 10080 $.each(source, function(label, content) { 10081 var 10082 fieldExists = (typeof content[field] == 'string') || (typeof content[field] == 'number') 10083 ; 10084 if(fieldExists) { 10085 var text; 10086 if (typeof content[field] === 'string'){ 10087 text = module.remove.diacritics(content[field]); 10088 } else { 10089 text = content[field].toString(); 10090 } 10091 if( text.search(matchRegExp) !== -1) { 10092 // content starts with value (first in results) 10093 addResult(results, content); 10094 } 10095 else if(settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, text) ) { 10096 // content fuzzy matches (last in results) 10097 addResult(exactResults, content); 10098 } 10099 else if(settings.fullTextSearch == true && module.fuzzySearch(searchTerm, text) ) { 10100 // content fuzzy matches (last in results) 10101 addResult(fuzzyResults, content); 10102 } 10103 } 10104 }); 10105 }); 10106 $.merge(exactResults, fuzzyResults); 10107 $.merge(results, exactResults); 10108 return results; 10109 } 10110 }, 10111 exactSearch: function (query, term) { 10112 query = query.toLowerCase(); 10113 term = term.toLowerCase(); 10114 return term.indexOf(query) > -1; 10115 }, 10116 fuzzySearch: function(query, term) { 10117 var 10118 termLength = term.length, 10119 queryLength = query.length 10120 ; 10121 if(typeof query !== 'string') { 10122 return false; 10123 } 10124 query = query.toLowerCase(); 10125 term = term.toLowerCase(); 10126 if(queryLength > termLength) { 10127 return false; 10128 } 10129 if(queryLength === termLength) { 10130 return (query === term); 10131 } 10132 search: for (var characterIndex = 0, nextCharacterIndex = 0; characterIndex < queryLength; characterIndex++) { 10133 var 10134 queryCharacter = query.charCodeAt(characterIndex) 10135 ; 10136 while(nextCharacterIndex < termLength) { 10137 if(term.charCodeAt(nextCharacterIndex++) === queryCharacter) { 10138 continue search; 10139 } 10140 } 10141 return false; 10142 } 10143 return true; 10144 }, 10145 10146 parse: { 10147 response: function(response, searchTerm) { 10148 if(Array.isArray(response)){ 10149 var o={}; 10150 o[fields.results]=response; 10151 response = o; 10152 } 10153 var 10154 searchHTML = module.generateResults(response) 10155 ; 10156 module.verbose('Parsing server response', response); 10157 if(response !== undefined) { 10158 if(searchTerm !== undefined && response[fields.results] !== undefined) { 10159 module.addResults(searchHTML); 10160 module.inject.id(response[fields.results]); 10161 module.write.cache(searchTerm, { 10162 html : searchHTML, 10163 results : response[fields.results] 10164 }); 10165 module.save.results(response[fields.results]); 10166 } 10167 } 10168 } 10169 }, 10170 10171 cancel: { 10172 query: function() { 10173 if( module.can.useAPI() ) { 10174 $module.api('abort'); 10175 } 10176 } 10177 }, 10178 10179 has: { 10180 minimumCharacters: function() { 10181 var 10182 searchTerm = module.get.value(), 10183 numCharacters = searchTerm.length 10184 ; 10185 return (numCharacters >= settings.minCharacters); 10186 }, 10187 results: function() { 10188 if($results.length === 0) { 10189 return false; 10190 } 10191 var 10192 html = $results.html() 10193 ; 10194 return html != ''; 10195 } 10196 }, 10197 10198 clear: { 10199 cache: function(value) { 10200 var 10201 cache = $module.data(metadata.cache) 10202 ; 10203 if(!value) { 10204 module.debug('Clearing cache', value); 10205 $module.removeData(metadata.cache); 10206 } 10207 else if(value && cache && cache[value]) { 10208 module.debug('Removing value from cache', value); 10209 delete cache[value]; 10210 $module.data(metadata.cache, cache); 10211 } 10212 } 10213 }, 10214 10215 read: { 10216 cache: function(name) { 10217 var 10218 cache = $module.data(metadata.cache) 10219 ; 10220 if(settings.cache) { 10221 module.verbose('Checking cache for generated html for query', name); 10222 return (typeof cache == 'object') && (cache[name] !== undefined) 10223 ? cache[name] 10224 : false 10225 ; 10226 } 10227 return false; 10228 } 10229 }, 10230 10231 create: { 10232 categoryResults: function(results) { 10233 var 10234 categoryResults = {} 10235 ; 10236 $.each(results, function(index, result) { 10237 if(!result.category) { 10238 return; 10239 } 10240 if(categoryResults[result.category] === undefined) { 10241 module.verbose('Creating new category of results', result.category); 10242 categoryResults[result.category] = { 10243 name : result.category, 10244 results : [result] 10245 }; 10246 } 10247 else { 10248 categoryResults[result.category].results.push(result); 10249 } 10250 }); 10251 return categoryResults; 10252 }, 10253 id: function(resultIndex, categoryIndex) { 10254 var 10255 resultID = (resultIndex + 1), // not zero indexed 10256 letterID, 10257 id 10258 ; 10259 if(categoryIndex !== undefined) { 10260 // start char code for "A" 10261 letterID = String.fromCharCode(97 + categoryIndex); 10262 id = letterID + resultID; 10263 module.verbose('Creating category result id', id); 10264 } 10265 else { 10266 id = resultID; 10267 module.verbose('Creating result id', id); 10268 } 10269 return id; 10270 }, 10271 results: function() { 10272 if($results.length === 0) { 10273 $results = $('<div />') 10274 .addClass(className.results) 10275 .appendTo($module) 10276 ; 10277 } 10278 } 10279 }, 10280 10281 inject: { 10282 result: function(result, resultIndex, categoryIndex) { 10283 module.verbose('Injecting result into results'); 10284 var 10285 $selectedResult = (categoryIndex !== undefined) 10286 ? $results 10287 .children().eq(categoryIndex) 10288 .children(selector.results) 10289 .first() 10290 .children(selector.result) 10291 .eq(resultIndex) 10292 : $results 10293 .children(selector.result).eq(resultIndex) 10294 ; 10295 module.verbose('Injecting results metadata', $selectedResult); 10296 $selectedResult 10297 .data(metadata.result, result) 10298 ; 10299 }, 10300 id: function(results) { 10301 module.debug('Injecting unique ids into results'); 10302 var 10303 // since results may be object, we must use counters 10304 categoryIndex = 0, 10305 resultIndex = 0 10306 ; 10307 if(settings.type === 'category') { 10308 // iterate through each category result 10309 $.each(results, function(index, category) { 10310 if(category.results.length > 0){ 10311 resultIndex = 0; 10312 $.each(category.results, function(index, result) { 10313 if(result.id === undefined) { 10314 result.id = module.create.id(resultIndex, categoryIndex); 10315 } 10316 module.inject.result(result, resultIndex, categoryIndex); 10317 resultIndex++; 10318 }); 10319 categoryIndex++; 10320 } 10321 }); 10322 } 10323 else { 10324 // top level 10325 $.each(results, function(index, result) { 10326 if(result.id === undefined) { 10327 result.id = module.create.id(resultIndex); 10328 } 10329 module.inject.result(result, resultIndex); 10330 resultIndex++; 10331 }); 10332 } 10333 return results; 10334 } 10335 }, 10336 10337 save: { 10338 results: function(results) { 10339 module.verbose('Saving current search results to metadata', results); 10340 $module.data(metadata.results, results); 10341 } 10342 }, 10343 10344 write: { 10345 cache: function(name, value) { 10346 var 10347 cache = ($module.data(metadata.cache) !== undefined) 10348 ? $module.data(metadata.cache) 10349 : {} 10350 ; 10351 if(settings.cache) { 10352 module.verbose('Writing generated html to cache', name, value); 10353 cache[name] = value; 10354 $module 10355 .data(metadata.cache, cache) 10356 ; 10357 } 10358 } 10359 }, 10360 10361 addResults: function(html) { 10362 if( $.isFunction(settings.onResultsAdd) ) { 10363 if( settings.onResultsAdd.call($results, html) === false ) { 10364 module.debug('onResultsAdd callback cancelled default action'); 10365 return false; 10366 } 10367 } 10368 if(html) { 10369 $results 10370 .html(html) 10371 ; 10372 module.refreshResults(); 10373 if(settings.selectFirstResult) { 10374 module.select.firstResult(); 10375 } 10376 module.showResults(); 10377 } 10378 else { 10379 module.hideResults(function() { 10380 $results.empty(); 10381 }); 10382 } 10383 }, 10384 10385 showResults: function(callback) { 10386 callback = $.isFunction(callback) 10387 ? callback 10388 : function(){} 10389 ; 10390 if(resultsDismissed) { 10391 return; 10392 } 10393 if(!module.is.visible() && module.has.results()) { 10394 if( module.can.transition() ) { 10395 module.debug('Showing results with css animations'); 10396 $results 10397 .transition({ 10398 animation : settings.transition + ' in', 10399 debug : settings.debug, 10400 verbose : settings.verbose, 10401 duration : settings.duration, 10402 onShow : function() { 10403 var $firstResult = $module.find(selector.result).eq(0); 10404 if($firstResult.length > 0) { 10405 module.ensureVisible($firstResult); 10406 } 10407 }, 10408 onComplete : function() { 10409 callback(); 10410 }, 10411 queue : true 10412 }) 10413 ; 10414 } 10415 else { 10416 module.debug('Showing results with javascript'); 10417 $results 10418 .stop() 10419 .fadeIn(settings.duration, settings.easing) 10420 ; 10421 } 10422 settings.onResultsOpen.call($results); 10423 } 10424 }, 10425 hideResults: function(callback) { 10426 callback = $.isFunction(callback) 10427 ? callback 10428 : function(){} 10429 ; 10430 if( module.is.visible() ) { 10431 if( module.can.transition() ) { 10432 module.debug('Hiding results with css animations'); 10433 $results 10434 .transition({ 10435 animation : settings.transition + ' out', 10436 debug : settings.debug, 10437 verbose : settings.verbose, 10438 duration : settings.duration, 10439 onComplete : function() { 10440 callback(); 10441 }, 10442 queue : true 10443 }) 10444 ; 10445 } 10446 else { 10447 module.debug('Hiding results with javascript'); 10448 $results 10449 .stop() 10450 .fadeOut(settings.duration, settings.easing) 10451 ; 10452 } 10453 settings.onResultsClose.call($results); 10454 } 10455 }, 10456 10457 generateResults: function(response) { 10458 module.debug('Generating html from response', response); 10459 var 10460 template = settings.templates[settings.type], 10461 isProperObject = ($.isPlainObject(response[fields.results]) && !$.isEmptyObject(response[fields.results])), 10462 isProperArray = (Array.isArray(response[fields.results]) && response[fields.results].length > 0), 10463 html = '' 10464 ; 10465 if(isProperObject || isProperArray ) { 10466 if(settings.maxResults > 0) { 10467 if(isProperObject) { 10468 if(settings.type == 'standard') { 10469 module.error(error.maxResults); 10470 } 10471 } 10472 else { 10473 response[fields.results] = response[fields.results].slice(0, settings.maxResults); 10474 } 10475 } 10476 if($.isFunction(template)) { 10477 html = template(response, fields, settings.preserveHTML); 10478 } 10479 else { 10480 module.error(error.noTemplate, false); 10481 } 10482 } 10483 else if(settings.showNoResults) { 10484 html = module.displayMessage(error.noResults, 'empty', error.noResultsHeader); 10485 } 10486 settings.onResults.call(element, response); 10487 return html; 10488 }, 10489 10490 displayMessage: function(text, type, header) { 10491 type = type || 'standard'; 10492 module.debug('Displaying message', text, type, header); 10493 module.addResults( settings.templates.message(text, type, header) ); 10494 return settings.templates.message(text, type, header); 10495 }, 10496 10497 setting: function(name, value) { 10498 if( $.isPlainObject(name) ) { 10499 $.extend(true, settings, name); 10500 } 10501 else if(value !== undefined) { 10502 settings[name] = value; 10503 } 10504 else { 10505 return settings[name]; 10506 } 10507 }, 10508 internal: function(name, value) { 10509 if( $.isPlainObject(name) ) { 10510 $.extend(true, module, name); 10511 } 10512 else if(value !== undefined) { 10513 module[name] = value; 10514 } 10515 else { 10516 return module[name]; 10517 } 10518 }, 10519 debug: function() { 10520 if(!settings.silent && settings.debug) { 10521 if(settings.performance) { 10522 module.performance.log(arguments); 10523 } 10524 else { 10525 module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); 10526 module.debug.apply(console, arguments); 10527 } 10528 } 10529 }, 10530 verbose: function() { 10531 if(!settings.silent && settings.verbose && settings.debug) { 10532 if(settings.performance) { 10533 module.performance.log(arguments); 10534 } 10535 else { 10536 module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); 10537 module.verbose.apply(console, arguments); 10538 } 10539 } 10540 }, 10541 error: function() { 10542 if(!settings.silent) { 10543 module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); 10544 module.error.apply(console, arguments); 10545 } 10546 }, 10547 performance: { 10548 log: function(message) { 10549 var 10550 currentTime, 10551 executionTime, 10552 previousTime 10553 ; 10554 if(settings.performance) { 10555 currentTime = new Date().getTime(); 10556 previousTime = time || currentTime; 10557 executionTime = currentTime - previousTime; 10558 time = currentTime; 10559 performance.push({ 10560 'Name' : message[0], 10561 'Arguments' : [].slice.call(message, 1) || '', 10562 'Element' : element, 10563 'Execution Time' : executionTime 10564 }); 10565 } 10566 clearTimeout(module.performance.timer); 10567 module.performance.timer = setTimeout(module.performance.display, 500); 10568 }, 10569 display: function() { 10570 var 10571 title = settings.name + ':', 10572 totalTime = 0 10573 ; 10574 time = false; 10575 clearTimeout(module.performance.timer); 10576 $.each(performance, function(index, data) { 10577 totalTime += data['Execution Time']; 10578 }); 10579 title += ' ' + totalTime + 'ms'; 10580 if(moduleSelector) { 10581 title += ' \'' + moduleSelector + '\''; 10582 } 10583 if($allModules.length > 1) { 10584 title += ' ' + '(' + $allModules.length + ')'; 10585 } 10586 if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { 10587 console.groupCollapsed(title); 10588 if(console.table) { 10589 console.table(performance); 10590 } 10591 else { 10592 $.each(performance, function(index, data) { 10593 console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); 10594 }); 10595 } 10596 console.groupEnd(); 10597 } 10598 performance = []; 10599 } 10600 }, 10601 invoke: function(query, passedArguments, context) { 10602 var 10603 object = instance, 10604 maxDepth, 10605 found, 10606 response 10607 ; 10608 passedArguments = passedArguments || queryArguments; 10609 context = element || context; 10610 if(typeof query == 'string' && object !== undefined) { 10611 query = query.split(/[\. ]/); 10612 maxDepth = query.length - 1; 10613 $.each(query, function(depth, value) { 10614 var camelCaseValue = (depth != maxDepth) 10615 ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) 10616 : query 10617 ; 10618 if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { 10619 object = object[camelCaseValue]; 10620 } 10621 else if( object[camelCaseValue] !== undefined ) { 10622 found = object[camelCaseValue]; 10623 return false; 10624 } 10625 else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { 10626 object = object[value]; 10627 } 10628 else if( object[value] !== undefined ) { 10629 found = object[value]; 10630 return false; 10631 } 10632 else { 10633 return false; 10634 } 10635 }); 10636 } 10637 if( $.isFunction( found ) ) { 10638 response = found.apply(context, passedArguments); 10639 } 10640 else if(found !== undefined) { 10641 response = found; 10642 } 10643 if(Array.isArray(returnedValue)) { 10644 returnedValue.push(response); 10645 } 10646 else if(returnedValue !== undefined) { 10647 returnedValue = [returnedValue, response]; 10648 } 10649 else if(response !== undefined) { 10650 returnedValue = response; 10651 } 10652 return found; 10653 } 10654 }; 10655 if(methodInvoked) { 10656 if(instance === undefined) { 10657 module.initialize(); 10658 } 10659 module.invoke(query); 10660 } 10661 else { 10662 if(instance !== undefined) { 10663 instance.invoke('destroy'); 10664 } 10665 module.initialize(); 10666 } 10667 10668 }) 10669 ; 10670 10671 return (returnedValue !== undefined) 10672 ? returnedValue 10673 : this 10674 ; 10675 }; 10676 10677 $.fn.search.settings = { 10678 10679 name : 'Search', 10680 namespace : 'search', 10681 10682 silent : false, 10683 debug : false, 10684 verbose : false, 10685 performance : true, 10686 10687 // template to use (specified in settings.templates) 10688 type : 'standard', 10689 10690 // minimum characters required to search 10691 minCharacters : 1, 10692 10693 // whether to select first result after searching automatically 10694 selectFirstResult : false, 10695 10696 // API config 10697 apiSettings : false, 10698 10699 // object to search 10700 source : false, 10701 10702 // Whether search should query current term on focus 10703 searchOnFocus : true, 10704 10705 // fields to search 10706 searchFields : [ 10707 'id', 10708 'title', 10709 'description' 10710 ], 10711 10712 // field to display in standard results template 10713 displayField : '', 10714 10715 // search anywhere in value (set to 'exact' to require exact matches 10716 fullTextSearch : 'exact', 10717 10718 // match results also if they contain diacritics of the same base character (for example searching for "a" will also match "á" or "â" or "à ", etc...) 10719 ignoreDiacritics : false, 10720 10721 // whether to add events to prompt automatically 10722 automatic : true, 10723 10724 // delay before hiding menu after blur 10725 hideDelay : 0, 10726 10727 // delay before searching 10728 searchDelay : 200, 10729 10730 // maximum results returned from search 10731 maxResults : 7, 10732 10733 // whether to store lookups in local cache 10734 cache : true, 10735 10736 // whether no results errors should be shown 10737 showNoResults : true, 10738 10739 // preserve possible html of resultset values 10740 preserveHTML : true, 10741 10742 // transition settings 10743 transition : 'scale', 10744 duration : 200, 10745 easing : 'easeOutExpo', 10746 10747 // callbacks 10748 onSelect : false, 10749 onResultsAdd : false, 10750 10751 onSearchQuery : function(query){}, 10752 onResults : function(response){}, 10753 10754 onResultsOpen : function(){}, 10755 onResultsClose : function(){}, 10756 10757 className: { 10758 animating : 'animating', 10759 active : 'active', 10760 empty : 'empty', 10761 focus : 'focus', 10762 hidden : 'hidden', 10763 loading : 'loading', 10764 results : 'results', 10765 pressed : 'down' 10766 }, 10767 10768 error : { 10769 source : 'Cannot search. No source used, and Semantic API module was not included', 10770 noResultsHeader : 'No Results', 10771 noResults : 'Your search returned no results', 10772 logging : 'Error in debug logging, exiting.', 10773 noEndpoint : 'No search endpoint was specified', 10774 noTemplate : 'A valid template name was not specified.', 10775 oldSearchSyntax : 'searchFullText setting has been renamed fullTextSearch for consistency, please adjust your settings.', 10776 serverError : 'There was an issue querying the server.', 10777 maxResults : 'Results must be an array to use maxResults setting', 10778 method : 'The method you called is not defined.', 10779 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.' 10780 }, 10781 10782 metadata: { 10783 cache : 'cache', 10784 results : 'results', 10785 result : 'result' 10786 }, 10787 10788 regExp: { 10789 escape : /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, 10790 beginsWith : '(?:\s|^)' 10791 }, 10792 10793 // maps api response attributes to internal representation 10794 fields: { 10795 categories : 'results', // array of categories (category view) 10796 categoryName : 'name', // name of category (category view) 10797 categoryResults : 'results', // array of results (category view) 10798 description : 'description', // result description 10799 image : 'image', // result image 10800 price : 'price', // result price 10801 results : 'results', // array of results (standard) 10802 title : 'title', // result title 10803 url : 'url', // result url 10804 action : 'action', // "view more" object name 10805 actionText : 'text', // "view more" text 10806 actionURL : 'url' // "view more" url 10807 }, 10808 10809 selector : { 10810 prompt : '.prompt', 10811 searchButton : '.search.button', 10812 results : '.results', 10813 message : '.results > .message', 10814 category : '.category', 10815 result : '.result', 10816 title : '.title, .name' 10817 }, 10818 10819 templates: { 10820 escape: function(string, preserveHTML) { 10821 if (preserveHTML){ 10822 return string; 10823 } 10824 var 10825 badChars = /[<>"'`]/g, 10826 shouldEscape = /[&<>"'`]/, 10827 escape = { 10828 "<": "<", 10829 ">": ">", 10830 '"': """, 10831 "'": "'", 10832 "`": "`" 10833 }, 10834 escapedChar = function(chr) { 10835 return escape[chr]; 10836 } 10837 ; 10838 if(shouldEscape.test(string)) { 10839 string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&"); 10840 return string.replace(badChars, escapedChar); 10841 } 10842 return string; 10843 }, 10844 message: function(message, type, header) { 10845 var 10846 html = '' 10847 ; 10848 if(message !== undefined && type !== undefined) { 10849 html += '' 10850 + '<div class="message ' + type + '">' 10851 ; 10852 if(header) { 10853 html += '' 10854 + '<div class="header">' + header + '</div>' 10855 ; 10856 } 10857 html += ' <div class="description">' + message + '</div>'; 10858 html += '</div>'; 10859 } 10860 return html; 10861 }, 10862 category: function(response, fields, preserveHTML) { 10863 var 10864 html = '', 10865 escape = $.fn.search.settings.templates.escape 10866 ; 10867 if(response[fields.categoryResults] !== undefined) { 10868 10869 // each category 10870 $.each(response[fields.categoryResults], function(index, category) { 10871 if(category[fields.results] !== undefined && category.results.length > 0) { 10872 10873 html += '<div class="category">'; 10874 10875 if(category[fields.categoryName] !== undefined) { 10876 html += '<div class="name">' + escape(category[fields.categoryName], preserveHTML) + '</div>'; 10877 } 10878 10879 // each item inside category 10880 html += '<div class="results">'; 10881 $.each(category.results, function(index, result) { 10882 if(result[fields.url]) { 10883 html += '<a class="result" href="' + result[fields.url].replace(/"/g,"") + '">'; 10884 } 10885 else { 10886 html += '<a class="result">'; 10887 } 10888 if(result[fields.image] !== undefined) { 10889 html += '' 10890 + '<div class="image">' 10891 + ' <img src="' + result[fields.image].replace(/"/g,"") + '">' 10892 + '</div>' 10893 ; 10894 } 10895 html += '<div class="content">'; 10896 if(result[fields.price] !== undefined) { 10897 html += '<div class="price">' + escape(result[fields.price], preserveHTML) + '</div>'; 10898 } 10899 if(result[fields.title] !== undefined) { 10900 html += '<div class="title">' + escape(result[fields.title], preserveHTML) + '</div>'; 10901 } 10902 if(result[fields.description] !== undefined) { 10903 html += '<div class="description">' + escape(result[fields.description], preserveHTML) + '</div>'; 10904 } 10905 html += '' 10906 + '</div>' 10907 ; 10908 html += '</a>'; 10909 }); 10910 html += '</div>'; 10911 html += '' 10912 + '</div>' 10913 ; 10914 } 10915 }); 10916 if(response[fields.action]) { 10917 if(fields.actionURL === false) { 10918 html += '' 10919 + '<div class="action">' 10920 + escape(response[fields.action][fields.actionText], preserveHTML) 10921 + '</div>'; 10922 } else { 10923 html += '' 10924 + '<a href="' + response[fields.action][fields.actionURL].replace(/"/g,"") + '" class="action">' 10925 + escape(response[fields.action][fields.actionText], preserveHTML) 10926 + '</a>'; 10927 } 10928 } 10929 return html; 10930 } 10931 return false; 10932 }, 10933 standard: function(response, fields, preserveHTML) { 10934 var 10935 html = '', 10936 escape = $.fn.search.settings.templates.escape 10937 ; 10938 if(response[fields.results] !== undefined) { 10939 10940 // each result 10941 $.each(response[fields.results], function(index, result) { 10942 if(result[fields.url]) { 10943 html += '<a class="result" href="' + result[fields.url].replace(/"/g,"") + '">'; 10944 } 10945 else { 10946 html += '<a class="result">'; 10947 } 10948 if(result[fields.image] !== undefined) { 10949 html += '' 10950 + '<div class="image">' 10951 + ' <img src="' + result[fields.image].replace(/"/g,"") + '">' 10952 + '</div>' 10953 ; 10954 } 10955 html += '<div class="content">'; 10956 if(result[fields.price] !== undefined) { 10957 html += '<div class="price">' + escape(result[fields.price], preserveHTML) + '</div>'; 10958 } 10959 if(result[fields.title] !== undefined) { 10960 html += '<div class="title">' + escape(result[fields.title], preserveHTML) + '</div>'; 10961 } 10962 if(result[fields.description] !== undefined) { 10963 html += '<div class="description">' + escape(result[fields.description], preserveHTML) + '</div>'; 10964 } 10965 html += '' 10966 + '</div>' 10967 ; 10968 html += '</a>'; 10969 }); 10970 if(response[fields.action]) { 10971 if(fields.actionURL === false) { 10972 html += '' 10973 + '<div class="action">' 10974 + escape(response[fields.action][fields.actionText], preserveHTML) 10975 + '</div>'; 10976 } else { 10977 html += '' 10978 + '<a href="' + response[fields.action][fields.actionURL].replace(/"/g,"") + '" class="action">' 10979 + escape(response[fields.action][fields.actionText], preserveHTML) 10980 + '</a>'; 10981 } 10982 } 10983 return html; 10984 } 10985 return false; 10986 } 10987 } 10988 }; 10989 10990 })( jQuery, window, document ); 10991 10992 /*! 10993 * # Fomantic-UI - Tab 10994 * http://github.com/fomantic/Fomantic-UI/ 10995 * 10996 * 10997 * Released under the MIT license 10998 * http://opensource.org/licenses/MIT 10999 * 11000 */ 11001 11002 ;(function ($, window, document, undefined) { 11003 11004 'use strict'; 11005 11006 $.isWindow = $.isWindow || function(obj) { 11007 return obj != null && obj === obj.window; 11008 }; 11009 $.isFunction = $.isFunction || function(obj) { 11010 return typeof obj === "function" && typeof obj.nodeType !== "number"; 11011 }; 11012 11013 window = (typeof window != 'undefined' && window.Math == Math) 11014 ? window 11015 : (typeof self != 'undefined' && self.Math == Math) 11016 ? self 11017 : Function('return this')() 11018 ; 11019 11020 $.fn.tab = function(parameters) { 11021 11022 var 11023 // use window context if none specified 11024 $allModules = $.isFunction(this) 11025 ? $(window) 11026 : $(this), 11027 11028 moduleSelector = $allModules.selector || '', 11029 time = new Date().getTime(), 11030 performance = [], 11031 11032 query = arguments[0], 11033 methodInvoked = (typeof query == 'string'), 11034 queryArguments = [].slice.call(arguments, 1), 11035 11036 initializedHistory = false, 11037 returnedValue 11038 ; 11039 11040 $allModules 11041 .each(function() { 11042 var 11043 11044 settings = ( $.isPlainObject(parameters) ) 11045 ? $.extend(true, {}, $.fn.tab.settings, parameters) 11046 : $.extend({}, $.fn.tab.settings), 11047 11048 className = settings.className, 11049 metadata = settings.metadata, 11050 selector = settings.selector, 11051 error = settings.error, 11052 regExp = settings.regExp, 11053 11054 eventNamespace = '.' + settings.namespace, 11055 moduleNamespace = 'module-' + settings.namespace, 11056 11057 $module = $(this), 11058 $context, 11059 $tabs, 11060 11061 cache = {}, 11062 firstLoad = true, 11063 recursionDepth = 0, 11064 element = this, 11065 instance = $module.data(moduleNamespace), 11066 11067 activeTabPath, 11068 parameterArray, 11069 module, 11070 11071 historyEvent 11072 11073 ; 11074 11075 module = { 11076 11077 initialize: function() { 11078 module.debug('Initializing tab menu item', $module); 11079 module.fix.callbacks(); 11080 module.determineTabs(); 11081 11082 module.debug('Determining tabs', settings.context, $tabs); 11083 // set up automatic routing 11084 if(settings.auto) { 11085 module.set.auto(); 11086 } 11087 module.bind.events(); 11088 11089 if(settings.history && !initializedHistory) { 11090 module.initializeHistory(); 11091 initializedHistory = true; 11092 } 11093 11094 if(settings.autoTabActivation && instance === undefined && module.determine.activeTab() == null) { 11095 module.debug('No active tab detected, setting first tab active', module.get.initialPath()); 11096 module.changeTab(settings.autoTabActivation === true ? module.get.initialPath() : settings.autoTabActivation); 11097 }; 11098 11099 module.instantiate(); 11100 }, 11101 11102 instantiate: function () { 11103 module.verbose('Storing instance of module', module); 11104 instance = module; 11105 $module 11106 .data(moduleNamespace, module) 11107 ; 11108 }, 11109 11110 destroy: function() { 11111 module.debug('Destroying tabs', $module); 11112 $module 11113 .removeData(moduleNamespace) 11114 .off(eventNamespace) 11115 ; 11116 }, 11117 11118 bind: { 11119 events: function() { 11120 // if using $.tab don't add events 11121 if( !$.isWindow( element ) ) { 11122 module.debug('Attaching tab activation events to element', $module); 11123 $module 11124 .on('click' + eventNamespace, module.event.click) 11125 ; 11126 } 11127 } 11128 }, 11129 11130 determineTabs: function() { 11131 var 11132 $reference 11133 ; 11134 11135 // determine tab context 11136 if(settings.context === 'parent') { 11137 if($module.closest(selector.ui).length > 0) { 11138 $reference = $module.closest(selector.ui); 11139 module.verbose('Using closest UI element as parent', $reference); 11140 } 11141 else { 11142 $reference = $module; 11143 } 11144 $context = $reference.parent(); 11145 module.verbose('Determined parent element for creating context', $context); 11146 } 11147 else if(settings.context) { 11148 $context = $(settings.context); 11149 module.verbose('Using selector for tab context', settings.context, $context); 11150 } 11151 else { 11152 $context = $('body'); 11153 } 11154 // find tabs 11155 if(settings.childrenOnly) { 11156 $tabs = $context.children(selector.tabs); 11157 module.debug('Searching tab context children for tabs', $context, $tabs); 11158 } 11159 else { 11160 $tabs = $context.find(selector.tabs); 11161 module.debug('Searching tab context for tabs', $context, $tabs); 11162 } 11163 }, 11164 11165 fix: { 11166 callbacks: function() { 11167 if( $.isPlainObject(parameters) && (parameters.onTabLoad || parameters.onTabInit) ) { 11168 if(parameters.onTabLoad) { 11169 parameters.onLoad = parameters.onTabLoad; 11170 delete parameters.onTabLoad; 11171 module.error(error.legacyLoad, parameters.onLoad); 11172 } 11173 if(parameters.onTabInit) { 11174 parameters.onFirstLoad = parameters.onTabInit; 11175 delete parameters.onTabInit; 11176 module.error(error.legacyInit, parameters.onFirstLoad); 11177 } 11178 settings = $.extend(true, {}, $.fn.tab.settings, parameters); 11179 } 11180 } 11181 }, 11182 11183 initializeHistory: function() { 11184 module.debug('Initializing page state'); 11185 if( $.address === undefined ) { 11186 module.error(error.state); 11187 return false; 11188 } 11189 else { 11190 if(settings.historyType == 'state') { 11191 module.debug('Using HTML5 to manage state'); 11192 if(settings.path !== false) { 11193 $.address 11194 .history(true) 11195 .state(settings.path) 11196 ; 11197 } 11198 else { 11199 module.error(error.path); 11200 return false; 11201 } 11202 } 11203 $.address 11204 .bind('change', module.event.history.change) 11205 ; 11206 } 11207 }, 11208 11209 event: { 11210 click: function(event) { 11211 var 11212 tabPath = $(this).data(metadata.tab) 11213 ; 11214 if(tabPath !== undefined) { 11215 if(settings.history) { 11216 module.verbose('Updating page state', event); 11217 $.address.value(tabPath); 11218 } 11219 else { 11220 module.verbose('Changing tab', event); 11221 module.changeTab(tabPath); 11222 } 11223 event.preventDefault(); 11224 } 11225 else { 11226 module.debug('No tab specified'); 11227 } 11228 }, 11229 history: { 11230 change: function(event) { 11231 var 11232 tabPath = event.pathNames.join('/') || module.get.initialPath(), 11233 pageTitle = settings.templates.determineTitle(tabPath) || false 11234 ; 11235 module.performance.display(); 11236 module.debug('History change event', tabPath, event); 11237 historyEvent = event; 11238 if(tabPath !== undefined) { 11239 module.changeTab(tabPath); 11240 } 11241 if(pageTitle) { 11242 $.address.title(pageTitle); 11243 } 11244 } 11245 } 11246 }, 11247 11248 refresh: function() { 11249 if(activeTabPath) { 11250 module.debug('Refreshing tab', activeTabPath); 11251 module.changeTab(activeTabPath); 11252 } 11253 }, 11254 11255 cache: { 11256 11257 read: function(cacheKey) { 11258 return (cacheKey !== undefined) 11259 ? cache[cacheKey] 11260 : false 11261 ; 11262 }, 11263 add: function(cacheKey, content) { 11264 cacheKey = cacheKey || activeTabPath; 11265 module.debug('Adding cached content for', cacheKey); 11266 cache[cacheKey] = content; 11267 }, 11268 remove: function(cacheKey) { 11269 cacheKey = cacheKey || activeTabPath; 11270 module.debug('Removing cached content for', cacheKey); 11271 delete cache[cacheKey]; 11272 } 11273 }, 11274 11275 escape: { 11276 string: function(text) { 11277 text = String(text); 11278 return text.replace(regExp.escape, '\\$&'); 11279 } 11280 }, 11281 11282 set: { 11283 auto: function() { 11284 var 11285 url = (typeof settings.path == 'string') 11286 ? settings.path.replace(/\/$/, '') + '/{$tab}' 11287 : '/{$tab}' 11288 ; 11289 module.verbose('Setting up automatic tab retrieval from server', url); 11290 if($.isPlainObject(settings.apiSettings)) { 11291 settings.apiSettings.url = url; 11292 } 11293 else { 11294 settings.apiSettings = { 11295 url: url 11296 }; 11297 } 11298 }, 11299 loading: function(tabPath) { 11300 var 11301 $tab = module.get.tabElement(tabPath), 11302 isLoading = $tab.hasClass(className.loading) 11303 ; 11304 if(!isLoading) { 11305 module.verbose('Setting loading state for', $tab); 11306 $tab 11307 .addClass(className.loading) 11308 .siblings($tabs) 11309 .removeClass(className.active + ' ' + className.loading) 11310 ; 11311 if($tab.length > 0) { 11312 settings.onRequest.call($tab[0], tabPath); 11313 } 11314 } 11315 }, 11316 state: function(state) { 11317 $.address.value(state); 11318 } 11319 }, 11320 11321 changeTab: function(tabPath) { 11322 var 11323 pushStateAvailable = (window.history && window.history.pushState), 11324 shouldIgnoreLoad = (pushStateAvailable && settings.ignoreFirstLoad && firstLoad), 11325 remoteContent = (settings.auto || $.isPlainObject(settings.apiSettings) ), 11326 // only add default path if not remote content 11327 pathArray = (remoteContent && !shouldIgnoreLoad) 11328 ? module.utilities.pathToArray(tabPath) 11329 : module.get.defaultPathArray(tabPath) 11330 ; 11331 tabPath = module.utilities.arrayToPath(pathArray); 11332 $.each(pathArray, function(index, tab) { 11333 var 11334 currentPathArray = pathArray.slice(0, index + 1), 11335 currentPath = module.utilities.arrayToPath(currentPathArray), 11336 11337 isTab = module.is.tab(currentPath), 11338 isLastIndex = (index + 1 == pathArray.length), 11339 11340 $tab = module.get.tabElement(currentPath), 11341 $anchor, 11342 nextPathArray, 11343 nextPath, 11344 isLastTab 11345 ; 11346 module.verbose('Looking for tab', tab); 11347 if(isTab) { 11348 module.verbose('Tab was found', tab); 11349 // scope up 11350 activeTabPath = currentPath; 11351 parameterArray = module.utilities.filterArray(pathArray, currentPathArray); 11352 11353 if(isLastIndex) { 11354 isLastTab = true; 11355 } 11356 else { 11357 nextPathArray = pathArray.slice(0, index + 2); 11358 nextPath = module.utilities.arrayToPath(nextPathArray); 11359 isLastTab = ( !module.is.tab(nextPath) ); 11360 if(isLastTab) { 11361 module.verbose('Tab parameters found', nextPathArray); 11362 } 11363 } 11364 if(isLastTab && remoteContent) { 11365 if(!shouldIgnoreLoad) { 11366 module.activate.navigation(currentPath); 11367 module.fetch.content(currentPath, tabPath); 11368 } 11369 else { 11370 module.debug('Ignoring remote content on first tab load', currentPath); 11371 firstLoad = false; 11372 module.cache.add(tabPath, $tab.html()); 11373 module.activate.all(currentPath); 11374 settings.onFirstLoad.call($tab[0], currentPath, parameterArray, historyEvent); 11375 settings.onLoad.call($tab[0], currentPath, parameterArray, historyEvent); 11376 } 11377 return false; 11378 } 11379 else { 11380 module.debug('Opened local tab', currentPath); 11381 module.activate.all(currentPath); 11382 if( !module.cache.read(currentPath) ) { 11383 module.cache.add(currentPath, true); 11384 module.debug('First time tab loaded calling tab init'); 11385 settings.onFirstLoad.call($tab[0], currentPath, parameterArray, historyEvent); 11386 } 11387 settings.onLoad.call($tab[0], currentPath, parameterArray, historyEvent); 11388 } 11389 11390 } 11391 else if(tabPath.search('/') == -1 && tabPath !== '') { 11392 // look for in page anchor 11393 tabPath = module.escape.string(tabPath); 11394 $anchor = $('#' + tabPath + ', a[name="' + tabPath + '"]'); 11395 currentPath = $anchor.closest('[data-tab]').data(metadata.tab); 11396 $tab = module.get.tabElement(currentPath); 11397 // if anchor exists use parent tab 11398 if($anchor && $anchor.length > 0 && currentPath) { 11399 module.debug('Anchor link used, opening parent tab', $tab, $anchor); 11400 if( !$tab.hasClass(className.active) ) { 11401 setTimeout(function() { 11402 module.scrollTo($anchor); 11403 }, 0); 11404 } 11405 module.activate.all(currentPath); 11406 if( !module.cache.read(currentPath) ) { 11407 module.cache.add(currentPath, true); 11408 module.debug('First time tab loaded calling tab init'); 11409 settings.onFirstLoad.call($tab[0], currentPath, parameterArray, historyEvent); 11410 } 11411 settings.onLoad.call($tab[0], currentPath, parameterArray, historyEvent); 11412 return false; 11413 } 11414 } 11415 else { 11416 module.error(error.missingTab, $module, $context, currentPath); 11417 return false; 11418 } 11419 }); 11420 }, 11421 11422 scrollTo: function($element) { 11423 var 11424 scrollOffset = ($element && $element.length > 0) 11425 ? $element.offset().top 11426 : false 11427 ; 11428 if(scrollOffset !== false) { 11429 module.debug('Forcing scroll to an in-page link in a hidden tab', scrollOffset, $element); 11430 $(document).scrollTop(scrollOffset); 11431 } 11432 }, 11433 11434 update: { 11435 content: function(tabPath, html, evaluateScripts) { 11436 var 11437 $tab = module.get.tabElement(tabPath), 11438 tab = $tab[0] 11439 ; 11440 evaluateScripts = (evaluateScripts !== undefined) 11441 ? evaluateScripts 11442 : settings.evaluateScripts 11443 ; 11444 if(typeof settings.cacheType == 'string' && settings.cacheType.toLowerCase() == 'dom' && typeof html !== 'string') { 11445 $tab 11446 .empty() 11447 .append($(html).clone(true)) 11448 ; 11449 } 11450 else { 11451 if(evaluateScripts) { 11452 module.debug('Updating HTML and evaluating inline scripts', tabPath, html); 11453 $tab.html(html); 11454 } 11455 else { 11456 module.debug('Updating HTML', tabPath, html); 11457 tab.innerHTML = html; 11458 } 11459 } 11460 } 11461 }, 11462 11463 fetch: { 11464 11465 content: function(tabPath, fullTabPath) { 11466 var 11467 $tab = module.get.tabElement(tabPath), 11468 apiSettings = { 11469 dataType : 'html', 11470 encodeParameters : false, 11471 on : 'now', 11472 cache : settings.alwaysRefresh, 11473 headers : { 11474 'X-Remote': true 11475 }, 11476 onSuccess : function(response) { 11477 if(settings.cacheType == 'response') { 11478 module.cache.add(fullTabPath, response); 11479 } 11480 module.update.content(tabPath, response); 11481 if(tabPath == activeTabPath) { 11482 module.debug('Content loaded', tabPath); 11483 module.activate.tab(tabPath); 11484 } 11485 else { 11486 module.debug('Content loaded in background', tabPath); 11487 } 11488 settings.onFirstLoad.call($tab[0], tabPath, parameterArray, historyEvent); 11489 settings.onLoad.call($tab[0], tabPath, parameterArray, historyEvent); 11490 11491 if(settings.loadOnce) { 11492 module.cache.add(fullTabPath, true); 11493 } 11494 else if(typeof settings.cacheType == 'string' && settings.cacheType.toLowerCase() == 'dom' && $tab.children().length > 0) { 11495 setTimeout(function() { 11496 var 11497 $clone = $tab.children().clone(true) 11498 ; 11499 $clone = $clone.not('script'); 11500 module.cache.add(fullTabPath, $clone); 11501 }, 0); 11502 } 11503 else { 11504 module.cache.add(fullTabPath, $tab.html()); 11505 } 11506 }, 11507 urlData: { 11508 tab: fullTabPath 11509 } 11510 }, 11511 request = $tab.api('get request') || false, 11512 existingRequest = ( request && request.state() === 'pending' ), 11513 requestSettings, 11514 cachedContent 11515 ; 11516 11517 fullTabPath = fullTabPath || tabPath; 11518 cachedContent = module.cache.read(fullTabPath); 11519 11520 11521 if(settings.cache && cachedContent) { 11522 module.activate.tab(tabPath); 11523 module.debug('Adding cached content', fullTabPath); 11524 if(!settings.loadOnce) { 11525 if(settings.evaluateScripts == 'once') { 11526 module.update.content(tabPath, cachedContent, false); 11527 } 11528 else { 11529 module.update.content(tabPath, cachedContent); 11530 } 11531 } 11532 settings.onLoad.call($tab[0], tabPath, parameterArray, historyEvent); 11533 } 11534 else if(existingRequest) { 11535 module.set.loading(tabPath); 11536 module.debug('Content is already loading', fullTabPath); 11537 } 11538 else if($.api !== undefined) { 11539 requestSettings = $.extend(true, {}, settings.apiSettings, apiSettings); 11540 module.debug('Retrieving remote content', fullTabPath, requestSettings); 11541 module.set.loading(tabPath); 11542 $tab.api(requestSettings); 11543 } 11544 else { 11545 module.error(error.api); 11546 } 11547 } 11548 }, 11549 11550 activate: { 11551 all: function(tabPath) { 11552 module.activate.tab(tabPath); 11553 module.activate.navigation(tabPath); 11554 }, 11555 tab: function(tabPath) { 11556 var 11557 $tab = module.get.tabElement(tabPath), 11558 $deactiveTabs = (settings.deactivate == 'siblings') 11559 ? $tab.siblings($tabs) 11560 : $tabs.not($tab), 11561 isActive = $tab.hasClass(className.active) 11562 ; 11563 module.verbose('Showing tab content for', $tab); 11564 if(!isActive) { 11565 $tab 11566 .addClass(className.active) 11567 ; 11568 $deactiveTabs 11569 .removeClass(className.active + ' ' + className.loading) 11570 ; 11571 if($tab.length > 0) { 11572 settings.onVisible.call($tab[0], tabPath); 11573 } 11574 } 11575 }, 11576 navigation: function(tabPath) { 11577 var 11578 $navigation = module.get.navElement(tabPath), 11579 $deactiveNavigation = (settings.deactivate == 'siblings') 11580 ? $navigation.siblings($allModules) 11581 : $allModules.not($navigation), 11582 isActive = $navigation.hasClass(className.active) 11583 ; 11584 module.verbose('Activating tab navigation for', $navigation, tabPath); 11585 if(!isActive) { 11586 $navigation 11587 .addClass(className.active) 11588 ; 11589 $deactiveNavigation 11590 .removeClass(className.active + ' ' + className.loading) 11591 ; 11592 } 11593 } 11594 }, 11595 11596 deactivate: { 11597 all: function() { 11598 module.deactivate.navigation(); 11599 module.deactivate.tabs(); 11600 }, 11601 navigation: function() { 11602 $allModules 11603 .removeClass(className.active) 11604 ; 11605 }, 11606 tabs: function() { 11607 $tabs 11608 .removeClass(className.active + ' ' + className.loading) 11609 ; 11610 } 11611 }, 11612 11613 is: { 11614 tab: function(tabName) { 11615 return (tabName !== undefined) 11616 ? ( module.get.tabElement(tabName).length > 0 ) 11617 : false 11618 ; 11619 } 11620 }, 11621 11622 get: { 11623 initialPath: function() { 11624 return $allModules.eq(0).data(metadata.tab) || $tabs.eq(0).data(metadata.tab); 11625 }, 11626 path: function() { 11627 return $.address.value(); 11628 }, 11629 // adds default tabs to tab path 11630 defaultPathArray: function(tabPath) { 11631 return module.utilities.pathToArray( module.get.defaultPath(tabPath) ); 11632 }, 11633 defaultPath: function(tabPath) { 11634 var 11635 $defaultNav = $allModules.filter('[data-' + metadata.tab + '^="' + module.escape.string(tabPath) + '/"]').eq(0), 11636 defaultTab = $defaultNav.data(metadata.tab) || false 11637 ; 11638 if( defaultTab ) { 11639 module.debug('Found default tab', defaultTab); 11640 if(recursionDepth < settings.maxDepth) { 11641 recursionDepth++; 11642 return module.get.defaultPath(defaultTab); 11643 } 11644 module.error(error.recursion); 11645 } 11646 else { 11647 module.debug('No default tabs found for', tabPath, $tabs); 11648 } 11649 recursionDepth = 0; 11650 return tabPath; 11651 }, 11652 navElement: function(tabPath) { 11653 tabPath = tabPath || activeTabPath; 11654 return $allModules.filter('[data-' + metadata.tab + '="' + module.escape.string(tabPath) + '"]'); 11655 }, 11656 tabElement: function(tabPath) { 11657 var 11658 $fullPathTab, 11659 $simplePathTab, 11660 tabPathArray, 11661 lastTab 11662 ; 11663 tabPath = tabPath || activeTabPath; 11664 tabPathArray = module.utilities.pathToArray(tabPath); 11665 lastTab = module.utilities.last(tabPathArray); 11666 $fullPathTab = $tabs.filter('[data-' + metadata.tab + '="' + module.escape.string(tabPath) + '"]'); 11667 $simplePathTab = $tabs.filter('[data-' + metadata.tab + '="' + module.escape.string(lastTab) + '"]'); 11668 return ($fullPathTab.length > 0) 11669 ? $fullPathTab 11670 : $simplePathTab 11671 ; 11672 }, 11673 tab: function() { 11674 return activeTabPath; 11675 } 11676 }, 11677 11678 determine: { 11679 activeTab: function() { 11680 var activeTab = null; 11681 11682 $tabs.each(function(_index, tab) { 11683 var $tab = $(tab); 11684 11685 if( $tab.hasClass(className.active) ) { 11686 var 11687 tabPath = $(this).data(metadata.tab), 11688 $anchor = $allModules.filter('[data-' + metadata.tab + '="' + module.escape.string(tabPath) + '"]') 11689 ; 11690 11691 if( $anchor.hasClass(className.active) ) { 11692 activeTab = tabPath; 11693 } 11694 } 11695 }); 11696 11697 return activeTab; 11698 } 11699 }, 11700 11701 utilities: { 11702 filterArray: function(keepArray, removeArray) { 11703 return $.grep(keepArray, function(keepValue) { 11704 return ( $.inArray(keepValue, removeArray) == -1); 11705 }); 11706 }, 11707 last: function(array) { 11708 return Array.isArray(array) 11709 ? array[ array.length - 1] 11710 : false 11711 ; 11712 }, 11713 pathToArray: function(pathName) { 11714 if(pathName === undefined) { 11715 pathName = activeTabPath; 11716 } 11717 return typeof pathName == 'string' 11718 ? pathName.split('/') 11719 : [pathName] 11720 ; 11721 }, 11722 arrayToPath: function(pathArray) { 11723 return Array.isArray(pathArray) 11724 ? pathArray.join('/') 11725 : false 11726 ; 11727 } 11728 }, 11729 11730 setting: function(name, value) { 11731 module.debug('Changing setting', name, value); 11732 if( $.isPlainObject(name) ) { 11733 $.extend(true, settings, name); 11734 } 11735 else if(value !== undefined) { 11736 if($.isPlainObject(settings[name])) { 11737 $.extend(true, settings[name], value); 11738 } 11739 else { 11740 settings[name] = value; 11741 } 11742 } 11743 else { 11744 return settings[name]; 11745 } 11746 }, 11747 internal: function(name, value) { 11748 if( $.isPlainObject(name) ) { 11749 $.extend(true, module, name); 11750 } 11751 else if(value !== undefined) { 11752 module[name] = value; 11753 } 11754 else { 11755 return module[name]; 11756 } 11757 }, 11758 debug: function() { 11759 if(!settings.silent && settings.debug) { 11760 if(settings.performance) { 11761 module.performance.log(arguments); 11762 } 11763 else { 11764 module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); 11765 module.debug.apply(console, arguments); 11766 } 11767 } 11768 }, 11769 verbose: function() { 11770 if(!settings.silent && settings.verbose && settings.debug) { 11771 if(settings.performance) { 11772 module.performance.log(arguments); 11773 } 11774 else { 11775 module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); 11776 module.verbose.apply(console, arguments); 11777 } 11778 } 11779 }, 11780 error: function() { 11781 if(!settings.silent) { 11782 module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); 11783 module.error.apply(console, arguments); 11784 } 11785 }, 11786 performance: { 11787 log: function(message) { 11788 var 11789 currentTime, 11790 executionTime, 11791 previousTime 11792 ; 11793 if(settings.performance) { 11794 currentTime = new Date().getTime(); 11795 previousTime = time || currentTime; 11796 executionTime = currentTime - previousTime; 11797 time = currentTime; 11798 performance.push({ 11799 'Name' : message[0], 11800 'Arguments' : [].slice.call(message, 1) || '', 11801 'Element' : element, 11802 'Execution Time' : executionTime 11803 }); 11804 } 11805 clearTimeout(module.performance.timer); 11806 module.performance.timer = setTimeout(module.performance.display, 500); 11807 }, 11808 display: function() { 11809 var 11810 title = settings.name + ':', 11811 totalTime = 0 11812 ; 11813 time = false; 11814 clearTimeout(module.performance.timer); 11815 $.each(performance, function(index, data) { 11816 totalTime += data['Execution Time']; 11817 }); 11818 title += ' ' + totalTime + 'ms'; 11819 if(moduleSelector) { 11820 title += ' \'' + moduleSelector + '\''; 11821 } 11822 if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { 11823 console.groupCollapsed(title); 11824 if(console.table) { 11825 console.table(performance); 11826 } 11827 else { 11828 $.each(performance, function(index, data) { 11829 console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); 11830 }); 11831 } 11832 console.groupEnd(); 11833 } 11834 performance = []; 11835 } 11836 }, 11837 invoke: function(query, passedArguments, context) { 11838 var 11839 object = instance, 11840 maxDepth, 11841 found, 11842 response 11843 ; 11844 passedArguments = passedArguments || queryArguments; 11845 context = element || context; 11846 if(typeof query == 'string' && object !== undefined) { 11847 query = query.split(/[\. ]/); 11848 maxDepth = query.length - 1; 11849 $.each(query, function(depth, value) { 11850 var camelCaseValue = (depth != maxDepth) 11851 ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) 11852 : query 11853 ; 11854 if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { 11855 object = object[camelCaseValue]; 11856 } 11857 else if( object[camelCaseValue] !== undefined ) { 11858 found = object[camelCaseValue]; 11859 return false; 11860 } 11861 else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { 11862 object = object[value]; 11863 } 11864 else if( object[value] !== undefined ) { 11865 found = object[value]; 11866 return false; 11867 } 11868 else { 11869 module.error(error.method, query); 11870 return false; 11871 } 11872 }); 11873 } 11874 if ( $.isFunction( found ) ) { 11875 response = found.apply(context, passedArguments); 11876 } 11877 else if(found !== undefined) { 11878 response = found; 11879 } 11880 if(Array.isArray(returnedValue)) { 11881 returnedValue.push(response); 11882 } 11883 else if(returnedValue !== undefined) { 11884 returnedValue = [returnedValue, response]; 11885 } 11886 else if(response !== undefined) { 11887 returnedValue = response; 11888 } 11889 return found; 11890 } 11891 }; 11892 if(methodInvoked) { 11893 if(instance === undefined) { 11894 module.initialize(); 11895 } 11896 module.invoke(query); 11897 } 11898 else { 11899 if(instance !== undefined) { 11900 instance.invoke('destroy'); 11901 } 11902 module.initialize(); 11903 } 11904 }) 11905 ; 11906 return (returnedValue !== undefined) 11907 ? returnedValue 11908 : this 11909 ; 11910 11911 }; 11912 11913 // shortcut for tabbed content with no defined navigation 11914 $.tab = function() { 11915 $(window).tab.apply(this, arguments); 11916 }; 11917 11918 $.fn.tab.settings = { 11919 11920 name : 'Tab', 11921 namespace : 'tab', 11922 11923 silent : false, 11924 debug : false, 11925 verbose : false, 11926 performance : true, 11927 11928 auto : false, // uses pjax style endpoints fetching content from same url with remote-content headers 11929 history : false, // use browser history 11930 historyType : 'hash', // #/ or html5 state 11931 path : false, // base path of url 11932 11933 context : false, // specify a context that tabs must appear inside 11934 childrenOnly : false, // use only tabs that are children of context 11935 maxDepth : 25, // max depth a tab can be nested 11936 11937 deactivate : 'siblings', // whether tabs should deactivate sibling menu elements or all elements initialized together 11938 11939 alwaysRefresh : false, // load tab content new every tab click 11940 cache : true, // cache the content requests to pull locally 11941 loadOnce : false, // Whether tab data should only be loaded once when using remote content 11942 cacheType : 'response', // Whether to cache exact response, or to html cache contents after scripts execute 11943 ignoreFirstLoad : false, // don't load remote content on first load 11944 11945 apiSettings : false, // settings for api call 11946 evaluateScripts : 'once', // whether inline scripts should be parsed (true/false/once). Once will not re-evaluate on cached content 11947 autoTabActivation: true, // whether a non existing active tab will auto activate the first available tab 11948 11949 onFirstLoad : function(tabPath, parameterArray, historyEvent) {}, // called first time loaded 11950 onLoad : function(tabPath, parameterArray, historyEvent) {}, // called on every load 11951 onVisible : function(tabPath, parameterArray, historyEvent) {}, // called every time tab visible 11952 onRequest : function(tabPath, parameterArray, historyEvent) {}, // called ever time a tab beings loading remote content 11953 11954 templates : { 11955 determineTitle: function(tabArray) {} // returns page title for path 11956 }, 11957 11958 error: { 11959 api : 'You attempted to load content without API module', 11960 method : 'The method you called is not defined', 11961 missingTab : 'Activated tab cannot be found. Tabs are case-sensitive.', 11962 noContent : 'The tab you specified is missing a content url.', 11963 path : 'History enabled, but no path was specified', 11964 recursion : 'Max recursive depth reached', 11965 legacyInit : 'onTabInit has been renamed to onFirstLoad in 2.0, please adjust your code.', 11966 legacyLoad : 'onTabLoad has been renamed to onLoad in 2.0. Please adjust your code', 11967 state : 'History requires Asual\'s Address library <https://github.com/asual/jquery-address>' 11968 }, 11969 11970 regExp : { 11971 escape : /[-[\]{}()*+?.,\\^$|#\s:=@]/g 11972 }, 11973 11974 metadata : { 11975 tab : 'tab', 11976 loaded : 'loaded', 11977 promise: 'promise' 11978 }, 11979 11980 className : { 11981 loading : 'loading', 11982 active : 'active' 11983 }, 11984 11985 selector : { 11986 tabs : '.ui.tab', 11987 ui : '.ui' 11988 } 11989 11990 }; 11991 11992 })( jQuery, window, document );