]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blobdiff - resources/src/mediawiki/htmlform/htmlform.Checker.js
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / resources / src / mediawiki / htmlform / htmlform.Checker.js
diff --git a/resources/src/mediawiki/htmlform/htmlform.Checker.js b/resources/src/mediawiki/htmlform/htmlform.Checker.js
new file mode 100644 (file)
index 0000000..3f53b63
--- /dev/null
@@ -0,0 +1,180 @@
+( function ( mw, $ ) {
+
+       mw.htmlform = {};
+
+       /**
+        * @class mw.htmlform.Checker
+        */
+
+       /**
+        * A helper class to add validation to non-OOUI HtmlForm fields.
+        *
+        * @constructor
+        * @param {jQuery} $element Form field generated by HTMLForm
+        * @param {Function} validator Validation callback
+        * @param {string} validator.value Value of the form field to be validated
+        * @param {jQuery.Promise} validator.return The promise should be resolved
+        *  with an object with two properties: Boolean 'valid' to indicate success
+        *  or failure of validation, and an array 'messages' to be passed to
+        *  setErrors() on failure.
+        */
+       mw.htmlform.Checker = function ( $element, validator ) {
+               this.validator = validator;
+               this.$element = $element;
+
+               this.$errorBox = $element.next( '.error' );
+               if ( !this.$errorBox.length ) {
+                       this.$errorBox = $( '<span>' );
+                       this.$errorBox.hide();
+                       $element.after( this.$errorBox );
+               }
+
+               this.currentValue = this.$element.val();
+       };
+
+       /**
+        * Attach validation events to the form element
+        *
+        * @param {jQuery} [$extraElements] Additional elements to listen for change
+        *  events on.
+        * @return {mw.htmlform.Checker}
+        * @chainable
+        */
+       mw.htmlform.Checker.prototype.attach = function ( $extraElements ) {
+               var $e,
+                       // We need to hook to all of these events to be sure we are
+                       // notified of all changes to the value of an <input type=text>
+                       // field.
+                       events = 'keyup keydown change mouseup cut paste focus blur';
+
+               $e = this.$element;
+               if ( $extraElements ) {
+                       $e = $e.add( $extraElements );
+               }
+               $e.on( events, $.debounce( 1000, this.validate.bind( this ) ) );
+
+               return this;
+       };
+
+       /**
+        * Validate the form element
+        * @return {jQuery.Promise}
+        */
+       mw.htmlform.Checker.prototype.validate = function () {
+               var currentRequestInternal,
+                       that = this,
+                       value = this.$element.val();
+
+               // Abort any pending requests.
+               if ( this.currentRequest && this.currentRequest.abort ) {
+                       this.currentRequest.abort();
+               }
+
+               if ( value === '' ) {
+                       this.currentValue = value;
+                       this.setErrors( [] );
+                       return;
+               }
+
+               this.currentRequest = currentRequestInternal = this.validator( value )
+                       .done( function ( info ) {
+                               var forceReplacement = value !== that.currentValue;
+
+                               // Another request was fired in the meantime, the result we got here is no longer current.
+                               // This shouldn't happen as we abort pending requests, but you never know.
+                               if ( that.currentRequest !== currentRequestInternal ) {
+                                       return;
+                               }
+                               // If we're here, then the current request has finished, avoid calling .abort() needlessly.
+                               that.currentRequest = undefined;
+
+                               that.currentValue = value;
+
+                               if ( info.valid ) {
+                                       that.setErrors( [], forceReplacement );
+                               } else {
+                                       that.setErrors( info.messages, forceReplacement );
+                               }
+                       } ).fail( function () {
+                               that.currentValue = null;
+                               that.setErrors( [] );
+                       } );
+
+               return currentRequestInternal;
+       };
+
+       /**
+        * Display errors associated with the form element
+        * @param {Array} errors Error messages. Each error message will be appended to a
+        *  `<span>` or `<li>`, as with jQuery.append().
+        * @param {boolean} [forceReplacement] Set true to force a visual replacement even
+        *  if the errors are the same. Ignored if errors are empty.
+        * @return {mw.htmlform.Checker}
+        * @chainable
+        */
+       mw.htmlform.Checker.prototype.setErrors = function ( errors, forceReplacement ) {
+               var $oldErrorBox, tagName, showFunc, text, replace,
+                       $errorBox = this.$errorBox;
+
+               if ( errors.length === 0 ) {
+                       $errorBox.slideUp( function () {
+                               $errorBox
+                                       .removeAttr( 'class' )
+                                       .empty();
+                       } );
+               } else {
+                       // Match behavior of HTMLFormField::formatErrors(), <span> or <ul>
+                       // depending on the count.
+                       tagName = errors.length === 1 ? 'span' : 'ul';
+
+                       // We have to animate the replacement if we're changing the tag. We
+                       // also want to if told to by the caller (i.e. to make it visually
+                       // obvious that the changed field value gives the same error) or if
+                       // the error text changes (because it makes more sense than
+                       // changing the text with no animation).
+                       replace = (
+                               forceReplacement || $errorBox.length > 1 ||
+                               $errorBox[ 0 ].tagName.toLowerCase() !== tagName
+                       );
+                       if ( !replace ) {
+                               text = $( '<' + tagName + '>' )
+                                       .append( errors.map( function ( e ) {
+                                               return errors.length === 1 ? e : $( '<li>' ).append( e );
+                                       } ) );
+                               if ( text.text() !== $errorBox.text() ) {
+                                       replace = true;
+                               }
+                       }
+
+                       $oldErrorBox = $errorBox;
+                       if ( replace ) {
+                               this.$errorBox = $errorBox = $( '<' + tagName + '>' );
+                               $errorBox.hide();
+                               $oldErrorBox.after( this.$errorBox );
+                       }
+
+                       showFunc = function () {
+                               if ( $oldErrorBox !== $errorBox ) {
+                                       $oldErrorBox
+                                               .removeAttr( 'class' )
+                                               .detach();
+                               }
+                               $errorBox
+                                       .attr( 'class', 'error' )
+                                       .empty()
+                                       .append( errors.map( function ( e ) {
+                                               return errors.length === 1 ? e : $( '<li>' ).append( e );
+                                       } ) )
+                                       .slideDown();
+                       };
+                       if ( $oldErrorBox !== $errorBox && $oldErrorBox.hasClass( 'error' ) ) {
+                               $oldErrorBox.slideUp( showFunc );
+                       } else {
+                               showFunc();
+                       }
+               }
+
+               return this;
+       };
+
+}( mediaWiki, jQuery ) );