text: /**
   * Yii form widget.
   *
   * This is the JavaScript widget used by the yii\widgets\ActiveForm widget.
   *
   * @link http://www.yiiframework.com/
   * @copyright Copyright (c) 2008 Yii Software LLC
   * @license http://www.yiiframework.com/license/
   * @author Qiang Xue <qiang.xue@gmail.com>
   * @since 2.0
   */
  (function ($) {
  
      $.fn.yiiActiveForm = function (method) {
          if (methods[method]) {
              return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
          } else if (typeof method === 'object' || !method) {
              return methods.init.apply(this, arguments);
          } else {
              $.error('Method ' + method + ' does not exist on jQuery.yiiActiveForm');
              return false;
          }
      };
  
      var events = {
          /**
           * beforeValidate event is triggered before validating the whole form.
           * The signature of the event handler should be:
           *     function (event, messages, deferreds)
           * where
           *  - event: an Event object.
           *  - messages: an associative array with keys being attribute IDs and values being error message arrays
           *    for the corresponding attributes.
           *  - deferreds: an array of Deferred objects. You can use deferreds.add(callback) to add a new deferred validation.
           *
           * If the handler returns a boolean false, it will stop further form validation after this event. And as
           * a result, afterValidate event will not be triggered.
           */
          beforeValidate: 'beforeValidate',
          /**
           * afterValidate event is triggered after validating the whole form.
           * The signature of the event handler should be:
           *     function (event, messages, errorAttributes)
           * where
           *  - event: an Event object.
           *  - messages: an associative array with keys being attribute IDs and values being error message arrays
           *    for the corresponding attributes.
           *  - errorAttributes: an array of attributes that have validation errors. Please refer to attributeDefaults for the structure of this parameter.
           */
          afterValidate: 'afterValidate',
          /**
           * beforeValidateAttribute event is triggered before validating an attribute.
           * The signature of the event handler should be:
           *     function (event, attribute, messages, deferreds)
           * where
           *  - event: an Event object.
           *  - attribute: the attribute to be validated. Please refer to attributeDefaults for the structure of this parameter.
           *  - messages: an array to which you can add validation error messages for the specified attribute.
           *  - deferreds: an array of Deferred objects. You can use deferreds.add(callback) to add a new deferred validation.
           *
           * If the handler returns a boolean false, it will stop further validation of the specified attribute.
           * And as a result, afterValidateAttribute event will not be triggered.
           */
          beforeValidateAttribute: 'beforeValidateAttribute',
          /**
           * afterValidateAttribute event is triggered after validating the whole form and each attribute.
           * The signature of the event handler should be:
           *     function (event, attribute, messages)
           * where
           *  - event: an Event object.
           *  - attribute: the attribute being validated. Please refer to attributeDefaults for the structure of this parameter.
           *  - messages: an array to which you can add additional validation error messages for the specified attribute.
           */
          afterValidateAttribute: 'afterValidateAttribute',
          /**
           * beforeSubmit event is triggered before submitting the form after all validations have passed.
           * The signature of the event handler should be:
           *     function (event)
           * where event is an Event object.
           *
           * If the handler returns a boolean false, it will stop form submission.
           */
          beforeSubmit: 'beforeSubmit',
          /**
           * ajaxBeforeSend event is triggered before sending an AJAX request for AJAX-based validation.
           * The signature of the event handler should be:
           *     function (event, jqXHR, settings)
           * where
           *  - event: an Event object.
           *  - jqXHR: a jqXHR object
           *  - settings: the settings for the AJAX request
           */
          ajaxBeforeSend: 'ajaxBeforeSend',
          /**
           * ajaxComplete event is triggered after completing an AJAX request for AJAX-based validation.
           * The signature of the event handler should be:
           *     function (event, jqXHR, textStatus)
           * where
           *  - event: an Event object.
           *  - jqXHR: a jqXHR object
           *  - textStatus: the status of the request ("success", "notmodified", "error", "timeout", "abort", or "parsererror").
           */
          ajaxComplete: 'ajaxComplete'
      };
  
      // NOTE: If you change any of these defaults, make sure you update yii\widgets\ActiveForm::getClientOptions() as well
      var defaults = {
          // whether to encode the error summary
          encodeErrorSummary: true,
          // the jQuery selector for the error summary
          errorSummary: '.error-summary',
          // whether to perform validation before submitting the form.
          validateOnSubmit: true,
          // the container CSS class representing the corresponding attribute has validation error
          errorCssClass: 'has-error',
          // the container CSS class representing the corresponding attribute passes validation
          successCssClass: 'has-success',
          // the container CSS class representing the corresponding attribute is being validated
          validatingCssClass: 'validating',
          // the GET parameter name indicating an AJAX-based validation
          ajaxParam: 'ajax',
          // the type of data that you're expecting back from the server
          ajaxDataType: 'json',
          // the URL for performing AJAX-based validation. If not set, it will use the the form's action
          validationUrl: undefined,
          // whether to scroll to first visible error after validation.
          scrollToError: true
      };
  
      // NOTE: If you change any of these defaults, make sure you update yii\widgets\ActiveField::getClientOptions() as well
      var attributeDefaults = {
          // a unique ID identifying an attribute (e.g. "loginform-username") in a form
          id: undefined,
          // attribute name or expression (e.g. "[0]content" for tabular input)
          name: undefined,
          // the jQuery selector of the container of the input field
          container: undefined,
          // the jQuery selector of the input field under the context of the form
          input: undefined,
          // the jQuery selector of the error tag under the context of the container
          error: '.help-block',
          // whether to encode the error
          encodeError: true,
          // whether to perform validation when a change is detected on the input
          validateOnChange: true,
          // whether to perform validation when the input loses focus
          validateOnBlur: true,
          // whether to perform validation when the user is typing.
          validateOnType: false,
          // number of milliseconds that the validation should be delayed when a user is typing in the input field.
          validationDelay: 500,
          // whether to enable AJAX-based validation.
          enableAjaxValidation: false,
          // function (attribute, value, messages, deferred, $form), the client-side validation function.
          validate: undefined,
          // status of the input field, 0: empty, not entered before, 1: validated, 2: pending validation, 3: validating
          status: 0,
          // whether the validation is cancelled by beforeValidateAttribute event handler
          cancelled: false,
          // the value of the input
          value: undefined
      };
  
  
      var submitDefer;
  
      var setSubmitFinalizeDefer = function($form) {
          submitDefer = $.Deferred();
          $form.data('yiiSubmitFinalizePromise', submitDefer.promise());
      };
  
      // finalize yii.js $form.submit
      var submitFinalize = function($form) {
          if(submitDefer) {
              submitDefer.resolve();
              submitDefer = undefined;
              $form.removeData('yiiSubmitFinalizePromise');
          }
      };
  
  
      var methods = {
          init: function (attributes, options) {
              return this.each(function () {
                  var $form = $(this);
                  if ($form.data('yiiActiveForm')) {
                      return;
                  }
  
                  var settings = $.extend({}, defaults, options || {});
                  if (settings.validationUrl === undefined) {
                      settings.validationUrl = $form.attr('action');
                  }
  
                  $.each(attributes, function (i) {
                      attributes[i] = $.extend({value: getValue($form, this)}, attributeDefaults, this);
                      watchAttribute($form, attributes[i]);
                  });
  
                  $form.data('yiiActiveForm', {
                      settings: settings,
                      attributes: attributes,
                      submitting: false,
                      validated: false
                  });
  
                  /**
                   * Clean up error status when the form is reset.
                   * Note that $form.on('reset', ...) does work because the "reset" event does not bubble on IE.
                   */
                  $form.bind('reset.yiiActiveForm', methods.resetForm);
  
                  if (settings.validateOnSubmit) {
                      $form.on('mouseup.yiiActiveForm keyup.yiiActiveForm', ':submit', function () {
                          $form.data('yiiActiveForm').submitObject = $(this);
                      });
                      $form.on('submit.yiiActiveForm', methods.submitForm);
                  }
              });
          },
  
          // add a new attribute to the form dynamically.
          // please refer to attributeDefaults for the structure of attribute
          add: function (attribute) {
              var $form = $(this);
              attribute = $.extend({value: getValue($form, attribute)}, attributeDefaults, attribute);
              $form.data('yiiActiveForm').attributes.push(attribute);
              watchAttribute($form, attribute);
          },
  
          // remove the attribute with the specified ID from the form
          remove: function (id) {
              var $form = $(this),
                  attributes = $form.data('yiiActiveForm').attributes,
                  index = -1,
                  attribute = undefined;
              $.each(attributes, function (i) {
                  if (attributes[i]['id'] == id) {
                      index = i;
                      attribute = attributes[i];
                      return false;
                  }
              });
              if (index >= 0) {
                  attributes.splice(index, 1);
                  unwatchAttribute($form, attribute);
              }
              return attribute;
          },
  
          // manually trigger the validation of the attribute with the specified ID
          validateAttribute: function (id) {
              var attribute = methods.find.call(this, id);
              if (attribute != undefined) {
                  validateAttribute($(this), attribute, true);
              }
          },
  
          // find an attribute config based on the specified attribute ID
          find: function (id) {
              var attributes = $(this).data('yiiActiveForm').attributes,
                  result = undefined;
              $.each(attributes, function (i) {
                  if (attributes[i]['id'] == id) {
                      result = attributes[i];
                      return false;
                  }
              });
              return result;
          },
  
          destroy: function () {
              return this.each(function () {
                  $(this).unbind('.yiiActiveForm');
                  $(this).removeData('yiiActiveForm');
              });
          },
  
          data: function () {
              return this.data('yiiActiveForm');
          },
  
          // validate all applicable inputs in the form
          validate: function () {
              var $form = $(this),
                  data = $form.data('yiiActiveForm'),
                  needAjaxValidation = false,
                  messages = {},
                  deferreds = deferredArray(),
                  submitting = data.submitting;
  
              if (submitting) {
                  var event = $.Event(events.beforeValidate);
                  $form.trigger(event, [messages, deferreds]);
                  if (event.result === false) {
                      data.submitting = false;
                      submitFinalize($form);
                      return;
                  }
              }
  
              // client-side validation
              $.each(data.attributes, function () {
                  if (!$(this.input).is(":disabled")) {
                      this.cancelled = false;
                      // perform validation only if the form is being submitted or if an attribute is pending validation
                      if (data.submitting || this.status === 2 || this.status === 3) {
                          var msg = messages[this.id];
                          if (msg === undefined) {
                              msg = [];
                              messages[this.id] = msg;
                          }
                          var event = $.Event(events.beforeValidateAttribute);
                          $form.trigger(event, [this, msg, deferreds]);
                          if (event.result !== false) {
                              if (this.validate) {
                                  this.validate(this, getValue($form, this), msg, deferreds, $form);
                              }
                              if (this.enableAjaxValidation) {
                                  needAjaxValidation = true;
                              }
                          } else {
                              this.cancelled = true;
                          }
                      }
                  }
              });
  
              // ajax validation
              $.when.apply(this, deferreds).always(function() {
                  // Remove empty message arrays
                  for (var i in messages) {
                      if (0 === messages[i].length) {
                          delete messages[i];
                      }
                  }
                  if (needAjaxValidation) {
                      var $button = data.submitObject,
                          extData = '&' + data.settings.ajaxParam + '=' + $form.attr('id');
                      if ($button && $button.length && $button.attr('name')) {
                          extData += '&' + $button.attr('name') + '=' + $button.attr('value');
                      }
                      $.ajax({
                          url: data.settings.validationUrl,
                          type: $form.attr('method'),
                          data: $form.serialize() + extData,
                          dataType: data.settings.ajaxDataType,
                          complete: function (jqXHR, textStatus) {
                              $form.trigger(events.ajaxComplete, [jqXHR, textStatus]);
                          },
                          beforeSend: function (jqXHR, settings) {
                              $form.trigger(events.ajaxBeforeSend, [jqXHR, settings]);
                          },
                          success: function (msgs) {
                              if (msgs !== null && typeof msgs === 'object') {
                                  $.each(data.attributes, function () {
                                      if (!this.enableAjaxValidation || this.cancelled) {
                                          delete msgs[this.id];
                                      }
                                  });
                                  updateInputs($form, $.extend(messages, msgs), submitting);
                              } else {
                                  updateInputs($form, messages, submitting);
                              }
                          },
                          error: function () {
                              data.submitting = false;
                              submitFinalize($form);
                          }
                      });
                  } else if (data.submitting) {
                      // delay callback so that the form can be submitted without problem
                      setTimeout(function () {
                          updateInputs($form, messages, submitting);
                      }, 200);
                  } else {
                      updateInputs($form, messages, submitting);
                  }
              });
          },
  
          submitForm: function () {
              var $form = $(this),
                  data = $form.data('yiiActiveForm');
  
              if (data.validated) {
                  // Second submit's call (from validate/updateInputs)
                  data.submitting = false;
                  var event = $.Event(events.beforeSubmit);
                  $form.trigger(event);
                  if (event.result === false) {
                      data.validated = false;
                      submitFinalize($form);
                      return false;
                  }
                  return true;   // continue submitting the form since validation passes
              } else {
                  // First submit's call (from yii.js/handleAction) - execute validating
                  setSubmitFinalizeDefer($form);
  
                  if (data.settings.timer !== undefined) {
                      clearTimeout(data.settings.timer);
                  }
                  data.submitting = true;
                  methods.validate.call($form);
                  return false;
              }
          },
  
          resetForm: function () {
              var $form = $(this);
              var data = $form.data('yiiActiveForm');
              // Because we bind directly to a form reset event instead of a reset button (that may not exist),
              // when this function is executed form input values have not been reset yet.
              // Therefore we do the actual reset work through setTimeout.
              setTimeout(function () {
                  $.each(data.attributes, function () {
                      // Without setTimeout() we would get the input values that are not reset yet.
                      this.value = getValue($form, this);
                      this.status = 0;
                      var $container = $form.find(this.container);
                      $container.removeClass(
                          data.settings.validatingCssClass + ' ' +
                              data.settings.errorCssClass + ' ' +
                              data.settings.successCssClass
                      );
                      $container.find(this.error).html('');
                  });
                  $form.find(data.settings.errorSummary).hide().find('ul').html('');
              }, 1);
          },
  
          /**
           * Updates error messages, input containers, and optionally summary as well.
           * If an attribute is missing from messages, it is considered valid.
           * @param messages array the validation error messages, indexed by attribute IDs
           * @param summary whether to update summary as well.
           */
          updateMessages: function (messages, summary) {
              var $form = $(this);
              var data = $form.data('yiiActiveForm');
              $.each(data.attributes, function () {
                  updateInput($form, this, messages);
              });
              if (summary) {
                  updateSummary($form, messages);
              }
          },
  
          /**
           * Updates error messages and input container of a single attribute.
           * If messages is empty, the attribute is considered valid.
           * @param id attribute ID
           * @param messages array with error messages
           */
          updateAttribute: function(id, messages) {
              var attribute = methods.find.call(this, id);
              if (attribute != undefined) {
                  var msg = {};
                  msg[id] = messages;
                  updateInput($(this), attribute, msg);
              }
          }
  
      };
  
      var watchAttribute = function ($form, attribute) {
          var $input = findInput($form, attribute);
          if (attribute.validateOnChange) {
              $input.on('change.yiiActiveForm', function () {
                  validateAttribute($form, attribute, false);
              });
          }
          if (attribute.validateOnBlur) {
              $input.on('blur.yiiActiveForm', function () {
                  if (attribute.status == 0 || attribute.status == 1) {
                      validateAttribute($form, attribute, !attribute.status);
                  }
              });
          }
          if (attribute.validateOnType) {
              $input.on('keyup.yiiActiveForm', function (e) {
                  if ($.inArray(e.which, [16, 17, 18, 37, 38, 39, 40]) !== -1 ) {
                      return;
                  }
                  if (attribute.value !== getValue($form, attribute)) {
                      validateAttribute($form, attribute, false, attribute.validationDelay);
                  }
              });
          }
      };
  
      var unwatchAttribute = function ($form, attribute) {
          findInput($form, attribute).off('.yiiActiveForm');
      };
  
      var validateAttribute = function ($form, attribute, forceValidate, validationDelay) {
          var data = $form.data('yiiActiveForm');
  
          if (forceValidate) {
              attribute.status = 2;
          }
          $.each(data.attributes, function () {
              if (this.value !== getValue($form, this)) {
                  this.status = 2;
                  forceValidate = true;
              }
          });
          if (!forceValidate) {
              return;
          }
  
          if (data.settings.timer !== undefined) {
              clearTimeout(data.settings.timer);
          }
          data.settings.timer = setTimeout(function () {
              if (data.submitting || $form.is(':hidden')) {
                  return;
              }
              $.each(data.attributes, function () {
                  if (this.status === 2) {
                      this.status = 3;
                      $form.find(this.container).addClass(data.settings.validatingCssClass);
                  }
              });
              methods.validate.call($form);
          }, validationDelay ? validationDelay : 200);
      };
  
      /**
       * Returns an array prototype with a shortcut method for adding a new deferred.
       * The context of the callback will be the deferred object so it can be resolved like ```this.resolve()```
       * @returns Array
       */
      var deferredArray = function () {
          var array = [];
          array.add = function(callback) {
              this.push(new $.Deferred(callback));
          };
          return array;
      };
  
      /**
       * Updates the error messages and the input containers for all applicable attributes
       * @param $form the form jQuery object
       * @param messages array the validation error messages
       * @param submitting whether this method is called after validation triggered by form submission
       */
      var updateInputs = function ($form, messages, submitting) {
          var data = $form.data('yiiActiveForm');
  
          if (submitting) {
              var errorAttributes = [];
              $.each(data.attributes, function () {
                  if (!$(this.input).is(":disabled") && !this.cancelled && updateInput($form, this, messages)) {
                      errorAttributes.push(this);
                  }
              });
  
              $form.trigger(events.afterValidate, [messages, errorAttributes]);
  
              updateSummary($form, messages);
  
              if (errorAttributes.length) {
                  if (data.settings.scrollToError) {
                      var top = $form.find($.map(errorAttributes, function(attribute) {
                          return attribute.input;
                      }).join(',')).first().closest(':visible').offset().top;
                      var wtop = $(window).scrollTop();
                      if (top < wtop || top > wtop + $(window).height()) {
                          $(window).scrollTop(top);
                      }
                  }
                  data.submitting = false;
              } else {
                  data.validated = true;
                  var $button = data.submitObject || $form.find(':submit:first');
                  // TODO: if the submission is caused by "change" event, it will not work
                  if ($button.length && $button.attr('type') == 'submit' && $button.attr('name')) {
                      // simulate button input value
                      var $hiddenButton = $('input[type="hidden"][name="' + $button.attr('name') + '"]', $form);
                      if (!$hiddenButton.length) {
                          $('<input>').attr({
                              type: 'hidden',
                              name: $button.attr('name'),
                              value: $button.attr('value')
                          }).appendTo($form);
                      } else {
                          $hiddenButton.attr('value', $button.attr('value'));
                      }
                  }
                  $form.submit();
              }
          } else {
              $.each(data.attributes, function () {
                  if (!this.cancelled && (this.status === 2 || this.status === 3)) {
                      updateInput($form, this, messages);
                  }
              });
          }
          submitFinalize($form);
      };
  
      /**
       * Updates the error message and the input container for a particular attribute.
       * @param $form the form jQuery object
       * @param attribute object the configuration for a particular attribute.
       * @param messages array the validation error messages
       * @return boolean whether there is a validation error for the specified attribute
       */
      var updateInput = function ($form, attribute, messages) {
          var data = $form.data('yiiActiveForm'),
              $input = findInput($form, attribute),
              hasError = false;
  
          if (!$.isArray(messages[attribute.id])) {
              messages[attribute.id] = [];
          }
          $form.trigger(events.afterValidateAttribute, [attribute, messages[attribute.id]]);
  
          attribute.status = 1;
          if ($input.length) {
              hasError = messages[attribute.id].length > 0;
              var $container = $form.find(attribute.container);
              var $error = $container.find(attribute.error);
              if (hasError) {
                  if (attribute.encodeError) {
                      $error.text(messages[attribute.id][0]);
                  } else {
                      $error.html(messages[attribute.id][0]);
                  }
                  $container.removeClass(data.settings.validatingCssClass + ' ' + data.settings.successCssClass)
                      .addClass(data.settings.errorCssClass);
              } else {
                  $error.empty();
                  $container.removeClass(data.settings.validatingCssClass + ' ' + data.settings.errorCssClass + ' ')
                      .addClass(data.settings.successCssClass);
              }
              attribute.value = getValue($form, attribute);
          }
          return hasError;
      };
  
      /**
       * Updates the error summary.
       * @param $form the form jQuery object
       * @param messages array the validation error messages
       */
      var updateSummary = function ($form, messages) {
          var data = $form.data('yiiActiveForm'),
              $summary = $form.find(data.settings.errorSummary),
              $ul = $summary.find('ul').empty();
  
          if ($summary.length && messages) {
              $.each(data.attributes, function () {
                  if ($.isArray(messages[this.id]) && messages[this.id].length) {
                      var error = $('<li/>');
                      if (data.settings.encodeErrorSummary) {
                          error.text(messages[this.id][0]);
                      } else {
                          error.html(messages[this.id][0]);
                      }
                      $ul.append(error);
                  }
              });
              $summary.toggle($ul.find('li').length > 0);
          }
      };
  
      var getValue = function ($form, attribute) {
          var $input = findInput($form, attribute);
          var type = $input.attr('type');
          if (type === 'checkbox' || type === 'radio') {
              var $realInput = $input.filter(':checked');
              if (!$realInput.length) {
                  $realInput = $form.find('input[type=hidden][name="' + $input.attr('name') + '"]');
              }
              return $realInput.val();
          } else {
              return $input.val();
          }
      };
  
      var findInput = function ($form, attribute) {
          var $input = $form.find(attribute.input);
          if ($input.length && $input[0].tagName.toLowerCase() === 'div') {
              // checkbox list or radio list
              return $input.find('input');
          } else {
              return $input;
          }
      };
  
  })(window.jQuery);
  