mirror of
				https://github.com/yiisoft/yii2.git
				synced 2025-10-31 10:39:59 +08:00 
			
		
		
		
	Fixes #4955: Replaced callbacks with events for ActiveForm
				
					
				
			This commit is contained in:
		| @ -22,6 +22,66 @@ | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     var events = { | ||||
|         /** | ||||
|          * beforeValidate event is triggered before validating the whole form and each attribute. | ||||
|          * The signature of the event handler should be: | ||||
|          *     function (event, messages, deferreds, attribute) | ||||
|          * where | ||||
|          *  - event: an Event object. You can set event.isValid to be false to stop validating the form or attribute | ||||
|          *  - messages: error messages. When attribute is undefined, this parameter is an associative array | ||||
|          *    with keys being attribute IDs and values being error messages for the corresponding attributes. | ||||
|          *    When attribute is given, this parameter is an array of the error messages for that attribute. | ||||
|          *  - deferreds: an array of Deferred objects. You can use deferreds.add(callback) to add a new deferred validation. | ||||
|          *  - attribute: an attribute object. Please refer to attributeDefaults for the structure. | ||||
|          *    If this is undefined, it means the event is triggered before validating the whole form. | ||||
|          *    Otherwise it means the event is triggered before validating the specified attribute. | ||||
|          */ | ||||
|         beforeValidate: 'beforeValidate', | ||||
|         /** | ||||
|          * afterValidate event is triggered after validating the whole form and each attribute. | ||||
|          * The signature of the event handler should be: | ||||
|          *     function (event, messages, attribute) | ||||
|          * where | ||||
|          *  - event: an Event object. | ||||
|          *  - messages: error messages. When attribute is undefined, this parameter is an associative array | ||||
|          *    with keys being attribute IDs and values being error messages for the corresponding attributes. | ||||
|          *    When attribute is given, this parameter is an array of the error messages for that attribute. | ||||
|          *    If the array length is greater than 0, it means the attribute has validation errors. | ||||
|          *  - attribute: an attribute object. Please refer to attributeDefaults for the structure. | ||||
|          *    If this is undefined, it means the event is triggered before validating the whole form. | ||||
|          *    Otherwise it means the event is triggered before validating the specified attribute. | ||||
|          */ | ||||
|         afterValidate: 'afterValidate', | ||||
|         /** | ||||
|          * beforeSubmit event is triggered before submitting the form (after all validations pass). | ||||
|          * The signature of the event handler should be: | ||||
|          *     function (event) | ||||
|          * where event is an Event object. | ||||
|          */ | ||||
|         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 | ||||
|          *  - settings: 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 | ||||
| @ -41,28 +101,7 @@ | ||||
|         // 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, | ||||
|         // a callback that is called before submitting the form. The signature of the callback should be: | ||||
|         // function ($form) { ...return false to cancel submission...} | ||||
|         beforeSubmit: undefined, | ||||
|         // a callback that is called before validating each attribute. The signature of the callback should be: | ||||
|         // function ($form, attribute, messages) { ...return false to cancel the validation...} | ||||
|         beforeValidate: undefined, | ||||
|         // a callback that is called before validation starts (This callback is only called when the form is submitted). This signature of the callback should be: | ||||
|         // function($form, data) { ...return false to cancel the validation...} | ||||
|         beforeValidateAll: undefined, | ||||
|         // a callback that is called after an attribute is validated. The signature of the callback should be: | ||||
|         // function ($form, attribute, messages) | ||||
|         afterValidate: undefined, | ||||
|         // a callback that is called after all validation has run (This callback is only called when the form is submitted). The signature of the callback should be: | ||||
|         // function ($form, data, messages) | ||||
|         afterValidateAll: undefined, | ||||
|         // a pre-request callback function on AJAX-based validation. The signature of the callback should be: | ||||
|         // function ($form, jqXHR, textStatus) | ||||
|         ajaxBeforeSend: undefined, | ||||
|         // a function to be called when the request finishes on AJAX-based validation. The signature of the callback should be: | ||||
|         // function ($form, jqXHR, textStatus) | ||||
|         ajaxComplete: undefined | ||||
|         validationUrl: undefined | ||||
|     }; | ||||
|  | ||||
|     // NOTE: If you change any of these defaults, make sure you update yii\widgets\ActiveField::getClientOptions() as well | ||||
| @ -151,7 +190,7 @@ | ||||
|             var $form = $(this), | ||||
|                 attributes = $form.data('yiiActiveForm').attributes, | ||||
|                 index = -1, | ||||
|                 attribute; | ||||
|                 attribute = undefined; | ||||
|             $.each(attributes, function (i) { | ||||
|                 if (attributes[i]['id'] == id) { | ||||
|                     index = i; | ||||
| @ -168,7 +207,8 @@ | ||||
|  | ||||
|         // find an attribute config based on the specified attribute ID | ||||
|         find: function (id) { | ||||
|             var attributes = $(this).data('yiiActiveForm').attributes, result; | ||||
|             var attributes = $(this).data('yiiActiveForm').attributes, | ||||
|                 result = undefined; | ||||
|             $.each(attributes, function (i) { | ||||
|                 if (attributes[i]['id'] == id) { | ||||
|                     result = attributes[i]; | ||||
| @ -189,66 +229,120 @@ | ||||
|             return this.data('yiiActiveForm'); | ||||
|         }, | ||||
|  | ||||
|         validate: function () { | ||||
|             var $form = $(this), | ||||
|                 data = $form.data('yiiActiveForm'), | ||||
|                 needAjaxValidation = false, | ||||
|                 messages = {}, | ||||
|                 deferreds = deferredArray(); | ||||
|  | ||||
|             if (data.submitting) { | ||||
|                 var event = $.Event(events.beforeValidate, {'isValid': true}); | ||||
|                 $form.trigger(event, [messages, deferreds]); | ||||
|                 if (!event.isValid) { | ||||
|                     data.submitting = false; | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // client-side validation | ||||
|             $.each(data.attributes, function () { | ||||
|                 // 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.beforeValidate, {'isValid': true}); | ||||
|                     $form.trigger(event, [msg, deferreds, this]); | ||||
|                     if (event.isValid) { | ||||
|                         if (this.validate) { | ||||
|                             this.validate(this, getValue($form, this), msg, deferreds); | ||||
|                         } | ||||
|                         if (this.enableAjaxValidation) { | ||||
|                             needAjaxValidation = 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 && (!data.submitting || $.isEmptyObject(messages))) { | ||||
|                     // Perform ajax validation when at least one input needs it. | ||||
|                     // If the validation is triggered by form submission, ajax validation | ||||
|                     // should be done only when all inputs pass client validation | ||||
|                     var $button = data.submitObject, | ||||
|                         extData = '&' + data.settings.ajaxParam + '=' + $form.prop('id'); | ||||
|                     if ($button && $button.length && $button.prop('name')) { | ||||
|                         extData += '&' + $button.prop('name') + '=' + $button.prop('value'); | ||||
|                     } | ||||
|                     $.ajax({ | ||||
|                         url: data.settings.validationUrl, | ||||
|                         type: $form.prop('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) { | ||||
|                                         delete msgs[this.id]; | ||||
|                                     } | ||||
|                                 }); | ||||
|                                 updateInputs($form, $.extend(messages, msgs)); | ||||
|                             } else { | ||||
|                                 updateInputs($form, messages); | ||||
|                             } | ||||
|                         }, | ||||
|                         error: function () { | ||||
|                             data.submitting = false; | ||||
|                         } | ||||
|                     }); | ||||
|                 } else if (data.submitting) { | ||||
|                     // delay callback so that the form can be submitted without problem | ||||
|                     setTimeout(function () { | ||||
|                         updateInputs($form, messages); | ||||
|                     }, 200); | ||||
|                 } else { | ||||
|                     updateInputs($form, messages); | ||||
|                 } | ||||
|             }); | ||||
|         }, | ||||
|  | ||||
|         submitForm: function () { | ||||
|             var $form = $(this), | ||||
|                 data = $form.data('yiiActiveForm'); | ||||
|             if (data.validated) { | ||||
|                 if (data.settings.beforeSubmit !== undefined) { | ||||
|                     if (data.settings.beforeSubmit($form) == false) { | ||||
|                         data.validated = false; | ||||
|                         data.submitting = false; | ||||
|                         return false; | ||||
|                     } | ||||
|                 } | ||||
|                 // continue submitting the form since validation passes | ||||
|                 return true; | ||||
|             } | ||||
|  | ||||
|             if (data.settings.timer !== undefined) { | ||||
|                 clearTimeout(data.settings.timer); | ||||
|             } | ||||
|             data.submitting = true; | ||||
|              | ||||
|             if (data.settings.beforeValidateAll && !data.settings.beforeValidateAll($form, data)) { | ||||
|                 data.submitting = false; | ||||
|             if (data.validated) { | ||||
|                 var event = $.Event(events.beforeSubmit, {'isValid': true}); | ||||
|                 $form.trigger(event, [$form]); | ||||
|                 if (!event.isValid) { | ||||
|                     data.validated = false; | ||||
|                     data.submitting = false; | ||||
|                     return false; | ||||
|                 } | ||||
|                 return true;   // continue submitting the form since validation passes | ||||
|             } else { | ||||
|                 if (data.settings.timer !== undefined) { | ||||
|                     clearTimeout(data.settings.timer); | ||||
|                 } | ||||
|                 data.submitting = true; | ||||
|                 methods.validate.call($form); | ||||
|                 return false; | ||||
|             } | ||||
|             validate($form, function (messages) { | ||||
|                 var errors = []; | ||||
|                 $.each(data.attributes, function () { | ||||
|                     if (updateInput($form, this, messages)) { | ||||
|                         errors.push(this.input); | ||||
|                     } | ||||
|                 }); | ||||
|                  | ||||
|                 if (data.settings.afterValidateAll) { | ||||
|                     data.settings.afterValidateAll($form, data, messages); | ||||
|                 } | ||||
|                  | ||||
|                 updateSummary($form, messages); | ||||
|                 if (errors.length) { | ||||
|                     var top = $form.find(errors.join(',')).first().offset().top; | ||||
|                     var wtop = $(window).scrollTop(); | ||||
|                     if (top < wtop || top > wtop + $(window).height) { | ||||
|                         $(window).scrollTop(top); | ||||
|                     } | ||||
|                 } 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.click(); | ||||
|                     } else { | ||||
|                         // no submit button in the form | ||||
|                         $form.submit(); | ||||
|                     } | ||||
|                     return; | ||||
|                 } | ||||
|                 data.submitting = false; | ||||
|             }, function () { | ||||
|                 data.submitting = false; | ||||
|             }); | ||||
|             return false; | ||||
|         }, | ||||
|  | ||||
|         resetForm: function () { | ||||
| @ -275,31 +369,6 @@ | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     var watchAttributes = function ($form, attributes) { | ||||
|         $.each(attributes, function (i, 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 () { | ||||
|                     if (attribute.value !== getValue($form, attribute)) { | ||||
|                         validateAttribute($form, attribute, false); | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|     }; | ||||
|  | ||||
|     var watchAttribute = function ($form, attribute) { | ||||
|         var $input = findInput($form, attribute); | ||||
|         if (attribute.validateOnChange) { | ||||
| @ -356,14 +425,7 @@ | ||||
|                     $form.find(this.container).addClass(data.settings.validatingCssClass); | ||||
|                 } | ||||
|             }); | ||||
|             validate($form, function (messages) { | ||||
|                 var hasError = false; | ||||
|                 $.each(data.attributes, function () { | ||||
|                     if (this.status === 2 || this.status === 3) { | ||||
|                         hasError = updateInput($form, this, messages) || hasError; | ||||
|                     } | ||||
|                 }); | ||||
|             }); | ||||
|             methods.validate.call($form); | ||||
|         }, data.settings.validationDelay); | ||||
|     }; | ||||
|      | ||||
| @ -379,88 +441,52 @@ | ||||
|         }; | ||||
|         return array; | ||||
|     }; | ||||
|      | ||||
|  | ||||
|     /** | ||||
|      * Performs validation. | ||||
|      * @param $form jQuery the jquery representation of the form | ||||
|      * @param successCallback function the function to be invoked if the validation completes | ||||
|      * @param errorCallback function the function to be invoked if the ajax validation request fails | ||||
|      * 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 | ||||
|      */ | ||||
|     var validate = function ($form, successCallback, errorCallback) { | ||||
|         var data = $form.data('yiiActiveForm'), | ||||
|             needAjaxValidation = false, | ||||
|             messages = {}, | ||||
|             deferreds = deferredArray(); | ||||
|     var updateInputs = function ($form, messages) { | ||||
|         var data = $form.data('yiiActiveForm'); | ||||
|  | ||||
|         $.each(data.attributes, function () { | ||||
|             if (data.submitting || this.status === 2 || this.status === 3) { | ||||
|                 var msg = []; | ||||
|                 messages[this.id] = msg; | ||||
|                 if (!data.settings.beforeValidate || data.settings.beforeValidate($form, this, msg)) { | ||||
|                     if (this.validate) { | ||||
|                         this.validate(this, getValue($form, this), msg, deferreds); | ||||
|                     } | ||||
|                     if (this.enableAjaxValidation) { | ||||
|                         needAjaxValidation = true; | ||||
|                     } | ||||
|         if (data.submitting) { | ||||
|             var errorInputs = []; | ||||
|             $.each(data.attributes, function () { | ||||
|                 if (updateInput($form, this, messages)) { | ||||
|                     errorInputs.push(this.input); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|             }); | ||||
|  | ||||
|         $.when.apply(this, deferreds).always(function() { | ||||
|             //Remove empty message arrays | ||||
|             for (var i in messages) { | ||||
|                 if (0 === messages[i].length) { | ||||
|                     delete messages[i]; | ||||
|             $form.trigger(events.afterValidate, [messages]); | ||||
|  | ||||
|             updateSummary($form, messages); | ||||
|  | ||||
|             if (errorInputs.length) { | ||||
|                 var top = $form.find(errorInputs.join(',')).first().offset().top; | ||||
|                 var wtop = $(window).scrollTop(); | ||||
|                 if (top < wtop || top > wtop + $(window).height) { | ||||
|                     $(window).scrollTop(top); | ||||
|                 } | ||||
|             } | ||||
|             if (needAjaxValidation && (!data.submitting || $.isEmptyObject(messages))) { | ||||
|                 // Perform ajax validation when at least one input needs it. | ||||
|                 // If the validation is triggered by form submission, ajax validation | ||||
|                 // should be done only when all inputs pass client validation | ||||
|                 var $button = data.submitObject, | ||||
|                     extData = '&' + data.settings.ajaxParam + '=' + $form.prop('id'); | ||||
|                 if ($button && $button.length && $button.prop('name')) { | ||||
|                     extData += '&' + $button.prop('name') + '=' + $button.prop('value'); | ||||
|                 } | ||||
|                 $.ajax({ | ||||
|                     url: data.settings.validationUrl, | ||||
|                     type: $form.prop('method'), | ||||
|                     data: $form.serialize() + extData, | ||||
|                     dataType: data.settings.ajaxDataType, | ||||
|                     complete: function (jqXHR, textStatus) { | ||||
|                         if (data.settings.ajaxComplete) { | ||||
|                             data.settings.ajaxComplete($form, jqXHR, textStatus); | ||||
|                         } | ||||
|                     }, | ||||
|                     beforeSend: function (jqXHR, textStatus) { | ||||
|                         if (data.settings.ajaxBeforeSend) { | ||||
|                             data.settings.ajaxBeforeSend($form, jqXHR, textStatus); | ||||
|                         } | ||||
|                     }, | ||||
|                     success: function (msgs) { | ||||
|                         if (msgs !== null && typeof msgs === 'object') { | ||||
|                             $.each(data.attributes, function () { | ||||
|                                 if (!this.enableAjaxValidation) { | ||||
|                                     delete msgs[this.id]; | ||||
|                                 } | ||||
|                             }); | ||||
|                             successCallback($.extend({}, messages, msgs)); | ||||
|                         } else { | ||||
|                             successCallback(messages); | ||||
|                         } | ||||
|                     }, | ||||
|                     error: errorCallback | ||||
|                 }); | ||||
|             } else if (data.submitting) { | ||||
|                 // delay callback so that the form can be submitted without problem | ||||
|                 setTimeout(function () { | ||||
|                     successCallback(messages); | ||||
|                 }, 200); | ||||
|                 data.submitting = false; | ||||
|             } else { | ||||
|                 successCallback(messages); | ||||
|                 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.click(); | ||||
|                 } else { | ||||
|                     // no submit button in the form | ||||
|                     $form.submit(); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|         } else { | ||||
|             $.each(data.attributes, function () { | ||||
|                 if (this.status === 2 || this.status === 3) { | ||||
|                     updateInput($form, this, messages); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
| @ -475,12 +501,14 @@ | ||||
|             $input = findInput($form, attribute), | ||||
|             hasError = false; | ||||
|  | ||||
|         if (data.settings.afterValidate) { | ||||
|             data.settings.afterValidate($form, attribute, messages); | ||||
|         if (!$.isArray(messages[attribute.id])) { | ||||
|             messages[attribute.id] = []; | ||||
|         } | ||||
|         $form.trigger(events.afterValidate, [messages[attribute.id], attribute]); | ||||
|  | ||||
|         attribute.status = 1; | ||||
|         if ($input.length) { | ||||
|             hasError = messages && $.isArray(messages[attribute.id]) && messages[attribute.id].length; | ||||
|             hasError = messages[attribute.id].length > 0; | ||||
|             var $container = $form.find(attribute.container); | ||||
|             var $error = $container.find(attribute.error); | ||||
|             if (hasError) { | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Qiang Xue
					Qiang Xue