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, '&quot;'));
  5630                });
  5631                return values;
  5632              }
  5633              return value.replace(regExp.quote, '&quot;');
  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                        "<": "&lt;",
  5645                        ">": "&gt;",
  5646                        '"': "&quot;",
  5647                        "'": "&#x27;",
  5648                        "`": "&#x60;"
  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};)/, "&amp;");
  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            "<": "&lt;",
  6072            ">": "&gt;",
  6073            '"': "&quot;",
  6074            "'": "&#x27;",
  6075            "`": "&#x60;"
  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};)/, "&amp;");
  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            "<": "&lt;",
 10829            ">": "&gt;",
 10830            '"': "&quot;",
 10831            "'": "&#x27;",
 10832            "`": "&#x60;"
 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};)/, "&amp;");
 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 );