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