r89584 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r89583‎ | r89584 | r89585 >
Date:17:37, 6 June 2011
Author:salvatoreingala
Status:deferred (Comments)
Tags:
Comment:
- Added preferences of type "number"
- Improved validation
- Minor bugfixes
Modified paths:
  • /branches/salvatoreingala/Gadgets/Gadgets.i18n.php (modified) (history)
  • /branches/salvatoreingala/Gadgets/Gadgets.php (modified) (history)
  • /branches/salvatoreingala/Gadgets/Gadgets_body.php (modified) (history)
  • /branches/salvatoreingala/Gadgets/modules/jquery.formBuilder.js (modified) (history)

Diff [purge]

Index: branches/salvatoreingala/Gadgets/Gadgets_body.php
@@ -252,25 +252,52 @@
253253 ),
254254 'required' => array(
255255 'isMandatory' => false,
256 - 'checker' => 'is_bool'
 256+ 'checker' => 'is_bool'
257257 ),
258258 'minlength' => array(
259259 'isMandatory' => false,
260 - 'checker' => 'is_integer'
 260+ 'checker' => 'is_integer'
261261 ),
262262 'maxlength' => array(
263263 'isMandatory' => false,
264 - 'checker' => 'is_integer'
 264+ 'checker' => 'is_integer'
265265 )
266 -
 266+ ),
 267+ 'number' => array(
 268+ 'default' => array(
 269+ 'isMandatory' => true,
 270+ 'checker' => array( 'Gadget', 'isFloatOrInt' )
 271+ ),
 272+ 'label' => array(
 273+ 'isMandatory' => true,
 274+ 'checker' => 'is_string'
 275+ ),
 276+ 'required' => array(
 277+ 'isMandatory' => false,
 278+ 'checker' => 'is_bool'
 279+ ),
 280+ 'integer' => array(
 281+ 'isMandatory' => false,
 282+ 'checker' => 'is_bool'
 283+ ),
 284+ 'min' => array(
 285+ 'isMandatory' => false,
 286+ 'checker' => array( 'Gadget', 'isFloatOrInt' )
 287+ ),
 288+ 'max' => array(
 289+ 'isMandatory' => false,
 290+ 'checker' => array( 'Gadget', 'isFloatOrInt' )
 291+ )
267292 )
268293 );
269294
270295 //Type-specific checkers for finer validation
271296 private static $typeCheckers = array(
272 - 'string' => array( 'Gadget', 'checkStringOption' )
 297+ 'string' => array( 'Gadget', 'checkStringOption' ),
 298+ 'number' => array( 'Gadget', 'checkNumberOption' )
273299 );
274 -
 300+
 301+ //Further checks for 'string' options
275302 private static function checkStringOption( $option ) {
276303 if ( isset( $option['minlength'] ) && $option['minlength'] < 0 ) {
277304 return false;
@@ -286,9 +313,57 @@
287314 }
288315 }
289316
 317+ //$default must pass validation, too
 318+ $required = isset( $option['required'] ) ? $option['required'] : true;
 319+ $default = $option['default'];
 320+
 321+ if ( $required === false && $default === '' ) {
 322+ return true; //empty string is good, skip other checks
 323+ }
 324+
 325+ $len = strlen( $default );
 326+ if ( isset( $option['minlength'] ) && $len < $option['minlength'] ) {
 327+ return false;
 328+ }
 329+ if ( isset( $option['maxlength'] ) && $len > $option['maxlength'] ) {
 330+ return false;
 331+ }
 332+
290333 return true;
291334 }
292335
 336+ private static function isFloatOrInt( $param ) {
 337+ return is_float( $param ) || is_int( $param );
 338+ }
 339+
 340+ //Further checks for 'number' options
 341+ private static function checkNumberOption( $option ) {
 342+ if ( isset( $option['integer'] ) && $option['integer'] === true ) {
 343+ //Check if 'min', 'max' and 'default' are integers (if given)
 344+ if ( intval( $option['default'] ) != $option['default'] ) {
 345+ return false;
 346+ }
 347+ if ( isset( $option['min'] ) && intval( $option['min'] ) != $option['min'] ) {
 348+ return false;
 349+ }
 350+ if ( isset( $option['max'] ) && intval( $option['max'] ) != $option['max'] ) {
 351+ return false;
 352+ }
 353+ }
 354+
 355+ //validate $option['default']
 356+ $default = $option['default'];
 357+
 358+ if ( isset( $option['min'] ) && $default < $option['min'] ) {
 359+ return false;
 360+ }
 361+ if ( isset( $option['max'] ) && $default > $option['max'] ) {
 362+ return false;
 363+ }
 364+
 365+ return true;
 366+ }
 367+
