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 106 // NOTE: If you change any of these defaults, make sure you update yii\widgets\ActiveForm::getClientOptions() as well 107 var defaults = { 108 // whether to encode the error summary 109 encodeErrorSummary: true, 110 // the jQuery selector for the error summary 111 errorSummary: '.error-summary', 112 // whether to perform validation before submitting the form. 113 validateOnSubmit: true, 114 // the container CSS class representing the corresponding attribute has validation error 115 errorCssClass: 'has-error', 116 // the container CSS class representing the corresponding attribute passes validation 117 successCssClass: 'has-success', 118 // the container CSS class representing the corresponding attribute is being validated 119 validatingCssClass: 'validating', 120 // the GET parameter name indicating an AJAX-based validation 121 ajaxParam: 'ajax', 122 // the type of data that you're expecting back from the server 123 ajaxDataType: 'json', 124 // the URL for performing AJAX-based validation. If not set, it will use the the form's action 125 validationUrl: undefined, 126 // whether to scroll to first visible error after validation. 127 scrollToError: true 128 }; 129 130 // NOTE: If you change any of these defaults, make sure you update yii\widgets\ActiveField::getClientOptions() as well 131 var attributeDefaults = { 132 // a unique ID identifying an attribute (e.g. "loginform-username") in a form 133 id: undefined, 134 // attribute name or expression (e.g. "[0]content" for tabular input) 135 name: undefined, 136 // the jQuery selector of the container of the input field 137 container: undefined, 138 // the jQuery selector of the input field under the context of the form 139 input: undefined, 140 // the jQuery selector of the error tag under the context of the container 141 error: '.help-block', 142 // whether to encode the error 143 encodeError: true, 144 // whether to perform validation when a change is detected on the input 145 validateOnChange: true, 146 // whether to perform validation when the input loses focus 147 validateOnBlur: true, 148 // whether to perform validation when the user is typing. 149 validateOnType: false, 150 // number of milliseconds that the validation should be delayed when a user is typing in the input field. 151 validationDelay: 500, 152 // whether to enable AJAX-based validation. 153 enableAjaxValidation: false, 154 // function (attribute, value, messages, deferred, $form), the client-side validation function. 155 validate: undefined, 156 // status of the input field, 0: empty, not entered before, 1: validated, 2: pending validation, 3: validating 157 status: 0, 158 // whether the validation is cancelled by beforeValidateAttribute event handler 159 cancelled: false, 160 // the value of the input 161 value: undefined 162 }; 163 164 165 var submitDefer; 166 167 var setSubmitFinalizeDefer = function($form) { 168 submitDefer = $.Deferred(); 169 $form.data('yiiSubmitFinalizePromise', submitDefer.promise()); 170 }; 171 172 // finalize yii.js $form.submit 173 var submitFinalize = function($form) { 174 if(submitDefer) { 175 submitDefer.resolve(); 176 submitDefer = undefined; 177 $form.removeData('yiiSubmitFinalizePromise'); 178 } 179 }; 180 181 182 var methods = { 183 init: function (attributes, options) { 184 return this.each(function () { 185 var $form = $(this); 186 if ($form.data('yiiActiveForm')) { 187 return; 188 } 189 190 var settings = $.extend({}, defaults, options || {});
191 if (settings.validationUrl === undefined) { 192 settings.validationUrl = $form.attr('action'); 193 } 194 195 $.each(attributes, function (i) { 196 attributes[i] = $.extend({value: getValue($form, this)}, attributeDefaults, this); 197 watchAttribute($form, attributes[i]); 198 }); 199 200 $form.data('yiiActiveForm', { 201 settings: settings, 202 attributes: attributes, 203 submitting: false, 204 validated: false, 205 target: $form.attr('target') 206 }); 207 208 /** 209 * Clean up error status when the form is reset. 210 * Note that $form.on('reset', ...) does work because the "reset" event does not bubble on IE. 211 */ 212 $form.bind('reset.yiiActiveForm', methods.resetForm); 213 214 if (settings.validateOnSubmit) { 215 $form.on('mouseup.yiiActiveForm keyup.yiiActiveForm', ':submit', function () { 216 $form.data('yiiActiveForm').submitObject = $(this); 217 }); 218 $form.on('submit.yiiActiveForm', methods.submitForm); 219 } 220 }); 221 }, 222 223 // add a new attribute to the form dynamically. 224 // please refer to attributeDefaults for the structure of attribute 225 add: function (attribute) { 226 var $form = $(this); 227 attribute = $.extend({value: getValue($form, attribute)}, attributeDefaults, attribute); 228 $form.data('yiiActiveForm').attributes.push(attribute); 229 watchAttribute($form, attribute); 230 }, 231 232 // remove the attribute with the specified ID from the form 233 remove: function (id) { 234 var $form = $(this), 235 attributes = $form.data('yiiActiveForm').attributes, 236 index = -1, 237 attribute = undefined; 238 $.each(attributes, function (i) { 239 if (attributes[i]['id'] == id) { 240 index = i; 241 attribute = attributes[i]; 242 return false; 243 } 244 }); 245 if (index >= 0) { 246 attributes.splice(index, 1); 247 unwatchAttribute($form, attribute); 248 } 249 return attribute; 250 }, 251 252 // manually trigger the validation of the attribute with the specified ID 253 validateAttribute: function (id) { 254 var attribute = methods.find.call(this, id); 255 if (attribute != undefined) { 256 validateAttribute($(this), attribute, true); 257 } 258 }, 259 260 // find an attribute config based on the specified attribute ID 261 find: function (id) { 262 var attributes = $(this).data('yiiActiveForm').attributes, 263 result = undefined; 264 $.each(attributes, function (i) { 265 if (attributes[i]['id'] == id) { 266 result = attributes[i]; 267 return false; 268 } 269 }); 270 return result; 271 }, 272 273 destroy: function () { 274 return this.each(function () { 275 $(this).unbind('.yiiActiveForm'); 276 $(this).removeData('yiiActiveForm'); 277 }); 278 }, 279 280 data: function () { 281 return this.data('yiiActiveForm'); 282 }, 283 284 // validate all applicable inputs in the form 285 validate: function () { 286 var $form = $(this), 287 data = $form.data('yiiActiveForm'), 288 needAjaxValidation = false, 289 messages = {}, 290 deferreds = deferredArray(), 291 submitting = data.submitting; 292 293 if (submitting) { 294 var event = $.Event(events.beforeValidate); 295 $form.trigger(event, [messages, deferreds]); 296 if (event.result === false) { 297 data.submitting = false; 298 submitFinalize($form); 299 return; 300 } 301 } 302 303 // client-side validation 304 $.each(data.attributes, function () { 305 if (!$(this.input).is(":disabled")) { 306 this.cancelled = false;
307 // perform validation only if the form is being submitted or if an attribute is pending validation 308 if (data.submitting || this.status === 2 || this.status === 3) { 309 var msg = messages[this.id]; 310 if (msg === undefined) { 311 msg = []; 312 messages[this.id] = msg; 313 } 314 var event = $.Event(events.beforeValidateAttribute); 315 $form.trigger(event, [this, msg, deferreds]); 316 if (event.result !== false) { 317 if (this.validate) { 318 this.validate(this, getValue($form, this), msg, deferreds, $form); 319 } 320 if (this.enableAjaxValidation) { 321 needAjaxValidation = true; 322 } 323 } else { 324 this.cancelled = true; 325 } 326 } 327 } 328 }); 329 330 // ajax validation 331 $.when.apply(this, deferreds).always(function() { 332 // Remove empty message arrays 333 for (var i in messages) { 334 if (0 === messages[i].length) { 335 delete messages[i]; 336 } 337 } 338 if ($.isEmptyObject(messages) && needAjaxValidation) { 339 var $button = data.submitObject, 340 extData = '&' + data.settings.ajaxParam + '=' + $form.attr('id'); 341 if ($button && $button.length && $button.attr('name')) { 342 extData += '&' + $button.attr('name') + '=' + $button.attr('value'); 343 } 344 $.ajax({ 345 url: data.settings.validationUrl, 346 type: $form.attr('method'), 347 data: $form.serialize() + extData, 348 dataType: data.settings.ajaxDataType, 349 complete: function (jqXHR, textStatus) { 350 $form.trigger(events.ajaxComplete, [jqXHR, textStatus]); 351 }, 352 beforeSend: function (jqXHR, settings) { 353 $form.trigger(events.ajaxBeforeSend, [jqXHR, settings]); 354 }, 355 success: function (msgs) { 356 if (msgs !== null && typeof msgs === 'object') { 357 $.each(data.attributes, function () { 358 if (!this.enableAjaxValidation || this.cancelled) { 359 delete msgs[this.id]; 360 } 361 }); 362 updateInputs($form, $.extend(messages, msgs), submitting); 363 } else { 364 updateInputs($form, messages, submitting); 365 } 366 }, 367 error: function () { 368 data.submitting = false; 369 submitFinalize($form); 370 } 371 }); 372 } else if (data.submitting) { 373 // delay callback so that the form can be submitted without problem 374 setTimeout(function () { 375 updateInputs($form, messages, submitting); 376 }, 200); 377 } else { 378 updateInputs($form, messages, submitting); 379 } 380 }); 381 }, 382 383 submitForm: function () { 384 var $form = $(this), 385 data = $form.data('yiiActiveForm'); 386 387 if (data.validated) { 388 // Second submit's call (from validate/updateInputs) 389 data.submitting = false; 390 var event = $.Event(events.beforeSubmit); 391 $form.trigger(event); 392 if (event.result === false) { 393 data.validated = false;
394 submitFinalize($form); 395 return false; 396 } 397 updateHiddenButton($form); 398 return true; // continue submitting the form since validation passes 399 } else { 400 // First submit's call (from yii.js/handleAction) - execute validating 401 setSubmitFinalizeDefer($form); 402 403 if (data.settings.timer !== undefined) { 404 clearTimeout(data.settings.timer); 405 } 406 data.submitting = true; 407 methods.validate.call($form); 408 return false; 409 } 410 }, 411 412 resetForm: function () { 413 var $form = $(this); 414 var data = $form.data('yiiActiveForm'); 415 // Because we bind directly to a form reset event instead of a reset button (that may not exist), 416 // when this function is executed form input values have not been reset yet. 417 // Therefore we do the actual reset work through setTimeout. 418 setTimeout(function () { 419 $.each(data.attributes, function () {
420 // Without setTimeout() we would get the input values that are not reset yet. 421 this.value = getValue($form, this); 422 this.status = 0; 423 var $container = $form.find(this.container); 424 $container.removeClass( 425 data.settings.validatingCssClass + ' ' + 426 data.settings.errorCssClass + ' ' + 427 data.settings.successCssClass 428 ); 429 $container.find(this.error).html(''); 430 }); 431 $form.find(data.settings.errorSummary).hide().find('ul').html(''); 432 }, 1); 433 }, 434 435 /** 436 * Updates error messages, input containers, and optionally summary as well. 437 * If an attribute is missing from messages, it is considered valid. 438 * @param messages array the validation error messages, indexed by attribute IDs 439 * @param summary whether to update summary as well. 440 */ 441 updateMessages: function (messages, summary) { 442 var $form = $(this); 443 var data = $form.data('yiiActiveForm'); 444 $.each(data.attributes, function () { 445 updateInput($form, this, messages); 446 }); 447 if (summary) { 448 updateSummary($form, messages); 449 } 450 }, 451 452 /** 453 * Updates error messages and input container of a single attribute. 454 * If messages is empty, the attribute is considered valid. 455 * @param id attribute ID 456 * @param messages array with error messages 457 */ 458 updateAttribute: function(id, messages) { 459 var attribute = methods.find.call(this, id); 460 if (attribute != undefined) { 461 var msg = {}; 462 msg[id] = messages; 463 updateInput($(this), attribute, msg); 464 } 465 } 466 467 }; 468 469 var watchAttribute = function ($form, attribute) { 470 var $input = findInput($form, attribute); 471 if (attribute.validateOnChange) { 472 $input.on('change.yiiActiveForm', function () { 473 validateAttribute($form, attribute, false); 474 }); 475 } 476 if (attribute.validateOnBlur) { 477 $input.on('blur.yiiActiveForm', function () { 478 if (attribute.status == 0 || attribute.status == 1) { 479 validateAttribute($form, attribute, true); 480 } 481 }); 482 } 483 if (attribute.validateOnType) { 484 $input.on('keyup.yiiActiveForm', function (e) { 485 if ($.inArray(e.which, [16, 17, 18, 37, 38, 39, 40]) !== -1 ) { 486 return; 487 } 488 if (attribute.value !== getValue($form, attribute)) { 489 validateAttribute($form, attribute, false, attribute.validationDelay); 490 } 491 }); 492 } 493 }; 494 495 var unwatchAttribute = function ($form, attribute) { 496 findInput($form, attribute).off('.yiiActiveForm'); 497 }; 498 499 var validateAttribute = function ($form, attribute, forceValidate, validationDelay) { 500 var data = $form.data('yiiActiveForm'); 501 502 if (forceValidate) { 503 attribute.status = 2; 504 } 505 $.each(data.attributes, function () { 506 if (this.value !== getValue($form, this)) { 507 this.status = 2; 508 forceValidate = true; 509 } 510 }); 511 if (!forceValidate) { 512 return; 513 } 514 515 if (data.settings.timer !== undefined) { 516 clearTimeout(data.settings.timer); 517 } 518 data.settings.timer = setTimeout(function () { 519 if (data.submitting || $form.is(':hidden')) { 520 return; 521 } 522 $.each(data.attributes, function () { 523 if (this.status === 2) { 524 this.status = 3; 525 $form.find(this.container).addClass(data.settings.validatingCssClass); 526 } 527 }); 528 methods.validate.call($form); 529 }, validationDelay ? validationDelay : 200); 530 }; 531 532 /** 533 * Returns an array prototype with a shortcut method for adding a new deferred. 534 * The context of the callback will be the deferred object so it can be resolved like ```this.resolve()``` 535 * @returns Array 536 */ 537 var deferredArray = function () { 538 var array = []; 539 array.add = function(callback) { 540 this.push(new $.Deferred(callback)); 541 }; 542 return array; 543 }; 544 545 /** 546 * Updates the error messages and the input containers for all applicable attributes 547 * @param $form the form jQuery object 548 * @param messages array the validation error messages 549 * @param submitting whether this method is called after validation triggered by form submission 550 */ 551 var updateInputs = function ($form, messages, submitting) { 552 var data = $form.data('yiiActiveForm'); 553 554 if (submitting) { 555 var errorAttributes = []; 556 $.each(data.attributes, function () { 557 if (!$(this.input).is(":disabled") && !this.cancelled && updateInput($form, this, messages)) { 558 errorAttributes.push(this); 559 } 560 }); 561 562 $form.trigger(events.afterValidate, [messages, errorAttributes]); 563 564 updateSummary($form, messages); 565 566 if (errorAttributes.length) { 567 if (data.settings.scrollToError) { 568 var top = $form.find($.map(errorAttributes, function(attribute) { 569 return attribute.input; 570 }).join(',')).first().closest(':visible').offset().top; 571 var wtop = $(window).scrollTop(); 572 if (top < wtop || top > wtop + $(window).height()) { 573 $(window).scrollTop(top); 574 } 575 } 576 data.submitting = false;
577 } else { 578 data.validated = true; 579 var buttonTarget = data.submitObject ? data.submitObject.attr('formtarget') : null; 580 if (buttonTarget) { 581 // set target attribute to form tag before submit 582 $form.attr('target', buttonTarget); 583 } 584 $form.submit(); 585 // restore original target attribute value 586 $form.attr('target', data.target); 587 } 588 } else { 589 $.each(data.attributes, function () { 590 if (!this.cancelled && (this.status === 2 || this.status === 3)) { 591 updateInput($form, this, messages); 592 } 593 }); 594 } 595 submitFinalize($form); 596 }; 597 598 /** 599 * Updates hidden field that represents clicked submit button. 600 * @param $form the form jQuery object. 601 */ 602 var updateHiddenButton = function ($form) { 603 var data = $form.data('yiiActiveForm'); 604 var $button = data.submitObject || $form.find(':submit:first'); 605 // TODO: if the submission is caused by "change" event, it will not work 606 if ($button.length && $button.attr('type') == 'submit' && $button.attr('name')) { 607 // simulate button input value 608 var $hiddenButton = $('input[type="hidden"][name="' + $button.attr('name') + '"]', $form); 609 if (!$hiddenButton.length) { 610 $('<input>').attr({ 611 type: 'hidden', 612 name: $button.attr('name'), 613 value: $button.attr('value') 614 }).appendTo($form); 615 } else { 616 $hiddenButton.attr('value', $button.attr('value')); 617 } 618 } 619 }; 620 621 /** 622 * Updates the error message and the input container for a particular attribute. 623 * @param $form the form jQuery object 624 * @param attribute object the configuration for a particular attribute. 625 * @param messages array the validation error messages 626 * @return boolean whether there is a validation error for the specified attribute 627 */ 628 var updateInput = function ($form, attribute, messages) { 629 var data = $form.data('yiiActiveForm'), 630 $input = findInput($form, attribute), 631 hasError = false; 632 633 if (!$.isArray(messages[attribute.id])) { 634 messages[attribute.id] = []; 635 } 636 $form.trigger(events.afterValidateAttribute, [attribute, messages[attribute.id]]); 637 638 attribute.status = 1; 639 if ($input.length) { 640 hasError = messages[attribute.id].length > 0; 641 var $container = $form.find(attribute.container); 642 var $error = $container.find(attribute.error); 643 if (hasError) { 644 if (attribute.encodeError) { 645 $error.text(messages[attribute.id][0]); 646 } else { 647 $error.html(messages[attribute.id][0]); 648 } 649 $container.removeClass(data.settings.validatingCssClass + ' ' + data.settings.successCssClass) 650 .addClass(data.settings.errorCssClass); 651 } else { 652 $error.empty(); 653 $container.removeClass(data.settings.validatingCssClass + ' ' + data.settings.errorCssClass + ' ') 654 .addClass(data.settings.successCssClass); 655 } 656 attribute.value = getValue($form, attribute); 657 } 658 return hasError; 659 }; 660 661 /** 662 * Updates the error summary. 663 * @param $form the form jQuery object 664 * @param messages array the validation error messages 665 */ 666 var updateSummary = function ($form, messages) { 667 var data = $form.data('yiiActiveForm'), 668 $summary = $form.find(data.settings.errorSummary), 669 $ul = $summary.find('ul').empty(); 670 671 if ($summary.length && messages) { 672 $.each(data.attributes, function () { 673 if ($.isArray(messages[this.id]) && messages[this.id].length) { 674 var error = $('<li/>'); 675 if (data.settings.encodeErrorSummary) { 676 error.text(messages[this.id][0]); 677 } else { 678 error.html(messages[this.id][0]); 679 } 680 $ul.append(error); 681 } 682 }); 683 $summary.toggle($ul.find('li').length > 0); 684 } 685 }; 686 687 var getValue = function ($form, attribute) { 688 var $input = findInput($form, attribute); 689 var type = $input.attr('type'); 690 if (type === 'checkbox' || type === 'radio') { 691 var $realInput = $input.filter(':checked'); 692 if (!$realInput.length) { 693 $realInput = $form.find('input[type=hidden][name="' + $input.attr('name') + '"]'); 694 } 695 return $realInput.val(); 696 } else { 697 return $input.val(); 698 } 699 }; 700 701 var findInput = function ($form, attribute) { 702 var $input = $form.find(attribute.input); 703 if ($input.length && $input[0].tagName.toLowerCase() === 'div') { 704 // checkbox list or radio list 705 return $input.find('input'); 706 } else { 707 return $input; 708 } 709 }; 710 711})(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.