1/** 2 * Yii form widget. 3 * 4 * This is the JavaScript widget used by the yii\widgets\ActiveForm widget. 5 * 6 * @link https://www.yiiframework.com/ 7 * @copyright Copyright (c) 2008 Yii Software LLC 8 * @license https://www.yiiframework.com/license/ 9 * @author Qiang Xue <[email protected]> 10 * @since 2.0 11 */ 12(function ($) { 13 14 $.fn.yiiActiveForm = function (method) { 15 if (methods[method]) { 16 return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); 17 } else { 18 if (typeof method === 'object' || !method) { 19 return methods.init.apply(this, arguments); 20 } else { 21 $.error('Method ' + method + ' does not exist on jQuery.yiiActiveForm'); 22 return false; 23 } 24 } 25 }; 26 27 var events = { 28 /** 29 * beforeValidate event is triggered before validating the whole form. 30 * The signature of the event handler should be: 31 * function (event, messages, deferreds) 32 * where 33 * - event: an Event object. 34 * - messages: an associative array with keys being attribute IDs and values being error message arrays 35 * for the corresponding attributes. 36 * - deferreds: an array of Deferred objects. You can use deferreds.add(callback) to add a new deferred validation. 37 * 38 * If the handler returns a boolean false, it will stop further form validation after this event. And as 39 * a result, afterValidate event will not be triggered. 40 */ 41 beforeValidate: 'beforeValidate', 42 /** 43 * afterValidate event is triggered after validating the whole form. 44 * The signature of the event handler should be: 45 * function (event, messages, errorAttributes) 46 * where 47 * - event: an Event object. 48 * - messages: an associative array with keys being attribute IDs and values being error message arrays 49 * for the corresponding attributes. 50 * - errorAttributes: an array of attributes that have validation errors. Please refer to attributeDefaults for the structure of this parameter. 51 */ 52 afterValidate: 'afterValidate', 53 /** 54 * beforeValidateAttribute event is triggered before validating an attribute. 55 * The signature of the event handler should be: 56 * function (event, attribute, messages, deferreds) 57 * where 58 * - event: an Event object. 59 * - attribute: the attribute to be validated. Please refer to attributeDefaults for the structure of this parameter. 60 * - messages: an array to which you can add validation error messages for the specified attribute. 61 * - deferreds: an array of Deferred objects. You can use deferreds.add(callback) to add a new deferred validation. 62 * 63 * If the handler returns a boolean false, it will stop further validation of the specified attribute. 64 * And as a result, afterValidateAttribute event will not be triggered. 65 */ 66 beforeValidateAttribute: 'beforeValidateAttribute', 67 /** 68 * afterValidateAttribute event is triggered after validating the whole form and each attribute. 69 * The signature of the event handler should be: 70 * function (event, attribute, messages) 71 * where 72 * - event: an Event object. 73 * - attribute: the attribute being validated. Please refer to attributeDefaults for the structure of this parameter. 74 * - messages: an array to which you can add additional validation error messages for the specified attribute. 75 */ 76 afterValidateAttribute: 'afterValidateAttribute', 77 /** 78 * beforeSubmit event is triggered before submitting the form after all validations have passed. 79 * The signature of the event handler should be: 80 * function (event) 81 * where event is an Event object. 82 * 83 * If the handler returns a boolean false, it will stop form submission. 84 */ 85 beforeSubmit: 'beforeSubmit', 86 /** 87 * ajaxBeforeSend event is triggered before sending an AJAX request for AJAX-based validation. 88 * The signature of the event handler should be: 89 * function (event, jqXHR, settings) 90 * where 91 * - event: an Event object. 92 * - jqXHR: a jqXHR object 93 * - settings: the settings for the AJAX request 94 */ 95 ajaxBeforeSend: 'ajaxBeforeSend', 96 /** 97 * ajaxComplete event is triggered after completing an AJAX request for AJAX-based validation. 98 * The signature of the event handler should be: 99 * function (event, jqXHR, textStatus) 100 * where 101 * - event: an Event object. 102 * - jqXHR: a jqXHR object 103 * - textStatus: the status of the request ("success", "notmodified", "error", "timeout", "abort", or "parsererror"). 104 */ 105 ajaxComplete: 'ajaxComplete', 106 /** 107 * afterInit event is triggered after yii activeForm init. 108 * The signature of the event handler should be: 109 * function (event) 110 * where 111 * - event: an Event object. 112 */ 113 afterInit: 'afterInit' 114 }; 115 116 // NOTE: If you change any of these defaults, make sure you update yii\widgets\ActiveForm::getClientOptions() as well 117 var defaults = { 118 // whether to encode the error summary 119 encodeErrorSummary: true, 120 // the jQuery selector for the error summary 121 errorSummary: '.error-summary', 122 // whether to perform validation before submitting the form. 123 validateOnSubmit: true, 124 // the container CSS class representing the corresponding attribute has validation error 125 errorCssClass: 'has-error', 126 // the container CSS class representing the corresponding attribute passes validation 127 successCssClass: 'has-success', 128 // the container CSS class representing the corresponding attribute is being validated 129 validatingCssClass: 'validating', 130 // the GET parameter name indicating an AJAX-based validation 131 ajaxParam: 'ajax', 132 // the type of data that you're expecting back from the server 133 ajaxDataType: 'json', 134 // the URL for performing AJAX-based validation. If not set, it will use the the form's action 135 validationUrl: undefined, 136 // whether to scroll to first visible error after validation. 137 scrollToError: true, 138 // offset in pixels that should be added when scrolling to the first error. 139 scrollToErrorOffset: 0, 140 // where to add validation class: container or input 141 validationStateOn: 'container' 142 }; 143 144 // NOTE: If you change any of these defaults, make sure you update yii\widgets\ActiveField::getClientOptions() as well 145 var attributeDefaults = { 146 // a unique ID identifying an attribute (e.g. "loginform-username") in a form 147 id: undefined, 148 // attribute name or expression (e.g. "[0]content" for tabular input) 149 name: undefined, 150 // the jQuery selector of the container of the input field 151 container: undefined, 152 // the jQuery selector of the input field under the context of the form 153 input: undefined, 154 // the jQuery selector of the error tag under the context of the container 155 error: '.help-block', 156 // whether to encode the error 157 encodeError: true, 158 // whether to perform validation when a change is detected on the input 159 validateOnChange: true, 160 // whether to perform validation when the input loses focus 161 validateOnBlur: true, 162 // whether to perform validation when the user is typing. 163 validateOnType: false, 164 // number of milliseconds that the validation should be delayed when a user is typing in the input field. 165 validationDelay: 500, 166 // whether to enable AJAX-based validation. 167 enableAjaxValidation: false, 168 // function (attribute, value, messages, deferred, $form), the client-side validation function. 169 validate: undefined, 170 // status of the input field, 0: empty, not entered before, 1: validated, 2: pending validation, 3: validating 171 status: 0, 172 // whether the validation is cancelled by beforeValidateAttribute event handler 173 cancelled: false, 174 // the value of the input 175 value: undefined, 176 // whether to update aria-invalid attribute after validation 177 updateAriaInvalid: true 178 }; 179 180 181 var submitDefer; 182 183 var setSubmitFinalizeDefer = function ($form) { 184 submitDefer = $.Deferred(); 185 $form.data('yiiSubmitFinalizePromise', submitDefer.promise()); 186 }; 187 188 // finalize yii.js $form.submit 189 var submitFinalize = function ($form) { 190 if (submitDefer) { 191 submitDefer.resolve(); 192 submitDefer = undefined; 193 $form.removeData('yiiSubmitFinalizePromise'); 194 } 195 }; 196 197 198 var methods = { 199 init: function (attributes, options) { 200 return this.each(function () { 201 var $form = $(this); 202 if ($form.data('yiiActiveForm')) { 203 return; 204 } 205 206 var settings = $.extend({}, defaults, options || {});
207 if (settings.validationUrl === undefined) { 208 settings.validationUrl = $form.attr('action'); 209 } 210 211 $.each(attributes, function (i) { 212 attributes[i] = $.extend({value: getValue($form, this)}, attributeDefaults, this); 213 watchAttribute($form, attributes[i]); 214 }); 215 216 $form.data('yiiActiveForm', { 217 settings: settings, 218 attributes: attributes, 219 submitting: false, 220 validated: false, 221 validate_only: false, // validate without auto submitting 222 options: getFormOptions($form) 223 }); 224 225 /** 226 * Clean up error status when the form is reset. 227 * Note that $form.on('reset', ...) does work because the "reset" event does not bubble on IE. 228 */ 229 $form.on('reset.yiiActiveForm', methods.resetForm); 230 231 if (settings.validateOnSubmit) { 232 $form.on('mouseup.yiiActiveForm keyup.yiiActiveForm', ':submit', function () { 233 $form.data('yiiActiveForm').submitObject = $(this); 234 }); 235 $form.on('submit.yiiActiveForm', methods.submitForm); 236 } 237 var event = $.Event(events.afterInit); 238 $form.trigger(event); 239 }); 240 }, 241 242 // add a new attribute to the form dynamically. 243 // please refer to attributeDefaults for the structure of attribute 244 add: function (attribute) { 245 var $form = $(this); 246 attribute = $.extend({value: getValue($form, attribute)}, attributeDefaults, attribute); 247 $form.data('yiiActiveForm').attributes.push(attribute); 248 watchAttribute($form, attribute); 249 }, 250 251 // remove the attribute with the specified ID from the form 252 remove: function (id) { 253 var $form = $(this), 254 attributes = $form.data('yiiActiveForm').attributes, 255 index = -1, 256 attribute = undefined; 257 $.each(attributes, function (i) { 258 if (attributes[i]['id'] == id) { 259 index = i; 260 attribute = attributes[i]; 261 return false; 262 } 263 }); 264 if (index >= 0) { 265 attributes.splice(index, 1); 266 unwatchAttribute($form, attribute); 267 } 268 269 return attribute; 270 }, 271 272 // manually trigger the validation of the attribute with the specified ID 273 validateAttribute: function (id) { 274 var attribute = methods.find.call(this, id); 275 if (attribute != undefined) { 276 validateAttribute($(this), attribute, true); 277 } 278 }, 279 280 // find an attribute config based on the specified attribute ID 281 find: function (id) { 282 var attributes = $(this).data('yiiActiveForm').attributes, 283 result = undefined; 284 $.each(attributes, function (i) { 285 if (attributes[i]['id'] == id) { 286 result = attributes[i]; 287 return false; 288 } 289 }); 290 return result; 291 }, 292 293 destroy: function () { 294 return this.each(function () { 295 $(this).off('.yiiActiveForm'); 296 $(this).removeData('yiiActiveForm'); 297 }); 298 }, 299 300 data: function () { 301 return this.data('yiiActiveForm'); 302 }, 303 304 // validate all applicable inputs in the form 305 validate: function (forceValidate) { 306 if (forceValidate) { 307 $(this).data('yiiActiveForm').submitting = true; 308 } 309 310 var $form = $(this), 311 data = $form.data('yiiActiveForm'), 312 needAjaxValidation = false, 313 messages = {}, 314 deferreds = deferredArray(), 315 submitting = data.submitting; 316 317 if (submitting) { 318 var event = $.Event(events.beforeValidate); 319 $form.trigger(event, [messages, deferreds]); 320 321 if (event.result === false) { 322 data.submitting = false;
323 submitFinalize($form); 324 return; 325 } 326 } 327 328 // client-side validation 329 $.each(data.attributes, function () { 330 this.$form = $form; 331 var $input = findInput($form, this); 332 333 var disabled = $input.toArray().reduce(function (result, next) { 334 return result && $(next).is(':disabled'); 335 }, true); 336 if (disabled) { 337 return true; 338 } 339 // validate markup for select input 340 if ($input.length && $input[0].tagName.toLowerCase() === 'select') { 341 var opts = $input[0].options, isEmpty = !opts || !opts.length, isRequired = $input.attr('required'), 342 isMultiple = $input.attr('multiple'), size = $input.attr('size') || 1; 343 // check if valid HTML markup for select input, else return validation as `true` 344 // https://w3c.github.io/html-reference/select.html 345 if (isRequired && !isMultiple && parseInt(size, 10) === 1) { // invalid select markup condition 346 if (isEmpty) { // empty option elements for the select 347 return true; 348 } 349 if (opts[0] && (opts[0].value !== '' && opts[0].text !== '')) { // first option is not empty 350 return true; 351 } 352 } 353 } 354 this.cancelled = false; 355 // perform validation only if the form is being submitted or if an attribute is pending validation 356 if (data.submitting || this.status === 2 || this.status === 3) { 357 var msg = messages[this.id]; 358 if (msg === undefined) { 359 msg = []; 360 messages[this.id] = msg; 361 } 362 363 var event = $.Event(events.beforeValidateAttribute); 364 $form.trigger(event, [this, msg, deferreds]); 365 if (event.result !== false) { 366 if (this.validate) { 367 this.validate(this, getValue($form, this), msg, deferreds, $form); 368 } 369 if (this.enableAjaxValidation) { 370 needAjaxValidation = true; 371 } 372 } else { 373 this.cancelled = true; 374 } 375 } 376 }); 377 378 // ajax validation 379 $.when.apply(this, deferreds).always(function () { 380 // Remove empty message arrays 381 for (var i in messages) { 382 if (0 === messages[i].length) { 383 delete messages[i]; 384 } 385 } 386 if (needAjaxValidation && ($.isEmptyObject(messages) || data.submitting)) { 387 var $button = data.submitObject, 388 extData = '&' + data.settings.ajaxParam + '=' + $form.attr('id'); 389 if ($button && $button.length && $button.attr('name')) { 390 extData += '&' + $button.attr('name') + '=' + $button.attr('value'); 391 } 392 $.ajax({ 393 url: data.settings.validationUrl, 394 type: $form.attr('method'), 395 data: $form.serialize() + extData, 396 dataType: data.settings.ajaxDataType, 397 complete: function (jqXHR, textStatus) { 398 currentAjaxRequest = null; 399 $form.trigger(events.ajaxComplete, [jqXHR, textStatus]); 400 }, 401 beforeSend: function (jqXHR, settings) { 402 currentAjaxRequest = jqXHR; 403 $form.trigger(events.ajaxBeforeSend, [jqXHR, settings]); 404 }, 405 success: function (msgs) { 406 if (msgs !== null && typeof msgs === 'object') { 407 $.each(data.attributes, function () { 408 if (!this.enableAjaxValidation || this.cancelled) { 409 delete msgs[this.id]; 410 } 411 }); 412 updateInputs($form, $.extend(messages, msgs), submitting); 413 } else { 414 updateInputs($form, messages, submitting); 415 } 416 }, 417 error: function () { 418 data.submitting = false;
419 submitFinalize($form); 420 } 421 }); 422 } else { 423 if (data.submitting) { 424 // delay callback so that the form can be submitted without problem 425 window.setTimeout(function () { 426 updateInputs($form, messages, submitting); 427 }, 200); 428 } else { 429 updateInputs($form, messages, submitting); 430 } 431 } 432 }); 433 }, 434 435 submitForm: function () { 436 var $form = $(this), 437 data = $form.data('yiiActiveForm'); 438 if (data.validated) { 439 // Second submit's call (from validate/updateInputs) 440 data.submitting = false; 441 var event = $.Event(events.beforeSubmit); 442 $form.trigger(event); 443 if (event.result === false) { 444 data.validated = false; 445 submitFinalize($form); 446 return false; 447 } 448 updateHiddenButton($form); 449 return true; // continue submitting the form since validation passes 450 } else { 451 // First submit's call (from yii.js/handleAction) - execute validating 452 setSubmitFinalizeDefer($form); 453 454 if (data.settings.timer !== undefined) { 455 clearTimeout(data.settings.timer); 456 } 457 data.submitting = true; 458 methods.validate.call($form); 459 return false; 460 } 461 }, 462 463 resetForm: function () { 464 var $form = $(this); 465 var data = $form.data('yiiActiveForm'); 466 // Because we bind directly to a form reset event instead of a reset button (that may not exist), 467 // when this function is executed form input values have not been reset yet. 468 // Therefore we do the actual reset work through setTimeout. 469 window.setTimeout(function () { 470 $.each(data.attributes, function () {
471 // Without setTimeout() we would get the input values that are not reset yet. 472 this.value = getValue($form, this); 473 this.status = 0; 474 var $container = $form.find(this.container), 475 $input = findInput($form, this), 476 $errorElement = data.settings.validationStateOn === 'input' ? $input : $container; 477 478 $errorElement.removeClass( 479 data.settings.validatingCssClass + ' ' + 480 data.settings.errorCssClass + ' ' + 481 data.settings.successCssClass 482 ); 483 $container.find(this.error).html(''); 484 }); 485 $form.find(data.settings.errorSummary).hide().find('ul').html(''); 486 }, 1); 487 }, 488 489 /** 490 * Updates error messages, input containers, and optionally summary as well. 491 * If an attribute is missing from messages, it is considered valid. 492 * @param messages array the validation error messages, indexed by attribute IDs 493 * @param summary whether to update summary as well. 494 */ 495 updateMessages: function (messages, summary) { 496 var $form = $(this); 497 var data = $form.data('yiiActiveForm'); 498 $.each(data.attributes, function () { 499 updateInput($form, this, messages); 500 }); 501 if (summary) { 502 updateSummary($form, messages); 503 } 504 }, 505 506 /** 507 * Updates error messages and input container of a single attribute. 508 * If messages is empty, the attribute is considered valid. 509 * @param id attribute ID 510 * @param messages array with error messages 511 */ 512 updateAttribute: function (id, messages) { 513 var attribute = methods.find.call(this, id); 514 if (attribute != undefined) { 515 var msg = {}; 516 msg[id] = messages; 517 updateInput($(this), attribute, msg); 518 } 519 } 520 }; 521 522 var watchAttribute = function ($form, attribute) { 523 var $input = findInput($form, attribute); 524 if (attribute.validateOnChange) { 525 $input.on('change.yiiActiveForm', function () { 526 validateAttribute($form, attribute, false); 527 }); 528 } 529 if (attribute.validateOnBlur) { 530 $input.on('blur.yiiActiveForm', function () { 531 if (attribute.status == 0 || attribute.status == 1) { 532 validateAttribute($form, attribute, true); 533 } 534 }); 535 } 536 if (attribute.validateOnType) { 537 $input.on('keyup.yiiActiveForm', function (e) { 538 if ($.inArray(e.which, [16, 17, 18, 37, 38, 39, 40]) !== -1) { 539 return; 540 } 541 if (attribute.value !== getValue($form, attribute)) { 542 validateAttribute($form, attribute, false, attribute.validationDelay); 543 } 544 }); 545 } 546 }; 547 548 var unwatchAttribute = function ($form, attribute) { 549 findInput($form, attribute).off('.yiiActiveForm'); 550 }; 551 552 var validateAttribute = function ($form, attribute, forceValidate, validationDelay) { 553 var data = $form.data('yiiActiveForm'); 554 555 if (forceValidate) { 556 attribute.status = 2; 557 } 558 $.each(data.attributes, function () { 559 if (!isEqual(this.value, getValue($form, this))) { 560 this.status = 2; 561 forceValidate = true; 562 } 563 }); 564 if (!forceValidate) { 565 return; 566 } 567 568 if (currentAjaxRequest !== null) { 569 currentAjaxRequest.abort(); 570 } 571 if (data.settings.timer !== undefined) { 572 clearTimeout(data.settings.timer); 573 } 574 data.settings.timer = window.setTimeout(function () { 575 if (data.submitting || $form.is(':hidden')) { 576 return; 577 } 578 $.each(data.attributes, function () { 579 if (this.status === 2) { 580 this.status = 3; 581 582 var $container = $form.find(this.container), 583 $input = findInput($form, this); 584
585 var $errorElement = data.settings.validationStateOn === 'input' ? $input : $container; 586 587 $errorElement.addClass(data.settings.validatingCssClass); 588 } 589 }); 590 methods.validate.call($form); 591 }, validationDelay ? validationDelay : 200); 592 }; 593 594 /** 595 * Compares two value whatever it objects, arrays or simple types 596 * @param val1 597 * @param val2 598 * @returns boolean 599 */ 600 var isEqual = function (val1, val2) { 601 // objects 602 if (val1 instanceof Object) { 603 return isObjectsEqual(val1, val2) 604 } 605 606 // arrays 607 if (Array.isArray(val1)) { 608 return isArraysEqual(val1, val2); 609 } 610 611 // simple types 612 return val1 === val2; 613 }; 614 615 /** 616 * Compares two objects 617 * @param obj1 618 * @param obj2 619 * @returns boolean 620 */ 621 var isObjectsEqual = function (obj1, obj2) { 622 if (!(obj1 instanceof Object) || !(obj2 instanceof Object)) { 623 return false; 624 } 625 626 var keys1 = Object.keys(obj1); 627 var keys2 = Object.keys(obj2); 628 if (keys1.length !== keys2.length) { 629 return false; 630 } 631 632 for (var i = 0; i < keys1.length; i += 1) { 633 if (!obj2.hasOwnProperty(keys1[i])) { 634 return false; 635 } 636 if (obj1[keys1[i]] !== obj2[keys1[i]]) { 637 return false; 638 } 639 } 640 641 return true; 642 }; 643 644 /** 645 * Compares two arrays 646 * @param arr1 647 * @param arr2 648 * @returns boolean 649 */ 650 var isArraysEqual = function (arr1, arr2) { 651 if (!Array.isArray(arr1) || !Array.isArray(arr2)) { 652 return false; 653 } 654 655 if (arr1.length !== arr2.length) { 656 return false; 657 } 658 for (var i = 0; i < arr1.length; i += 1) { 659 if (arr1[i] !== arr2[i]) { 660 return false; 661 } 662 } 663 return true; 664 }; 665 666 /** 667 * Returns an array prototype with a shortcut method for adding a new deferred. 668 * The context of the callback will be the deferred object so it can be resolved like ```this.resolve()``` 669 * @returns Array 670 */ 671 var deferredArray = function () { 672 var array = []; 673 array.add = function (callback) { 674 this.push(new $.Deferred(callback)); 675 }; 676 return array; 677 }; 678 679 var buttonOptions = ['action', 'target', 'method', 'enctype']; 680 681 /** 682 * Returns current form options 683 * @param $form 684 * @returns object Object with button of form options 685 */ 686 var getFormOptions = function ($form) { 687 var attributes = {}; 688 for (var i = 0; i < buttonOptions.length; i++) { 689 attributes[buttonOptions[i]] = $form.attr(buttonOptions[i]); 690 } 691 692 return attributes; 693 }; 694 695 /** 696 * Applies temporary form options related to submit button 697 * @param $form the form jQuery object 698 * @param $button the button jQuery object 699 */ 700 var applyButtonOptions = function ($form, $button) { 701 for (var i = 0; i < buttonOptions.length; i++) { 702 var value = $button.attr('form' + buttonOptions[i]); 703 if (value) { 704 $form.attr(buttonOptions[i], value); 705 } 706 } 707 }; 708 709 /** 710 * Restores original form options 711 * @param $form the form jQuery object 712 */ 713 var restoreButtonOptions = function ($form) { 714 var data = $form.data('yiiActiveForm'); 715 716 for (var i = 0; i < buttonOptions.length; i++) { 717 $form.attr(buttonOptions[i], data.options[buttonOptions[i]] || null); 718 } 719 }; 720 721 /** 722 * Updates the error messages and the input containers for all applicable attributes 723 * @param $form the form jQuery object 724 * @param messages array the validation error messages 725 * @param submitting whether this method is called after validation triggered by form submission 726 */ 727 var updateInputs = function ($form, messages, submitting) { 728 var data = $form.data('yiiActiveForm'); 729 730 if (data === undefined) { 731 return false;
732 } 733 734 var errorAttributes = [], $input; 735 $.each(data.attributes, function () { 736 var hasError = (submitting && updateInput($form, this, messages)) || (!submitting && attrHasError($form, this, messages)); 737 $input = findInput($form, this); 738 739 if (!$input.is(':disabled') && !this.cancelled && hasError) { 740 errorAttributes.push(this); 741 } 742 }); 743 744 $form.trigger(events.afterValidate, [messages, errorAttributes]); 745 746 if (submitting) { 747 updateSummary($form, messages); 748 if (errorAttributes.length) { 749 if (data.settings.scrollToError) { 750 var h = $(document).height(), top = $form.find($.map(errorAttributes, function (attribute) { 751 return attribute.input; 752 }).join(',')).first().closest(':visible').offset().top - data.settings.scrollToErrorOffset; 753 top = top < 0 ? 0 : (top > h ? h : top); 754 var wtop = $(window).scrollTop(); 755 if (top < wtop || top > wtop + $(window).height()) { 756 $(window).scrollTop(top); 757 } 758 } 759 data.submitting = false; 760 } else { 761 data.validated = true; 762 if (!data.validate_only) { 763 if (data.submitObject) { 764 applyButtonOptions($form, data.submitObject); 765 } 766 $form.submit(); 767 if (data.submitObject) { 768 restoreButtonOptions($form); 769 } 770 } 771 } 772 } else { 773 $.each(data.attributes, function () { 774 if (!this.cancelled && (this.status === 2 || this.status === 3)) { 775 updateInput($form, this, messages); 776 } 777 }); 778 } 779 submitFinalize($form); 780 }; 781 782 /** 783 * Updates hidden field that represents clicked submit button. 784 * @param $form the form jQuery object. 785 */ 786 var updateHiddenButton = function ($form) { 787 var data = $form.data('yiiActiveForm'); 788 var $button = data.submitObject || $form.find(':submit:first'); 789 // TODO: if the submission is caused by "change" event, it will not work 790 if ($button.length && $button.attr('type') == 'submit' && $button.attr('name')) { 791 // simulate button input value 792 var $hiddenButton = $('input[type="hidden"][name="' + $button.attr('name') + '"]', $form); 793 if (!$hiddenButton.length) { 794 $('<input>').attr({ 795 type: 'hidden', 796 name: $button.attr('name'), 797 value: $button.attr('value') 798 }).appendTo($form); 799 } else { 800 $hiddenButton.attr('value', $button.attr('value')); 801 } 802 } 803 }; 804 805 /** 806 * Updates the error message and the input container for a particular attribute. 807 * @param $form the form jQuery object 808 * @param attribute object the configuration for a particular attribute. 809 * @param messages array the validation error messages 810 * @return boolean whether there is a validation error for the specified attribute 811 */ 812 var updateInput = function ($form, attribute, messages) { 813 var data = $form.data('yiiActiveForm'), 814 $input = findInput($form, attribute), 815 hasError = attrHasError($form, attribute, messages); 816 817 if (!$.isArray(messages[attribute.id])) { 818 messages[attribute.id] = []; 819 } 820 821 attribute.status = 1; 822 if ($input.length) { 823 var $container = $form.find(attribute.container); 824 var $error = $container.find(attribute.error); 825 updateAriaInvalid($form, attribute, hasError); 826 827 var $errorElement = data.settings.validationStateOn === 'input' ? $input : $container; 828 829 if (hasError) { 830 if (attribute.encodeError) { 831 $error.text(messages[attribute.id][0]); 832 } else { 833 $error.html(messages[attribute.id][0]); 834 }
835 $errorElement.removeClass(data.settings.validatingCssClass + ' ' + data.settings.successCssClass) 836 .addClass(data.settings.errorCssClass); 837 } else { 838 $error.empty(); 839 $errorElement.removeClass(data.settings.validatingCssClass + ' ' + data.settings.errorCssClass + ' ') 840 .addClass(data.settings.successCssClass); 841 } 842 attribute.value = getValue($form, attribute); 843 } 844 845 $form.trigger(events.afterValidateAttribute, [attribute, messages[attribute.id]]); 846 847 return hasError; 848 }; 849 850 /** 851 * Checks if a particular attribute has an error 852 * @param $form the form jQuery object 853 * @param attribute object the configuration for a particular attribute. 854 * @param messages array the validation error messages 855 * @return boolean whether there is a validation error for the specified attribute 856 */ 857 var attrHasError = function ($form, attribute, messages) { 858 var $input = findInput($form, attribute), 859 hasError = false; 860 861 if (!$.isArray(messages[attribute.id])) { 862 messages[attribute.id] = []; 863 } 864 865 if ($input.length) { 866 hasError = messages[attribute.id].length > 0; 867 } 868 869 return hasError; 870 }; 871 872 /** 873 * Updates the error summary. 874 * @param $form the form jQuery object 875 * @param messages array the validation error messages 876 */ 877 var updateSummary = function ($form, messages) { 878 var data = $form.data('yiiActiveForm'), 879 $summary = $form.find(data.settings.errorSummary), 880 $ul = $summary.find('ul').empty(); 881 882 if ($summary.length && messages) { 883 $.each(data.attributes, function () { 884 if ($.isArray(messages[this.id]) && messages[this.id].length) { 885 var error = $('<li/>'); 886 if (data.settings.encodeErrorSummary) { 887 error.text(messages[this.id][0]); 888 } else { 889 error.html(messages[this.id][0]); 890 } 891 $ul.append(error); 892 } 893 }); 894 $summary.toggle($ul.find('li').length > 0); 895 } 896 }; 897 898 var getValue = function ($form, attribute) { 899 var $input = findInput($form, attribute); 900 var type = $input.attr('type'); 901 if (type === 'checkbox' || type === 'radio') { 902 var $realInput = $input.filter(':checked'); 903 if ($realInput.length > 1) { 904 var values = []; 905 $realInput.each(function (index) { 906 values.push($($realInput.get(index)).val()); 907 }); 908 return values; 909 } 910 911 if (!$realInput.length) { 912 $realInput = $form.find('input[type=hidden][name="' + $input.attr('name') + '"]'); 913 } 914 915 return $realInput.val(); 916 } else { 917 return $input.val(); 918 } 919 }; 920 921 var findInput = function ($form, attribute) { 922 var $input = $form.find(attribute.input); 923 if ($input.length && $input[0].tagName.toLowerCase() === 'div') { 924 // checkbox list or radio list 925 return $input.find('input'); 926 } else { 927 return $input; 928 } 929 }; 930 931 var updateAriaInvalid = function ($form, attribute, hasError) { 932 if (attribute.updateAriaInvalid) { 933 $form.find(attribute.input).attr('aria-invalid', hasError ? 'true' : 'false'); 934 } 935 } 936 937 var currentAjaxRequest = null; 938 939})(window.jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.