293368 /**
294369 * Creates an instance of this class from definition in MediaWiki:Gadgets-definition
295370 * @param $definition String: Gadget definition
@@ -672,7 +747,7 @@
673748
674749 $checker = $typeSpec[$fieldName]['checker'];
675750
676 - if ( !$checker( $fieldValue ) ) {
 751+ if ( !call_user_func( $checker, $fieldValue ) ) {
677752 return false;
678753 }
679754 }
@@ -724,12 +799,17 @@
725800 }
726801
727802 //Check if a preference is valid, according to description
728 - //NOTE: $pref needs to be passed by reference to suppress warning on undefined
729 - private static function checkSinglePref( &$prefDescription, &$pref ) {
730 - if ( !isset( $pref ) ) {
 803+ //NOTE: we pass both $prefs and $prefName (instead of just $prefs[$prefName])
 804+ // to allow checking for null.
 805+ private static function checkSinglePref( &$prefDescription, &$prefs, $prefName ) {
 806+
 807+ //isset( $prefs[$prefName] ) would return false for null values
 808+ if ( !array_key_exists( $prefName, $prefs ) ) {
731809 return false;
732810 }
733 -
 811+
 812+ $pref = $prefs[$prefName];
 813+
734814 switch ( $prefDescription['type'] ) {
735815 case 'boolean':
736816 return is_bool( $pref );
@@ -750,17 +830,48 @@
751831
752832 //Checks the "minlength" option, if present
753833 $minlength = isset( $prefDescription['minlength'] ) ? $prefDescription['minlength'] : 0;
754 - if ( is_integer( $minlength ) && $len < $minlength ){
 834+ if ( $len < $minlength ){
755835 return false;
756836 }
757837
758 - //Checks the "minlength" option, if present
759 - $maxlength = isset( $prefDescription['maxlength'] ) ? $prefDescription['maxlength'] : 1000; //TODO: what big integer here?
760 - if ( is_integer( $maxlength ) && $len > $maxlength ){
 838+ //Checks the "maxlength" option, if present
 839+ $maxlength = isset( $prefDescription['maxlength'] ) ? $prefDescription['maxlength'] : 1024; //TODO: what big integer here?
 840+ if ( $len > $maxlength ){
761841 return false;
762842 }
763843
764844 return true;
 845+ case 'number':
 846+ if ( !is_float( $pref ) && !is_int( $pref ) && $pref !== null ) {
 847+ return false;
 848+ }
 849+
 850+ $required = isset( $prefDescription['required'] ) ? $prefDescription['required'] : true;
 851+ if ( $required === false && $pref === null ) {
 852+ return true;
 853+ }
 854+
 855+ $integer = isset( $prefDescription['integer'] ) ? $prefDescription['integer'] : false;
 856+
 857+ if ( $integer === true && intval( $pref ) != $pref ) {
 858+ return false; //not integer
 859+ }
 860+
 861+ if ( isset( $prefsDescription['min'] ) ) {
 862+ $min = $prefsDescription['min'];
 863+ if ( $pref < $min ) {
 864+ return false; //value below minimum
 865+ }
 866+ }
 867+
 868+ if ( isset( $prefsDescription['max'] ) ) {
 869+ $max = $prefsDescription['max'];
 870+ if ( $pref > $max ) {
 871+ return false; //value above maximum
 872+ }
 873+ }
 874+
 875+ return true;
765876 default:
766877 return false; //unexisting type
767878 }
@@ -769,7 +880,7 @@
770881 //Returns true if $prefs is an array of preferences that passes validation
771882 private static function checkPrefsAgainstDescription( &$prefsDescription, &$prefs ) {
772883 foreach ( $prefsDescription['fields'] as $prefName => $prefDescription ) {
773 - if ( !self::checkSinglePref( $prefDescription, $prefs[$prefName] ) ) {
 884+ if ( !self::checkSinglePref( $prefDescription, $prefs, $prefName ) ) {
774885 return false;
775886 }
776887 }
@@ -779,8 +890,16 @@
780891 //Fixes $prefs so that it matches the description given by $prefsDescription.
781892 //All values of $prefs that fail validation are replaced with default values.
782893 private static function matchPrefsWithDescription( &$prefsDescription, &$prefs ) {
 894+ //Remove unexisting preferences from $prefs
 895+ foreach ( $prefs as $prefName => $value ) {
 896+ if ( !isset( $prefsDescription['fields'][$prefName] ) ) {
 897+ unset( $prefs[$prefName] );
 898+ }
 899+ }
 900+
 901+ //Fix preferences that fail validation
783902 foreach ( $prefsDescription['fields'] as $prefName => $prefDescription ) {
784 - if ( !self::checkSinglePref( $prefDescription, $prefs[$prefName] ) ) {
 903+ if ( !self::checkSinglePref( $prefDescription, $prefs, $prefName ) ) {
785904 $prefs[$prefName] = $prefDescription['default'];
786905 }
787906 }
Index: branches/salvatoreingala/Gadgets/Gadgets.i18n.php
@@ -54,6 +54,9 @@
5555 'gadgets-formbuilder-required' => 'This field is required.',
5656 'gadgets-formbuilder-minlength' => 'Please enter at least $1 characters.',
5757 'gadgets-formbuilder-maxlength' => 'Please enter no more than $1 characters.',
 58+ 'gadgets-formbuilder-min' => 'Please enter a value not less than $1.',
 59+ 'gadgets-formbuilder-max' => 'Please enter a value not greater than $1.',
 60+ 'gadgets-formbuilder-integer' => 'Please enter an integer number.',
5861 );
5962
6063 /** Message documentation (Message documentation)
Index: branches/salvatoreingala/Gadgets/Gadgets.php
@@ -72,7 +72,10 @@
7373 $wgResourceModules['jquery.formBuilder'] = array(
7474 'scripts' => array( 'jquery.formBuilder.js' ),
7575 'dependencies' => array( 'jquery', 'jquery.validate' ),
76 - 'messages' => array( 'gadgets-formbuilder-required', 'gadgets-formbuilder-minlength', 'gadgets-formbuilder-maxlength' ),
 76+ 'messages' => array(
 77+ 'gadgets-formbuilder-required', 'gadgets-formbuilder-minlength', 'gadgets-formbuilder-maxlength',
 78+ 'gadgets-formbuilder-min', 'gadgets-formbuilder-max', 'gadgets-formbuilder-integer'
 79+ ),
7780 'localBasePath' => $dir . 'modules/',
7881 'remoteExtPath' => 'Gadgets/modules'
7982 );
Index: branches/salvatoreingala/Gadgets/modules/jquery.formBuilder.js
@@ -4,7 +4,6 @@
55 * Released under the MIT and GPL licenses.
66 */
77
8 -
98 (function($, mw) {
109
1110 var idPrefix = "mw-gadgets-dialog-";
@@ -18,6 +17,28 @@
1918 }
2019 }
2120
 21+
 22+ //validator for "required" fields (without trimming whitespaces)
 23+ $.validator.addMethod( "requiredStrict", function( value, element ) {
 24+ return value.length > 0;
 25+ }, mw.msg( 'gadgets-formbuilder-required' ) );
 26+
 27+ //validator for "minlength" fields (without trimming whitespaces)
 28+ $.validator.addMethod( "minlengthStrict", function( value, element, param ) {
 29+ return value.length >= param;
 30+ } );
 31+
 32+ //validator for "maxlength" fields (without trimming whitespaces)
 33+ $.validator.addMethod( "maxlengthStrict", function( value, element, param ) {
 34+ return value.length <= param;
 35+ } );
 36+
 37+ //validator for integer fields
 38+ $.validator.addMethod( "integer", function( value, element ) {
 39+ return this.optional( element ) || /^-?\d+$/.test(value);
 40+ }, mw.msg( 'gadgets-formbuilder-integer' ) );
 41+
 42+
