Index: branches/salvatoreingala/Gadgets/Gadgets_body.php |
— | — | @@ -252,25 +252,52 @@ |
253 | 253 | ), |
254 | 254 | 'required' => array( |
255 | 255 | 'isMandatory' => false, |
256 | | - 'checker' => 'is_bool' |
| 256 | + 'checker' => 'is_bool' |
257 | 257 | ), |
258 | 258 | 'minlength' => array( |
259 | 259 | 'isMandatory' => false, |
260 | | - 'checker' => 'is_integer' |
| 260 | + 'checker' => 'is_integer' |
261 | 261 | ), |
262 | 262 | 'maxlength' => array( |
263 | 263 | 'isMandatory' => false, |
264 | | - 'checker' => 'is_integer' |
| 264 | + 'checker' => 'is_integer' |
265 | 265 | ) |
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 | + ) |
267 | 292 | ) |
268 | 293 | ); |
269 | 294 | |
270 | 295 | //Type-specific checkers for finer validation |
271 | 296 | private static $typeCheckers = array( |
272 | | - 'string' => array( 'Gadget', 'checkStringOption' ) |
| 297 | + 'string' => array( 'Gadget', 'checkStringOption' ), |
| 298 | + 'number' => array( 'Gadget', 'checkNumberOption' ) |
273 | 299 | ); |
274 | | - |
| 300 | + |
| 301 | + //Further checks for 'string' options |
275 | 302 | private static function checkStringOption( $option ) { |
276 | 303 | if ( isset( $option['minlength'] ) && $option['minlength'] < 0 ) { |
277 | 304 | return false; |
— | — | @@ -286,9 +313,57 @@ |
287 | 314 | } |
288 | 315 | } |
289 | 316 | |
| 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 | + |
290 | 333 | return true; |
291 | 334 | } |
292 | 335 | |
| 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 | + |
293 | 368 | /** |
294 | 369 | * Creates an instance of this class from definition in MediaWiki:Gadgets-definition |
295 | 370 | * @param $definition String: Gadget definition |
— | — | @@ -672,7 +747,7 @@ |
673 | 748 | |
674 | 749 | $checker = $typeSpec[$fieldName]['checker']; |
675 | 750 | |
676 | | - if ( !$checker( $fieldValue ) ) { |
| 751 | + if ( !call_user_func( $checker, $fieldValue ) ) { |
677 | 752 | return false; |
678 | 753 | } |
679 | 754 | } |
— | — | @@ -724,12 +799,17 @@ |
725 | 800 | } |
726 | 801 | |
727 | 802 | //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 ) ) { |
731 | 809 | return false; |
732 | 810 | } |
733 | | - |
| 811 | + |
| 812 | + $pref = $prefs[$prefName]; |
| 813 | + |
734 | 814 | switch ( $prefDescription['type'] ) { |
735 | 815 | case 'boolean': |
736 | 816 | return is_bool( $pref ); |
— | — | @@ -750,17 +830,48 @@ |
751 | 831 | |
752 | 832 | //Checks the "minlength" option, if present |
753 | 833 | $minlength = isset( $prefDescription['minlength'] ) ? $prefDescription['minlength'] : 0; |
754 | | - if ( is_integer( $minlength ) && $len < $minlength ){ |
| 834 | + if ( $len < $minlength ){ |
755 | 835 | return false; |
756 | 836 | } |
757 | 837 | |
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 ){ |
761 | 841 | return false; |
762 | 842 | } |
763 | 843 | |
764 | 844 | 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; |
765 | 876 | default: |
766 | 877 | return false; //unexisting type |
767 | 878 | } |
— | — | @@ -769,7 +880,7 @@ |
770 | 881 | //Returns true if $prefs is an array of preferences that passes validation |
771 | 882 | private static function checkPrefsAgainstDescription( &$prefsDescription, &$prefs ) { |
772 | 883 | foreach ( $prefsDescription['fields'] as $prefName => $prefDescription ) { |
773 | | - if ( !self::checkSinglePref( $prefDescription, $prefs[$prefName] ) ) { |
| 884 | + if ( !self::checkSinglePref( $prefDescription, $prefs, $prefName ) ) { |
774 | 885 | return false; |
775 | 886 | } |
776 | 887 | } |
— | — | @@ -779,8 +890,16 @@ |
780 | 891 | //Fixes $prefs so that it matches the description given by $prefsDescription. |
781 | 892 | //All values of $prefs that fail validation are replaced with default values. |
782 | 893 | 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 |
783 | 902 | foreach ( $prefsDescription['fields'] as $prefName => $prefDescription ) { |
784 | | - if ( !self::checkSinglePref( $prefDescription, $prefs[$prefName] ) ) { |
| 903 | + if ( !self::checkSinglePref( $prefDescription, $prefs, $prefName ) ) { |
785 | 904 | $prefs[$prefName] = $prefDescription['default']; |
786 | 905 | } |
787 | 906 | } |
Index: branches/salvatoreingala/Gadgets/Gadgets.i18n.php |
— | — | @@ -54,6 +54,9 @@ |
55 | 55 | 'gadgets-formbuilder-required' => 'This field is required.', |
56 | 56 | 'gadgets-formbuilder-minlength' => 'Please enter at least $1 characters.', |
57 | 57 | '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.', |
58 | 61 | ); |
59 | 62 | |
60 | 63 | /** Message documentation (Message documentation) |
Index: branches/salvatoreingala/Gadgets/Gadgets.php |
— | — | @@ -72,7 +72,10 @@ |
73 | 73 | $wgResourceModules['jquery.formBuilder'] = array( |
74 | 74 | 'scripts' => array( 'jquery.formBuilder.js' ), |
75 | 75 | '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 | + ), |
77 | 80 | 'localBasePath' => $dir . 'modules/', |
78 | 81 | 'remoteExtPath' => 'Gadgets/modules' |
79 | 82 | ); |
Index: branches/salvatoreingala/Gadgets/modules/jquery.formBuilder.js |
— | — | @@ -4,7 +4,6 @@ |
5 | 5 | * Released under the MIT and GPL licenses. |
6 | 6 | */ |
7 | 7 | |
8 | | - |
9 | 8 | (function($, mw) { |
10 | 9 | |
11 | 10 | var idPrefix = "mw-gadgets-dialog-"; |
— | — | @@ -18,6 +17,28 @@ |
19 | 18 | } |
20 | 19 | } |
21 | 20 | |
| 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 | + |
22 | 43 | //Helper function for inheritance, see http://javascript.crockford.com/prototypal.html |
23 | 44 | function object(o) { |
24 | 45 | function F() {} |
— | — | @@ -57,7 +78,10 @@ |
58 | 79 | }; |
59 | 80 | |
60 | 81 | EmptyField.prototype.getValidationSettings = function() { |
61 | | - return null; |
| 82 | + return { |
| 83 | + rules: {}, |
| 84 | + messages: {} |
| 85 | + }; |
62 | 86 | } |
63 | 87 | |
64 | 88 | //A field with just a label |
— | — | @@ -111,7 +135,7 @@ |
112 | 136 | .attr( 'type', 'text' ) |
113 | 137 | .attr( 'id', idPrefix + this.name ) |
114 | 138 | .attr( 'name', idPrefix + this.name ) |
115 | | - .val( this.desc.value ); |
| 139 | + .val( desc.value ); |
116 | 140 | |
117 | 141 | this.$p.append( this.$text ); |
118 | 142 | } |
— | — | @@ -121,9 +145,7 @@ |
122 | 146 | }; |
123 | 147 | |
124 | 148 | StringField.prototype.getValidationSettings = function() { |
125 | | - var settings = { |
126 | | - rules: {} |
127 | | - }, |
| 149 | + var settings = LabelField.prototype.getValidationSettings.call( this ), |
128 | 150 | fieldId = idPrefix + this.name; |
129 | 151 | |
130 | 152 | settings.rules[fieldId] = {}; |
— | — | @@ -131,33 +153,90 @@ |
132 | 154 | desc = this.desc; |
133 | 155 | |
134 | 156 | if ( desc.required === true ) { |
135 | | - fieldRules.required = true; |
| 157 | + fieldRules.requiredStrict = true; |
136 | 158 | } |
137 | 159 | |
138 | 160 | if ( typeof desc.minlength != 'undefined' ) { |
139 | | - fieldRules.minlength = desc.minlength; |
| 161 | + fieldRules.minlengthStrict = desc.minlength; |
140 | 162 | } |
141 | 163 | if ( typeof desc.maxlength != 'undefined' ) { |
142 | | - fieldRules.maxlength = desc.maxlength; |
| 164 | + fieldRules.maxlengthStrict = desc.maxlength; |
143 | 165 | } |
144 | 166 | |
145 | 167 | settings.messages = {}; |
146 | 168 | |
147 | 169 | 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 ) |
151 | 172 | }; |
152 | 173 | |
153 | 174 | return settings; |
154 | 175 | } |
155 | 176 | |
156 | 177 | |
| 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 | + } |
157 | 195 | |
| 196 | + NumberField.prototype.getValue = function() { |
| 197 | + var val = parseFloat( this.$text.val() ); |
| 198 | + return isNaN( val ) ? null : val; |
| 199 | + }; |
158 | 200 | |
| 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 | + |
159 | 237 | var validFields = { |
160 | 238 | "boolean": BooleanField, |
161 | | - "string" : StringField |
| 239 | + "string" : StringField, |
| 240 | + "number" : NumberField |
162 | 241 | }; |
163 | 242 | |
164 | 243 | function buildFormBody() { |
— | — | @@ -214,7 +293,7 @@ |
215 | 294 | var fieldSettings = f.getValidationSettings(); |
216 | 295 | |
217 | 296 | if ( fieldSettings ) { |
218 | | - $.extend( settings, fieldSettings, true ); |
| 297 | + $.extend( true, settings, fieldSettings ); |
219 | 298 | } |
220 | 299 | |
221 | 300 | fields.push( f ); |
— | — | @@ -254,7 +333,7 @@ |
255 | 334 | if ( methods[method] ) { |
256 | 335 | return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 )); |
257 | 336 | } else if ( typeof method === 'object' || !method ) { |
258 | | - return buildFormBody.apply( this, arguments ); //TODO |
| 337 | + return buildFormBody.apply( this, arguments ); |
259 | 338 | } else { |
260 | 339 | $.error( 'Method ' + method + ' does not exist on jQuery.formBuilder' ); |
261 | 340 | } |