2243 //Helper function for inheritance, see http://javascript.crockford.com/prototypal.html
2344 function object(o) {
2445 function F() {}
@@ -57,7 +78,10 @@
5879 };
5980
6081 EmptyField.prototype.getValidationSettings = function() {
61 - return null;
 82+ return {
 83+ rules: {},
 84+ messages: {}
 85+ };
6286 }
6387
6488 //A field with just a label
@@ -111,7 +135,7 @@
112136 .attr( 'type', 'text' )
113137 .attr( 'id', idPrefix + this.name )
114138 .attr( 'name', idPrefix + this.name )
115 - .val( this.desc.value );
 139+ .val( desc.value );
116140
117141 this.$p.append( this.$text );
118142 }
@@ -121,9 +145,7 @@
122146 };
123147
124148 StringField.prototype.getValidationSettings = function() {
125 - var settings = {
126 - rules: {}
127 - },
 149+ var settings = LabelField.prototype.getValidationSettings.call( this ),
128150 fieldId = idPrefix + this.name;
129151
130152 settings.rules[fieldId] = {};
@@ -131,33 +153,90 @@
132154 desc = this.desc;
133155
134156 if ( desc.required === true ) {
135 - fieldRules.required = true;
 157+ fieldRules.requiredStrict = true;
136158 }
137159
138160 if ( typeof desc.minlength != 'undefined' ) {
139 - fieldRules.minlength = desc.minlength;
 161+ fieldRules.minlengthStrict = desc.minlength;
140162 }
141163 if ( typeof desc.maxlength != 'undefined' ) {
142 - fieldRules.maxlength = desc.maxlength;
 164+ fieldRules.maxlengthStrict = desc.maxlength;
143165 }
144166
145167 settings.messages = {};
146168
147169 settings.messages[fieldId] = {
148 - "required": mw.msg( 'gadgets-formbuilder-required' ),
149 - "minlength": mw.msg( 'gadgets-formbuilder-minlength', desc.minlength ),
150 - "maxlength": mw.msg( 'gadgets-formbuilder-maxlength', desc.maxlength )
 170+ "minlengthStrict": mw.msg( 'gadgets-formbuilder-minlength', desc.minlength ),
 171+ "maxlengthStrict": mw.msg( 'gadgets-formbuilder-maxlength', desc.maxlength )
151172 };
152173
153174 return settings;
154175 }
155176
156177
 178+ NumberField.prototype = object( LabelField.prototype );
 179+ NumberField.prototype.constructor = NumberField;
 180+ function NumberField( name, desc ){
 181+ LabelField.call( this, name, desc );
 182+
 183+ if ( desc.value !== null && typeof desc.value != 'number' ) {
 184+ $.error( "desc.value is invalid" );
 185+ }
 186+
 187+ this.$text = $( '<input/>' )
 188+ .attr( 'type', 'text' )
 189+ .attr( 'id', idPrefix + this.name )
 190+ .attr( 'name', idPrefix + this.name )
 191+ .val( desc.value );
 192+
 193+ this.$p.append( this.$text );
 194+ }
157195
 196+ NumberField.prototype.getValue = function() {
 197+ var val = parseFloat( this.$text.val() );
 198+ return isNaN( val ) ? null : val;
 199+ };
158200
 201+ NumberField.prototype.getValidationSettings = function() {
 202+ var settings = LabelField.prototype.getValidationSettings.call( this ),
 203+ fieldId = idPrefix + this.name;
 204+
 205+ settings.rules[fieldId] = {};
 206+ var fieldRules = settings.rules[fieldId],
 207+ desc = this.desc;
 208+
 209+ if ( desc.required !== false ) {
 210+ fieldRules.requiredStrict = true;
 211+ }
 212+
 213+ if ( desc.integer === true ) {
 214+ fieldRules.integer = true;
 215+ }
 216+
 217+
 218+ if ( typeof desc.min != 'undefined' ) {
 219+ fieldRules.min = desc.min;
 220+ }
 221+ if ( typeof desc.max != 'undefined' ) {
 222+ fieldRules.max = desc.max;
 223+ }
 224+
 225+ settings.messages = {};
 226+
 227+ settings.messages[fieldId] = {
 228+ "required": mw.msg( 'gadgets-formbuilder-required' ),
 229+ "min": mw.msg( 'gadgets-formbuilder-min', desc.min ),
 230+ "max": mw.msg( 'gadgets-formbuilder-max', desc.max )
 231+ };
 232+
 233+ return settings;
 234+ }
 235+
 236+
159237 var validFields = {
160238 "boolean": BooleanField,
161 - "string" : StringField
 239+ "string" : StringField,
 240+ "number" : NumberField
162241 };
163242
164243 function buildFormBody() {
@@ -214,7 +293,7 @@
215294 var fieldSettings = f.getValidationSettings();
216295
217296 if ( fieldSettings ) {
218 - $.extend( settings, fieldSettings, true );
 297+ $.extend( true, settings, fieldSettings );
219298 }
220299
221300 fields.push( f );
@@ -254,7 +333,7 @@
255334 if ( methods[method] ) {
256335 return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
257336 } else if ( typeof method === 'object' || !method ) {
258 - return buildFormBody.apply( this, arguments ); //TODO
 337+ return buildFormBody.apply( this, arguments );
259338 } else {
260339 $.error( 'Method ' + method + ' does not exist on jQuery.formBuilder' );
261340 }

Follow-up revisions

RevisionCommit summaryAuthorDate
r89597- Bugfix in client side validation in presence of not-"required" fields...salvatoreingala19:45, 6 June 2011

Comments

#Comment by MaxSem (talk | contribs)   17:47, 6 June 2011
'checker' => array( 'Gadget', 'isFloatOrInt' )
You can safely use the PHP 5.2 syntax 'Gadget::isFloatOrInt' since 5.2.3 is a requirement now.

Status & tagging log