r92633 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r92632‎ | r92633 | r92634 >
Date:14:47, 20 July 2011
Author:salvatoreingala
Status:deferred
Tags:
Comment:
- Refactored GadgetPrefs to have a more structured and more flexible language for describing preference specifications.
- Decoupled field-description from preference-descriptions, so that it is possible to design fields that doesn't encode for preferences or fields that possibly encode for more than one preference (e.g.: containers). Refactored jquery.formBuilder for the same purpose.
- Changed the return values of the 'getgadgetprefs' api, separating "description" and "values" in two separate objects, for consistency.
Modified paths:
  • /branches/salvatoreingala/Gadgets/api/ApiGetGadgetPrefs.php (modified) (history)
  • /branches/salvatoreingala/Gadgets/backend/GadgetPrefs.php (modified) (history)
  • /branches/salvatoreingala/Gadgets/ui/resources/ext.gadgets.preferences.js (modified) (history)
  • /branches/salvatoreingala/Gadgets/ui/resources/jquery.formBuilder.js (modified) (history)

Diff [purge]

Index: branches/salvatoreingala/Gadgets/backend/GadgetPrefs.php
@@ -10,145 +10,200 @@
1111
1212 class GadgetPrefs {
1313
14 - //Syntax specifications of preference description language.
15 - //Each element describes a type, and it has a 'description' and may have a 'checker'.
16 - // - 'description' is an array that describes the fields of that option.
17 - // - 'checker' is an optional function that does validation of the entire preference description,
18 - // when more complex semantics are needed.
 14+ /*
 15+ * Syntax specifications of preference description language.
 16+ * Each element describes a field; a "simple" field encodes exactly one gadget preference, but some fields
 17+ * may encode for 0 or multiple gadget preferences.
 18+ * Each field has a 'description' and may have a 'validator', a 'flattener', and a 'checker'.
 19+ * - 'description' is an array that describes all the members of that fields. Each member description has this shape:
 20+ * - 'isMandatory' is a boolean that specifies if that member is mandatory for the field;
 21+ * - 'validator', if specified, is the name of a function that validates that member
 22+ * - 'validator' is an optional function that does validation of the entire field description,
 23+ * when member validators does not suffice since more complex semantics are needed.
 24+ * - 'flattener' is an optional function that takes a valid field description and returns an array of specification of
 25+ * gadget preferences, with preference names as keys and corresponding "simple" field descriptions as values.
 26+ * If omitted (for "simple fields"), the default flattener is used.
 27+ * - 'checker', only for "simple" fields, is the name of a function that takes a preference description and
 28+ * a preference value, and returns true if that value passes validation, false otherwise.
 29+ * - 'getMessages', if specified, is the name of a function that takes a valid description of a field and returns
 30+ * a list of messages referred to by it. If omitted, only the "label" field is returned (if it is a message).
 31+ */
1932 private static $prefsDescriptionSpecifications = array(
2033 'boolean' => array(
2134 'description' => array(
 35+ 'name' => array(
 36+ 'isMandatory' => true,
 37+ 'validator' => 'GadgetPrefs::isValidPreferenceName'
 38+ ),
2239 'default' => array(
2340 'isMandatory' => true,
24 - 'checker' => 'is_bool'
 41+ 'validator' => 'is_bool'
2542 ),
2643 'label' => array(
2744 'isMandatory' => true,
28 - 'checker' => 'is_string'
 45+ 'validator' => 'is_string'
2946 )
30 - )
 47+ ),
 48+ 'checker' => 'GadgetPrefs::checkBooleanPref'
3149 ),
3250 'string' => array(
3351 'description' => array(
 52+ 'name' => array(
 53+ 'isMandatory' => true,
 54+ 'validator' => 'GadgetPrefs::isValidPreferenceName'
 55+ ),
3456 'default' => array(
3557 'isMandatory' => true,
36 - 'checker' => 'is_string'
 58+ 'validator' => 'is_string'
3759 ),
3860 'label' => array(
3961 'isMandatory' => true,
40 - 'checker' => 'is_string'
 62+ 'validator' => 'is_string'
4163 ),
4264 'required' => array(
4365 'isMandatory' => false,
44 - 'checker' => 'is_bool'
 66+ 'validator' => 'is_bool'
4567 ),
4668 'minlength' => array(
4769 'isMandatory' => false,
48 - 'checker' => 'is_integer'
 70+ 'validator' => 'is_integer'
4971 ),
5072 'maxlength' => array(
5173 'isMandatory' => false,
52 - 'checker' => 'is_integer'
 74+ 'validator' => 'is_integer'
5375 )
5476 ),
55 - 'checker' => 'GadgetPrefs::checkStringOptionDefinition'
 77+ 'validator' => 'GadgetPrefs::validateStringOptionDefinition',
 78+ 'checker' => 'GadgetPrefs::checkStringPref'
5679 ),
5780 'number' => array(
5881 'description' => array(
 82+ 'name' => array(
 83+ 'isMandatory' => true,
 84+ 'validator' => 'GadgetPrefs::isValidPreferenceName'
 85+ ),
5986 'default' => array(
6087 'isMandatory' => true,
61 - 'checker' => 'GadgetPrefs::isFloatOrIntOrNull'
 88+ 'validator' => 'GadgetPrefs::isFloatOrIntOrNull'
6289 ),
6390 'label' => array(
6491 'isMandatory' => true,
65 - 'checker' => 'is_string'
 92+ 'validator' => 'is_string'
6693 ),
6794 'required' => array(
6895 'isMandatory' => false,
69 - 'checker' => 'is_bool'
 96+ 'validator' => 'is_bool'
7097 ),
7198 'integer' => array(
7299 'isMandatory' => false,
73 - 'checker' => 'is_bool'
 100+ 'validator' => 'is_bool'
74101 ),
75102 'min' => array(
76103 'isMandatory' => false,
77 - 'checker' => 'GadgetPrefs::isFloatOrInt'
 104+ 'validator' => 'GadgetPrefs::isFloatOrInt'
78105 ),
79106 'max' => array(
80107 'isMandatory' => false,
81 - 'checker' => 'GadgetPrefs::isFloatOrInt'
 108+ 'validator' => 'GadgetPrefs::isFloatOrInt'
82109 )
83110 ),
84 - 'checker' => 'GadgetPrefs::checkNumberOptionDefinition'
 111+ 'validator' => 'GadgetPrefs::validateNumberOptionDefinition',
 112+ 'checker' => 'GadgetPrefs::checkNumberPref'
85113 ),
86114 'select' => array(
87115 'description' => array(
 116+ 'name' => array(
 117+ 'isMandatory' => true,
 118+ 'validator' => 'GadgetPrefs::isValidPreferenceName'
 119+ ),
88120 'default' => array(
89121 'isMandatory' => true
90122 ),
91123 'label' => array(
92124 'isMandatory' => true,
93 - 'checker' => 'is_string'
 125+ 'validator' => 'is_string'
94126 ),
95127 'options' => array(
96128 'isMandatory' => true,
97 - 'checker' => 'is_array'
 129+ 'validator' => 'is_array'
98130 )
99131 ),
100 - 'checker' => 'GadgetPrefs::checkSelectOptionDefinition'
 132+ 'validator' => 'GadgetPrefs::validateSelectOptionDefinition',
 133+ 'checker' => 'GadgetPrefs::checkSelectPref',
 134+ 'getMessages' => 'GadgetPrefs::getSelectMessages'
101135 ),
102136 'range' => array(
103137 'description' => array(
 138+ 'name' => array(
 139+ 'isMandatory' => true,
 140+ 'validator' => 'GadgetPrefs::isValidPreferenceName'
 141+ ),
104142 'default' => array(
105143 'isMandatory' => true,
106 - 'checker' => 'GadgetPrefs::isFloatOrIntOrNull'
 144+ 'validator' => 'GadgetPrefs::isFloatOrIntOrNull'
107145 ),
108146 'label' => array(
109147 'isMandatory' => true,
110 - 'checker' => 'is_string'
 148+ 'validator' => 'is_string'
111149 ),
112150 'min' => array(
113151 'isMandatory' => true,
114 - 'checker' => 'GadgetPrefs::isFloatOrInt'
 152+ 'validator' => 'GadgetPrefs::isFloatOrInt'
115153 ),
116154 'max' => array(
117155 'isMandatory' => true,
118 - 'checker' => 'GadgetPrefs::isFloatOrInt'
 156+ 'validator' => 'GadgetPrefs::isFloatOrInt'
119157 ),
120158 'step' => array(
121159 'isMandatory' => false,
122 - 'checker' => 'GadgetPrefs::isFloatOrInt'
 160+ 'validator' => 'GadgetPrefs::isFloatOrInt'
123161 )
124162 ),
125 - 'checker' => 'GadgetPrefs::checkRangeOptionDefinition'
 163+ 'validator' => 'GadgetPrefs::validateRangeOptionDefinition',
 164+ 'checker' => 'GadgetPrefs::checkRangePref'
126165 ),
127166 'date' => array(
128167 'description' => array(
 168+ 'name' => array(
 169+ 'isMandatory' => true,
 170+ 'validator' => 'GadgetPrefs::isValidPreferenceName'
 171+ ),
129172 'default' => array(
130173 'isMandatory' => true
131174 ),
132175 'label' => array(
133176 'isMandatory' => true,
134 - 'checker' => 'is_string'
 177+ 'validator' => 'is_string'
135178 )
136 - )
 179+ ),
 180+ 'checker' => 'GadgetPrefs::checkDatePref'
137181 ),
138182 'color' => array(
139183 'description' => array(
 184+ 'name' => array(
 185+ 'isMandatory' => true,
 186+ 'validator' => 'GadgetPrefs::isValidPreferenceName'
 187+ ),
140188 'default' => array(
141189 'isMandatory' => true
142190 ),
143191 'label' => array(
144192 'isMandatory' => true,
145 - 'checker' => 'is_string'
 193+ 'validator' => 'is_string'
146194 )
147 - )
 195+ ),
 196+ 'checker' => 'GadgetPrefs::checkColorPref'
148197 )
149198 );
150199
 200+ private static function isValidPreferenceName( $name ) {
 201+ return strlen( $name ) <= 40
 202+ && preg_match( '/^[a-zA-Z_][a-zA-Z0-9_]*$/', $name );
 203+ }
 204+
 205+
151206 //Further checks for 'string' options
152 - private static function checkStringOptionDefinition( $option ) {
 207+ private static function validateStringOptionDefinition( $option ) {
153208 if ( isset( $option['minlength'] ) && $option['minlength'] < 0 ) {
154209 return false;
155210 }
@@ -174,8 +229,13 @@
175230 return is_float( $param ) || is_int( $param ) || $param === null;
176231 }
177232
 233+ //default flattener for simple fields that encode for a single preference
 234+ private static function flattenSimpleField( $fieldDescription ) {
 235+ return array( $fieldDescription['name'] => $fieldDescription );
 236+ }
 237+
178238 //Further checks for 'number' options
179 - private static function checkNumberOptionDefinition( $option ) {
 239+ private static function validateNumberOptionDefinition( $option ) {
180240 if ( isset( $option['integer'] ) && $option['integer'] === true ) {
181241 //Check if 'min', 'max' and 'default' are integers (if given)
182242 if ( intval( $option['default'] ) != $option['default'] ) {
@@ -192,7 +252,7 @@
193253 return true;
194254 }
195255
196 - private static function checkSelectOptionDefinition( $option ) {
 256+ private static function validateSelectOptionDefinition( $option ) {
197257 $options = $option['options'];
198258
199259 foreach ( $options as $opt => $optVal ) {
@@ -212,7 +272,7 @@
213273 return true;
214274 }
215275
216 - private static function checkRangeOptionDefinition( $option ) {
 276+ private static function validateRangeOptionDefinition( $option ) {
217277 $step = isset( $option['step'] ) ? $option['step'] : 1;
218278
219279 if ( $step <= 0 ) {
@@ -233,42 +293,71 @@
234294
235295 return true;
236296 }
237 -
238 - //Checks if the given description of the preferences is valid
239 - public static function isPrefsDescriptionValid( $prefsDescription ) {
240 - if ( !is_array( $prefsDescription )
241 - || !isset( $prefsDescription['fields'] )
242 - || !is_array( $prefsDescription['fields'] ) )
 297+
 298+ //Flattens a simple field, by calling its field-specific flattener if there is any,
 299+ //or the default flattener otherwise.
 300+ private static function flattenFieldDescription( $fieldDescription ) {
 301+ $typeSpec = self::$prefsDescriptionSpecifications[$fieldDescription['type']];
 302+ $typeDescription = $typeSpec['description'];
 303+ if ( isset( $typeSpec['flattener'] ) ) {
 304+ $flattener = $typeSpec['flattener'];
 305+ } else {
 306+ $flattener = 'GadgetPrefs::flattenSimpleField';
 307+ }
 308+ return call_user_func( $flattener, $fieldDescription );
 309+ }
 310+
 311+ //Returns a map keyed at preference names, and with their corresponding
 312+ //"simple" field descriptions as values.
 313+ //It is assumed that $prefsDescription is valid.
 314+ private static function flattenPrefsDescription( $prefsDescription ) {
 315+ $flattenedPrefsDescription = array();
 316+ foreach ( $prefsDescription['fields'] as $fieldDescription ) {
 317+ $flt = self::flattenFieldDescription( $fieldDescription );
 318+ $flattenedPrefsDescription = array_merge( $flattenedPrefsDescription, $flt );
 319+ }
 320+ return $flattenedPrefsDescription;
 321+ }
 322+
 323+ //Validate the description of a 'section' of preferences
 324+ private static function validateSectionDefinition( $sectionDescription ) {
 325+ static $mandatoryCount = array(), $initialized = false;
 326+
 327+ if ( !is_array( $sectionDescription )
 328+ || !isset( $sectionDescription['fields'] )
 329+ || !is_array( $sectionDescription['fields'] ) )
243330 {
244331 return false;
245332 }
246333
 334+ if ( !$initialized ) {
 335+ //Count of mandatory members for each type
 336+ foreach ( self::$prefsDescriptionSpecifications as $type => $typeSpec ) {
 337+ $mandatoryCount[$type] = 0;
 338+ foreach ( $typeSpec['description'] as $fieldName => $fieldSpec ) {
 339+ if ( $fieldSpec['isMandatory'] === true ) {
 340+ ++$mandatoryCount[$type];
 341+ }
 342+ }
 343+ }
 344+ $initialized = true;
 345+ }
 346+
247347 //Check if 'fields' is a regular (not-associative) array, and that it is not empty
248 - $count = count( $prefsDescription['fields'] );
249 - if ( $count == 0 || array_keys( $prefsDescription['fields'] ) !== range( 0, $count - 1 ) ) {
 348+ $count = count( $sectionDescription['fields'] );
 349+ if ( $count == 0 || array_keys( $sectionDescription['fields'] ) !== range( 0, $count - 1 ) ) {
250350 return false;
251351 }
252352
253 - //Count of mandatory members for each type
254 - $mandatoryCount = array();
255 - foreach ( self::$prefsDescriptionSpecifications as $type => $typeSpec ) {
256 - $mandatoryCount[$type] = 0;
257 - foreach ( $typeSpec['description'] as $fieldName => $fieldSpec ) {
258 - if ( $fieldSpec['isMandatory'] === true ) {
259 - ++$mandatoryCount[$type];
260 - }
261 - }
262 - }
263 -
264353 //TODO: validation of members other than $prefs['fields']
265354
266 - //Map of encountered names
267 - $names = array();
 355+ //Flattened preferences
 356+ $flattenedPrefs = array();
268357
269 - foreach ( $prefsDescription['fields'] as $optionDefinition ) {
 358+ foreach ( $sectionDescription['fields'] as $optionDefinition ) {
270359
271 - //Check if 'name' and 'type' are set
272 - if ( !isset( $optionDefinition['type'] ) || !isset( $optionDefinition['name'] ) ) {
 360+ //Check if 'type' is set
 361+ if ( !isset( $optionDefinition['type'] ) ) {
273362 return false;
274363 }
275364
@@ -279,30 +368,14 @@
280369 return false;
281370 }
282371
283 - $option = $optionDefinition['name'];
284 -
285 - //check that it's different from previous names
286 - if ( isset( $names[$option] ) ) {
287 - return false;
288 - }
289 -
290 - $names[$option] = true;
291 -
292 - //check option name compliance
293 - if ( strlen( $option ) > 40
294 - || !preg_match( '/^[a-zA-Z_][a-zA-Z0-9_]*$/', $option ) )
295 - {
296 - return false;
297 - }
298 -
299372 //Check if all fields satisfy specification
300373 $typeSpec = self::$prefsDescriptionSpecifications[$type];
301374 $typeDescription = $typeSpec['description'];
302375 $count = 0; //count of present mandatory members
303376 foreach ( $optionDefinition as $fieldName => $fieldValue ) {
304377
305 - if ( $fieldName == 'type' || $fieldName == 'name' ) {
306 - continue; //'type' and 'name' must not be checked
 378+ if ( $fieldName == 'type' ) {
 379+ continue; //'type' must not be checked
307380 }
308381
309382 if ( !isset( $typeDescription[$fieldName] ) ) {
@@ -313,9 +386,9 @@
314387 ++$count;
315388 }
316389
317 - if ( isset( $typeDescription[$fieldName]['checker'] ) ) {
318 - $checker = $typeDescription[$fieldName]['checker'];
319 - if ( !call_user_func( $checker, $fieldValue ) ) {
 390+ if ( isset( $typeDescription[$fieldName]['validator'] ) ) {
 391+ $validator = $typeDescription[$fieldName]['validator'];
 392+ if ( !call_user_func( $validator, $fieldValue ) ) {
320393 return false;
321394 }
322395 }
@@ -325,30 +398,48 @@
326399 return false; //not all mandatory members are given
327400 }
328401
329 - if ( isset( $typeSpec['checker'] ) ) {
 402+ if ( isset( $typeSpec['validator'] ) ) {
330403 //Call type-specific checker for finer validation
331 - if ( !call_user_func( $typeSpec['checker'], $optionDefinition ) ) {
 404+ if ( !call_user_func( $typeSpec['validator'], $optionDefinition ) ) {
332405 return false;
333406 }
334407 }
 408+
 409+ //flatten preferences described by this field
 410+ $flt = self::flattenFieldDescription( $optionDefinition );
335411
336 - //Finally, check that the 'default' fields exists and is valid
337 - if ( !array_key_exists( 'default', $optionDefinition ) ) {
338 - return false;
 412+ foreach ( $flt as $prefName => $prefDescription ) {
 413+ //Finally, check that the 'default' fields exists and is valid
 414+ //for all preferences encoded by this field
 415+ if ( !array_key_exists( 'default', $prefDescription ) ) {
 416+ return false;
 417+ }
 418+
 419+ $prefs = array( 'dummy' => $optionDefinition['default'] );
 420+ if ( !self::checkSinglePref( $optionDefinition, $prefs, 'dummy' ) ) {
 421+ return false;
 422+ }
339423 }
340424
341 - $prefs = array( 'dummy' => $optionDefinition['default'] );
342 - if ( !self::checkSinglePref( $optionDefinition, $prefs, 'dummy' ) ) {
 425+ //If there are preferences with the same name of a previously encountered preference, fail
 426+ if ( array_intersect( array_keys( $flt ), array_keys( $flattenedPrefs ) ) ) {
343427 return false;
344428 }
 429+ $flattenedPrefs = array_merge( $flattenedPrefs, $flt );
345430 }
346431
347432 return true;
348433 }
349434
350 - //Check if a preference is valid, according to description
 435+ //Checks if the given description of the preferences is valid
 436+ public static function isPrefsDescriptionValid( $prefsDescription ) {
 437+ return self::validateSectionDefinition( $prefsDescription );
 438+ }
 439+
 440+ //Check if a preference is valid, according to description.
 441+ //$prefDescription must be the description of a "simple" field (that is, with 'checker')
351442 //NOTE: we pass both $prefs and $prefName (instead of just $prefs[$prefName])
352 - // to allow checking for null.
 443+ // to allow checking for undefined values.
353444 private static function checkSinglePref( $prefDescription, $prefs, $prefName ) {
354445
355446 //isset( $prefs[$prefName] ) would return false for null values
@@ -356,129 +447,158 @@
357448 return false;
358449 }
359450
360 - $pref = $prefs[$prefName];
361 -
362 - switch ( $prefDescription['type'] ) {
363 - case 'boolean':
364 - return is_bool( $pref );
365 - case 'string':
366 - if ( !is_string( $pref ) ) {
367 - return false;
368 - }
369 -
370 - $len = strlen( $pref );
371 -
372 - //Checks the "required" option, if present
373 - $required = isset( $prefDescription['required'] ) ? $prefDescription['required'] : true;
374 - if ( $required === true && $len == 0 ) {
375 - return false;
376 - } elseif ( $required === false && $len == 0 ) {
377 - return true; //overriding 'minlength'
378 - }
379 -
380 - //Checks the "minlength" option, if present
381 - $minlength = isset( $prefDescription['minlength'] ) ? $prefDescription['minlength'] : 0;
382 - if ( $len < $minlength ){
383 - return false;
384 - }
 451+ $value = $prefs[$prefName];
 452+ $type = $prefDescription['type'];
 453+
 454+ if ( !isset( self::$prefsDescriptionSpecifications[$type] )
 455+ || !isset( self::$prefsDescriptionSpecifications[$type]['checker'] ) )
 456+ {
 457+ return false;
 458+ }
 459+
 460+ $checker = self::$prefsDescriptionSpecifications[$type]['checker'];
 461+ return call_user_func( $checker, $prefDescription, $value );
 462+ }
385463
386 - //Checks the "maxlength" option, if present
387 - $maxlength = isset( $prefDescription['maxlength'] ) ? $prefDescription['maxlength'] : 1024; //TODO: what big integer here?
388 - if ( $len > $maxlength ){
389 - return false;
390 - }
391 -
392 - return true;
393 - case 'number':
394 - if ( !is_float( $pref ) && !is_int( $pref ) && $pref !== null ) {
395 - return false;
396 - }
 464+ //Checker for 'boolean' preferences
 465+ private static function checkBooleanPref( $prefDescription, $value ) {
 466+ return is_bool( $value );
 467+ }
397468
398 - $required = isset( $prefDescription['required'] ) ? $prefDescription['required'] : true;
399 - if ( $required === false && $pref === null ) {
400 - return true;
401 - }
402 -
403 - if ( $pref === null ) {
404 - return false; //$required === true, so null is not acceptable
405 - }
 469+ //Checker for 'string' preferences
 470+ private static function checkStringPref( $prefDescription, $value ) {
 471+ if ( !is_string( $value ) ) {
 472+ return false;
 473+ }
 474+
 475+ $len = strlen( $value );
 476+
 477+ //Checks the "required" option, if present
 478+ $required = isset( $prefDescription['required'] ) ? $prefDescription['required'] : true;
 479+ if ( $required === true && $len == 0 ) {
 480+ return false;
 481+ } elseif ( $required === false && $len == 0 ) {
 482+ return true; //overriding 'minlength'
 483+ }
 484+
 485+ //Checks the "minlength" option, if present
 486+ $minlength = isset( $prefDescription['minlength'] ) ? $prefDescription['minlength'] : 0;
 487+ if ( $len < $minlength ){
 488+ return false;
 489+ }
406490
407 - $integer = isset( $prefDescription['integer'] ) ? $prefDescription['integer'] : false;
408 -
409 - if ( $integer === true && intval( $pref ) != $pref ) {
410 - return false; //not integer
411 - }
412 -
413 - if ( isset( $prefDescription['min'] ) ) {
414 - $min = $prefDescription['min'];
415 - if ( $pref < $min ) {
416 - return false; //value below minimum
417 - }
418 - }
 491+ //Checks the "maxlength" option, if present
 492+ $maxlength = isset( $prefDescription['maxlength'] ) ? $prefDescription['maxlength'] : 1024; //TODO: what big integer here?
 493+ if ( $len > $maxlength ){
 494+ return false;
 495+ }
 496+
 497+ return true;
 498+ }
419499
420 - if ( isset( $prefDescription['max'] ) ) {
421 - $max = $prefDescription['max'];
422 - if ( $pref > $max ) {
423 - return false; //value above maximum
424 - }
425 - }
 500+ //Checker for 'number' preferences
 501+ private static function checkNumberPref( $prefDescription, $value ) {
 502+ if ( !is_float( $value ) && !is_int( $value ) && $value !== null ) {
 503+ return false;
 504+ }
426505
427 - return true;
428 - case 'select':
429 - $values = array_values( $prefDescription['options'] );
430 - return in_array( $pref, $values, true );
431 - case 'range':
432 - if ( !is_float( $pref ) && !is_int( $pref ) ) {
433 - return false;
434 - }
435 -
436 - $min = $prefDescription['min'];
437 - $max = $prefDescription['max'];
438 -
439 - if ( $pref < $min || $pref > $max ) {
440 - return false;
441 - }
442 -
443 - $step = isset( $prefDescription['step'] ) ? $prefDescription['step'] : 1;
444 -
445 - if ( $step <= 0 ) {
446 - return false;
447 - }
448 -
449 - //Valid values are min, min + step, min + 2*step, ...
450 - //Then ( $pref - $min ) / $step must be close enough to an integer
451 - $eps = 1.0e-6; //tolerance
452 - $tmp = ( $pref - $min ) / $step;
453 - if ( abs( $tmp - floor( $tmp ) ) > $eps ) {
454 - return false;
455 - }
456 -
457 - return true;
458 - case 'date':
459 - if ( $pref === null ) {
460 - return true;
461 - }
462 -
463 - //Basic syntactic checks
464 - if ( !is_string( $pref ) ||
465 - !preg_match( '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/', $pref ) )
466 - {
467 - return false;
468 - }
469 -
470 - //Full parsing
471 - return date_create( $pref ) !== false;
472 - case 'color':
473 - //Check if it's a string representing a color
474 - //(with 6 hexadecimal lowercase characters).
475 - return is_string( $pref ) && preg_match( '/^#[0-9a-f]{6}$/', $pref );
476 - default:
477 - return false; //unexisting type
 506+ $required = isset( $prefDescription['required'] ) ? $prefDescription['required'] : true;
 507+ if ( $required === false && $value === null ) {
 508+ return true;
478509 }
 510+
 511+ if ( $value === null ) {
 512+ return false; //$required === true, so null is not acceptable
 513+ }
 514+
 515+ $integer = isset( $prefDescription['integer'] ) ? $prefDescription['integer'] : false;
 516+
 517+ if ( $integer === true && intval( $value ) != $value ) {
 518+ return false; //not integer
 519+ }
 520+
 521+ if ( isset( $prefDescription['min'] ) ) {
 522+ $min = $prefDescription['min'];
 523+ if ( $value < $min ) {
 524+ return false; //value below minimum
 525+ }
 526+ }
 527+
 528+ if ( isset( $prefDescription['max'] ) ) {
 529+ $max = $prefDescription['max'];
 530+ if ( $value > $max ) {
 531+ return false; //value above maximum
 532+ }
 533+ }
 534+
 535+ return true;
479536 }
480537
 538+ //Checker for 'select' preferences
 539+ private static function checkSelectPref( $prefDescription, $value ) {
 540+ $values = array_values( $prefDescription['options'] );
 541+ return in_array( $value, $values, true );
 542+ }
 543+
 544+ //Checker for 'range' preferences
 545+ private static function checkRangePref( $prefDescription, $value ) {
 546+ if ( !is_float( $value ) && !is_int( $value ) ) {
 547+ return false;
 548+ }
 549+
 550+ $min = $prefDescription['min'];
 551+ $max = $prefDescription['max'];
 552+
 553+ if ( $value < $min || $value > $max ) {
 554+ return false;
 555+ }
 556+
 557+ $step = isset( $prefDescription['step'] ) ? $prefDescription['step'] : 1;
 558+
 559+ if ( $step <= 0 ) {
 560+ return false;
 561+ }
 562+
 563+ //Valid values are min, min + step, min + 2*step, ...
 564+ //Then ( $value - $min ) / $step must be close enough to an integer
 565+ $eps = 1.0e-6; //tolerance
 566+ $tmp = ( $value - $min ) / $step;
 567+ if ( abs( $tmp - floor( $tmp ) ) > $eps ) {
 568+ return false;
 569+ }
 570+
 571+ return true;
 572+ }
 573+
 574+ //Checker for 'date' preferences
 575+ private static function checkDatePref( $prefDescription, $value ) {
 576+ if ( $value === null ) {
 577+ return true;
 578+ }
 579+
 580+ //Basic syntactic checks
 581+ if ( !is_string( $value ) ||
 582+ !preg_match( '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/', $value ) )
 583+ {
 584+ return false;
 585+ }
 586+
 587+ //Full parsing
 588+ return date_create( $value ) !== false;
 589+ }
 590+
 591+ //Checker for 'color' preferences
 592+ private static function checkColorPref( $prefDescription, $value ) {
 593+ //Check if it's a string representing a color
 594+ //(with 6 hexadecimal lowercase characters).
 595+ return is_string( $value ) && preg_match( '/^#[0-9a-f]{6}$/', $value );
 596+ }
 597+
 598+
 599+
481600 /**
482 - * Checks if $prefs is an array of preferences that passes validation
 601+ * Checks if $prefs is an array of preferences that passes validation.
 602+ * It is assumed that $prefsDescription is a valid description of preferences.
483603 *
484604 * @param $prefsDescription Array: the preferences description to use.
485605 * @param $prefs Array: reference of the array of preferences to check.
@@ -486,9 +606,10 @@
487607 * @return boolean true if $prefs passes validation against $prefsDescription, false otherwise.
488608 */
489609 public static function checkPrefsAgainstDescription( $prefsDescription, $prefs ) {
 610+ $flattenedPrefs = self::flattenPrefsDescription( $prefsDescription );
490611 $validPrefs = array();
491612 //Check that all the given preferences pass validation
492 - foreach ( $prefsDescription['fields'] as $prefDescription ) {
 613+ foreach ( $flattenedPrefs as $prefDescription ) {
493614 $prefName = $prefDescription['name'];
494615 if ( !self::checkSinglePref( $prefDescription, $prefs, $prefName ) ) {
495616 return false;
@@ -509,15 +630,17 @@
510631 /**
511632 * Fixes $prefs so that it matches the description given by $prefsDescription.
512633 * All values of $prefs that fail validation are replaced with default values.
 634+ * It is assumed that $prefsDescription is a valid description of preferences.
513635 *
514636 * @param $prefsDescription Array: the preferences description to use.
515637 * @param &$prefs Array: reference of the array of preferences to match.
516638 */
517639 public static function matchPrefsWithDescription( $prefsDescription, &$prefs ) {
 640+ $flattenedPrefs = self::flattenPrefsDescription( $prefsDescription );
518641 $validPrefs = array();
519642
520643 //Fix preferences that fail validation, by replacing their value with default
521 - foreach ( $prefsDescription['fields'] as $prefDescription ) {
 644+ foreach ( $flattenedPrefs as $prefDescription ) {
522645 $prefName = $prefDescription['name'];
523646 if ( !self::checkSinglePref( $prefDescription, $prefs, $prefName ) ) {
524647 $prefs[$prefName] = $prefDescription['default'];
@@ -568,28 +691,36 @@
569692 * @return Array: the messages needed by $prefsDescription.
570693 */
571694 public static function getMessages( $prefsDescription ) {
572 - $maybeMsgs = array();
 695+ $msgs = array();
573696
574 - if ( isset( $prefsDescription['intro'] ) ) {
575 - $maybeMsgs[] = $prefsDescription['intro'];
 697+ if ( isset( $prefsDescription['intro'] ) && self::isMessage( $prefsDescription['intro'] ) ) {
 698+ $msgs[] = substr( $prefsDescription['intro'], 1 );
576699 }
577700
578 - foreach ( $prefsDescription['fields'] as $prefName => $prefDesc ) {
579 - $maybeMsgs[] = $prefDesc['label'];
580 -
581 - if ( $prefDesc['type'] == 'select' ) {
582 - foreach ( $prefDesc['options'] as $optName => $value ) {
583 - $maybeMsgs[] = $optName;
 701+ foreach ( $prefsDescription['fields'] as $prefDesc ) {
 702+ $type = $prefDesc['type'];
 703+ $prefSpec = self::$prefsDescriptionSpecifications[$type];
 704+ if ( isset( $prefSpec['getMessages'] ) ) {
 705+ $getMessages = $prefSpec['getMessages'];
 706+ $msgs = array_merge( $msgs, call_user_func( $getMessages, $prefDesc ) );
 707+ } else {
 708+ if ( isset( $prefDesc['label'] ) && self::isMessage( $prefDesc['label'] ) ) {
 709+ $msgs[] = substr( $prefDesc['label'], 1 );
584710 }
585711 }
586712 }
587713
 714+ return array_unique( $msgs );
 715+ }
 716+
 717+ //Returns the messages for a 'select' field description
 718+ private static function getSelectMessages( $prefDescription ) {
588719 $msgs = array();
589 - foreach ( $maybeMsgs as $msg ) {
590 - if ( self::isMessage( $msg ) ) {
591 - $msgs[] = substr( $msg, 1 );
 720+ foreach ( $prefDescription['options'] as $optName => $value ) {
 721+ if ( self::isMessage( $optName ) ) {
 722+ $msgs[] = substr( $optName, 1 );
592723 }
593724 }
594 - return array_unique( $msgs );
 725+ return $msgs;
595726 }
596727 }
Index: branches/salvatoreingala/Gadgets/api/ApiGetGadgetPrefs.php
@@ -51,14 +51,9 @@
5252 if ( $userPrefs === null ) {
5353 throw new MWException( __METHOD__ . ': $userPrefs should not be null.' );
5454 }
55 -
56 - //Add user preferences to preference description
57 - foreach ( $prefsDescription['fields'] as $prefIdx => $prefDescription ) {
58 - $prefName = $prefDescription['name'];
59 - $prefsDescription['fields'][$prefIdx]['value'] = $userPrefs[$prefName];
60 - }
61 -
62 - $this->getResult()->addValue( null, $this->getModuleName(), $prefsDescription );
 55+
 56+ $this->getResult()->addValue( null, 'description', $prefsDescription );
 57+ $this->getResult()->addValue( null, 'values', $userPrefs );
6358 }
6459
6560 public function getAllowedParams() {
Index: branches/salvatoreingala/Gadgets/ui/resources/jquery.formBuilder.js
@@ -87,9 +87,9 @@
8888 }
8989
9090 //A field with no content
91 - function EmptyField( $form, name, desc ) {
 91+ function EmptyField( $form, desc, values ) {
9292 //Check existence of compulsory fields
93 - if ( typeof name == 'undefined' || !desc.type || !desc.label ) {
 93+ if ( !desc.type || !desc.label ) {
9494 $.error( "Missing arguments" );
9595 }
9696
@@ -97,22 +97,16 @@
9898
9999 this.$p = $( '<p/>' );
100100
101 - this.name = name;
102101 this.desc = desc;
103102 }
104103
105 - EmptyField.prototype.getName = function() {
106 - return this.name;
107 - };
108 -
109104 EmptyField.prototype.getDesc = function() {
110105 return this.desc;
111106 };
112107
113 -
114108 //Override expected
115 - EmptyField.prototype.getValue = function() {
116 - return null;
 109+ EmptyField.prototype.getValues = function() {
 110+ return {};
117111 };
118112
119113 EmptyField.prototype.getElement = function() {
@@ -129,12 +123,12 @@
130124 //A field with just a label
131125 LabelField.prototype = object( EmptyField.prototype );
132126 LabelField.prototype.constructor = LabelField;
133 - function LabelField( $form, name, desc ) {
134 - EmptyField.call( this, $form, name, desc );
 127+ function LabelField( $form, desc, values ) {
 128+ EmptyField.call( this, $form, desc, values );
135129
136130 var $label = $( '<label/>' )
137131 .text( preproc( this.$form, this.desc.label ) )
138 - .attr('for', idPrefix + this.name );
 132+ .attr('for', idPrefix + this.desc.name );
139133
140134 this.$p.append( $label );
141135 }
@@ -142,53 +136,59 @@
143137 //A field with a label and a checkbox
144138 BooleanField.prototype = object( LabelField.prototype );
145139 BooleanField.prototype.constructor = BooleanField;
146 - function BooleanField( $form, name, desc ){
147 - LabelField.call( this, $form, name, desc );
 140+ function BooleanField( $form, desc, values ){
 141+ LabelField.call( this, $form, desc, values );
148142
149 - if ( typeof desc.value != 'boolean' ) {
150 - $.error( "desc.value is invalid" );
 143+ var value = values[this.desc.name];
 144+ if ( typeof value != 'boolean' ) {
 145+ $.error( "value is invalid" );
151146 }
152147
153148 this.$c = $( '<input/>' )
154149 .attr( 'type', 'checkbox' )
155 - .attr( 'id', idPrefix + this.name )
156 - .attr( 'name', idPrefix + this.name )
157 - .attr( 'checked', this.desc.value );
 150+ .attr( 'id', idPrefix + this.desc.name )
 151+ .attr( 'name', idPrefix + this.desc.name )
 152+ .attr( 'checked', value );
158153
159154 this.$p.append( this.$c );
160155 }
161156
162 - BooleanField.prototype.getValue = function() {
163 - return this.$c.is( ':checked' );
 157+ BooleanField.prototype.getValues = function() {
 158+ var res = {};
 159+ res[this.desc.name] = this.$c.is( ':checked' );
 160+ return res;
164161 };
165162
166163 //A field with a textbox accepting string values
167164
168165 StringField.prototype = object( LabelField.prototype );
169166 StringField.prototype.constructor = StringField;
170 - function StringField( $form, name, desc ){
171 - LabelField.call( this, $form, name, desc );
 167+ function StringField( $form, desc, values ){
 168+ LabelField.call( this, $form, desc, values );
172169
173 - if ( typeof desc.value != 'string' ) {
174 - $.error( "desc.value is invalid" );
 170+ var value = values[this.desc.name];
 171+ if ( typeof value != 'string' ) {
 172+ $.error( "value is invalid" );
175173 }
176174
177175 this.$text = $( '<input/>' )
178176 .attr( 'type', 'text' )
179 - .attr( 'id', idPrefix + this.name )
180 - .attr( 'name', idPrefix + this.name )
181 - .val( desc.value );
 177+ .attr( 'id', idPrefix + this.desc.name )
 178+ .attr( 'name', idPrefix + this.desc.name )
 179+ .val( value );
182180
183181 this.$p.append( this.$text );
184182 }
185183
186 - StringField.prototype.getValue = function() {
187 - return this.$text.val();
 184+ StringField.prototype.getValues = function() {
 185+ var res = {};
 186+ res[this.desc.name] = this.$text.val();
 187+ return res;
188188 };
189189
190190 StringField.prototype.getValidationSettings = function() {
191191 var settings = LabelField.prototype.getValidationSettings.call( this ),
192 - fieldId = idPrefix + this.name;
 192+ fieldId = idPrefix + this.desc.name;
193193
194194 settings.rules[fieldId] = {};
195195 var fieldRules = settings.rules[fieldId],
@@ -218,30 +218,33 @@
219219 //A field with a textbox accepting numeric values
220220 NumberField.prototype = object( LabelField.prototype );
221221 NumberField.prototype.constructor = NumberField;
222 - function NumberField( $form, name, desc ){
223 - LabelField.call( this, $form, name, desc );
 222+ function NumberField( $form, desc, values ){
 223+ LabelField.call( this, $form, desc, values );
224224
225 - if ( desc.value !== null && typeof desc.value != 'number' ) {
226 - $.error( "desc.value is invalid" );
 225+ var value = values[this.desc.name];
 226+ if ( value !== null && typeof value != 'number' ) {
 227+ $.error( "value is invalid" );
227228 }
228229
229230 this.$text = $( '<input/>' )
230231 .attr( 'type', 'text' )
231 - .attr( 'id', idPrefix + this.name )
232 - .attr( 'name', idPrefix + this.name )
233 - .val( desc.value );
 232+ .attr( 'id', idPrefix + this.desc.name )
 233+ .attr( 'name', idPrefix + this.desc.name )
 234+ .val( value );
234235
235236 this.$p.append( this.$text );
236237 }
237238
238 - NumberField.prototype.getValue = function() {
239 - var val = parseFloat( this.$text.val() );
240 - return isNaN( val ) ? null : val;
 239+ NumberField.prototype.getValues = function() {
 240+ var val = parseFloat( this.$text.val() ),
 241+ res = {};
 242+ res[this.desc.name] = isNaN( val ) ? null : val;
 243+ return res;
241244 };
242245
243246 NumberField.prototype.getValidationSettings = function() {
244247 var settings = LabelField.prototype.getValidationSettings.call( this ),
245 - fieldId = idPrefix + this.name;
 248+ fieldId = idPrefix + this.desc.name;
246249
247250 settings.rules[fieldId] = {};
248251 var fieldRules = settings.rules[fieldId],
@@ -277,50 +280,54 @@
278281 //A field with a drop-down list
279282 SelectField.prototype = object( LabelField.prototype );
280283 SelectField.prototype.constructor = SelectField;
281 - function SelectField( $form, name, desc ){
282 - LabelField.call( this, $form, name, desc );
 284+ function SelectField( $form, desc, values ){
 285+ LabelField.call( this, $form, desc, values );
283286
284287 var $select = this.$select = $( '<select/>' )
285 - .attr( 'id', idPrefix + this.name )
286 - .attr( 'name', idPrefix + this.name );
 288+ .attr( 'id', idPrefix + this.desc.name )
 289+ .attr( 'name', idPrefix + this.desc.name );
287290
288 - var values = [];
 291+ var validValues = [];
289292 var self = this;
290293 $.each( desc.options, function( optName, optVal ) {
291 - var i = values.length;
 294+ var i = validValues.length;
292295 $( '<option/>' )
293296 .text( preproc( self.$form, optName ) )
294297 .val( i )
295298 .appendTo( $select );
296 - values.push( optVal );
 299+ validValues.push( optVal );
297300 } );
298301
299 - this.values = values;
 302+ this.validValues = validValues;
300303
301 - if ( $.inArray( desc.value, values ) == -1 ) {
302 - $.error( "desc.value is not in the list of possible values" );
 304+ var value = values[this.desc.name];
 305+ if ( $.inArray( value, validValues ) == -1 ) {
 306+ $.error( "value is not in the list of possible values" );
303307 }
304308
305 - var i = $.inArray( desc.value, values );
 309+ var i = $.inArray( value, validValues );
306310 $select.val( i ).attr( 'selected', 'selected' );
307311
308312 this.$p.append( $select );
309313 }
310314
311 - SelectField.prototype.getValue = function() {
312 - var i = parseInt( this.$select.val(), 10 );
313 - return this.values[i];
 315+ SelectField.prototype.getValues = function() {
 316+ var i = parseInt( this.$select.val(), 10 ),
 317+ res = {};
 318+ res[this.desc.name] = this.validValues[i];
 319+ return res;
314320 };
315321
316322
317323 //A field with a slider, representing ranges of numbers
318324 RangeField.prototype = object( LabelField.prototype );
319325 RangeField.prototype.constructor = RangeField;
320 - function RangeField( $form, name, desc ){
321 - LabelField.call( this, $form, name, desc );
 326+ function RangeField( $form, desc, values ){
 327+ LabelField.call( this, $form, desc, values );
322328
323 - if ( typeof desc.value != 'number' ) {
324 - $.error( "desc.value is invalid" );
 329+ var value = values[this.desc.name];
 330+ if ( typeof value != 'number' ) {
 331+ $.error( "value is invalid" );
325332 }
326333
327334 if ( typeof desc.min != 'number' ) {
@@ -335,17 +342,17 @@
336343 $.error( "desc.step is invalid" );
337344 }
338345
339 - if ( desc.value < desc.min || desc.value > desc.max ) {
340 - $.error( "desc.value is out of range" );
 346+ if ( value < desc.min || value > desc.max ) {
 347+ $.error( "value is out of range" );
341348 }
342349
343350 var $slider = this.$slider = $( '<div/>' )
344 - .attr( 'id', idPrefix + this.name );
 351+ .attr( 'id', idPrefix + this.desc.name );
345352
346353 var options = {
347354 min: desc.min,
348355 max: desc.max,
349 - value: desc.value
 356+ value: value
350357 };
351358
352359 if ( typeof desc.step != 'undefined' ) {
@@ -357,34 +364,37 @@
358365 this.$p.append( $slider );
359366 }
360367
361 - RangeField.prototype.getValue = function() {
362 - return this.$slider.slider( 'value' );
 368+ RangeField.prototype.getValues = function() {
 369+ var res = {};
 370+ res[this.desc.name] = this.$slider.slider( 'value' );
 371+ return res;
363372 };
364373
365374
366375 //A field with a textbox with a datepicker
367376 DateField.prototype = object( LabelField.prototype );
368377 DateField.prototype.constructor = DateField;
369 - function DateField( $form, name, desc ){
370 - LabelField.call( this, $form, name, desc );
 378+ function DateField( $form, desc, values ){
 379+ LabelField.call( this, $form, desc, values );
371380
372 - if ( typeof desc.value == 'undefined' ) {
373 - $.error( "desc.value is invalid" );
 381+ var value = values[this.desc.name];
 382+ if ( typeof value == 'undefined' ) {
 383+ $.error( "value is invalid" );
374384 }
375385
376386 var date;
377 - if ( desc.value !== null ) {
378 - date = new Date( desc.value );
 387+ if ( value !== null ) {
 388+ date = new Date( value );
379389
380390 if ( !isFinite( date ) ) {
381 - $.error( "desc.value is invalid" );
 391+ $.error( "value is invalid" );
382392 }
383393 }
384394
385395 this.$text = $( '<input/>' )
386396 .attr( 'type', 'text' )
387 - .attr( 'id', idPrefix + this.name )
388 - .attr( 'name', idPrefix + this.name )
 397+ .attr( 'id', idPrefix + this.desc.name )
 398+ .attr( 'name', idPrefix + this.desc.name )
389399 .datepicker( {
390400 onSelect: function() {
391401 //Force validation, so that a previous 'invalid' state is removed
@@ -392,7 +402,7 @@
393403 }
394404 } );
395405
396 - if ( desc.value !== null ) {
 406+ if ( value !== null ) {
397407 this.$text.datepicker( 'setDate', date );
398408 }
399409
@@ -400,25 +410,28 @@
401411 this.$p.append( this.$text );
402412 }
403413
404 - DateField.prototype.getValue = function() {
405 - var d = this.$text.datepicker( 'getDate' );
 414+ DateField.prototype.getValues = function() {
 415+ var d = this.$text.datepicker( 'getDate' ),
 416+ res = {};
406417
407418 if ( d === null ) {
408419 return null;
409420 }
410421
411422 //UTC date in ISO 8601 format [YYYY]-[MM]-[DD]T[hh]:[mm]:[ss]Z
412 - return '' + pad( d.getUTCFullYear(), 4 ) + '-' +
 423+ res[this.desc.name] = '' +
 424+ pad( d.getUTCFullYear(), 4 ) + '-' +
413425 pad( d.getUTCMonth() + 1, 2 ) + '-' +
414426 pad( d.getUTCDate(), 2 ) + 'T' +
415427 pad( d.getUTCHours(), 2 ) + ':' +
416428 pad( d.getUTCMinutes(), 2 ) + ':' +
417429 pad( d.getUTCSeconds(), 2 ) + 'Z';
 430+ return res;
418431 };
419432
420433 DateField.prototype.getValidationSettings = function() {
421434 var settings = LabelField.prototype.getValidationSettings.call( this ),
422 - fieldId = idPrefix + this.name;
 435+ fieldId = idPrefix + this.desc.name;
423436
424437 settings.rules[fieldId] = {
425438 "datePicker": true
@@ -436,20 +449,21 @@
437450
438451 ColorField.prototype = object( LabelField.prototype );
439452 ColorField.prototype.constructor = ColorField;
440 - function ColorField( $form, name, desc ){
441 - LabelField.call( this, $form, name, desc );
 453+ function ColorField( $form, desc, values ){
 454+ LabelField.call( this, $form, desc, values );
442455
443 - if ( typeof desc.value == 'undefined' ) {
444 - $.error( "desc.value is invalid" );
 456+ var value = values[this.desc.name];
 457+ if ( typeof value == 'undefined' ) {
 458+ $.error( "value is invalid" );
445459 }
446460
447461 this.$text = $( '<input/>' )
448462 .attr( 'type', 'text' )
449 - .attr( 'id', idPrefix + this.name )
450 - .attr( 'name', idPrefix + this.name )
 463+ .attr( 'id', idPrefix + this.desc.name )
 464+ .attr( 'name', idPrefix + this.desc.name )
451465 .addClass( 'colorpicker-input' )
452 - .val( desc.value )
453 - .css( 'background-color', desc.value )
 466+ .val( value )
 467+ .css( 'background-color', value )
454468 .focus( function() {
455469 $( '<div/>' )
456470 .attr( 'id', 'colorpicker' )
@@ -484,7 +498,7 @@
485499
486500 ColorField.prototype.getValidationSettings = function() {
487501 var settings = LabelField.prototype.getValidationSettings.call( this ),
488 - fieldId = idPrefix + this.name;
 502+ fieldId = idPrefix + this.desc.name;
489503
490504 settings.rules[fieldId] = {
491505 "color": true
@@ -492,10 +506,12 @@
493507 return settings;
494508 }
495509
496 - ColorField.prototype.getValue = function() {
497 - var color = $.colorUtil.getRGB( this.$text.val() );
498 - return '#' + pad( color[0].toString( 16 ), 2 ) +
 510+ ColorField.prototype.getValues = function() {
 511+ var color = $.colorUtil.getRGB( this.$text.val() ),
 512+ res = {}
 513+ res[this.desc.name] = '#' + pad( color[0].toString( 16 ), 2 ) +
499514 pad( color[1].toString( 16 ), 2 ) + pad( color[2].toString( 16 ), 2 );
 515+ return res;
500516 };
501517
502518 //If a click happens outside the colorpicker while it is showed, remove it
@@ -559,7 +575,6 @@
560576 for ( var i = 0; i < description.fields.length; i++ ) {
561577 //TODO: validate fieldName
562578 var field = description.fields[i],
563 - fieldName = field.name,
564579 FieldConstructor = validFieldTypes[field.type];
565580
566581 if ( typeof FieldConstructor != 'function' ) {
@@ -569,7 +584,7 @@
570585
571586 var f;
572587 try {
573 - f = new FieldConstructor( $form, fieldName, field );
 588+ f = new FieldConstructor( $form, field, options.values );
574589 } catch ( e ) {
575590 mw.log( e );
576591 return null; //constructor failed, wrong syntax in field description
@@ -610,7 +625,7 @@
611626
612627 for ( var i = 0; i < data.fields.length; i++ ) {
613628 var f = data.fields[i];
614 - result[f.getName()] = f.getValue();
 629+ $.extend( result, f.getValues() );
615630 }
616631
617632 return result;
Index: branches/salvatoreingala/Gadgets/ui/resources/ext.gadgets.preferences.js
@@ -57,17 +57,21 @@
5858 dataType: "json", // response type
5959 success: function( response ) {
6060
61 - if ( typeof response.getgadgetprefs != 'object' ) {
 61+ if ( typeof response.description != 'object'
 62+ || typeof response.values != 'object')
 63+ {
6264 alert( mw.msg( 'gadgets-unexpected-error' ) )
6365 return;
6466 }
6567
6668 //Create and show dialog
6769
68 - var prefs = response.getgadgetprefs;
 70+ var prefsDescription = response.description;
 71+ var values = response.values;
6972
70 - var dialogBody = $( prefs ).formBuilder( {
71 - gadget: gadget
 73+ var dialogBody = $( prefsDescription ).formBuilder( {
 74+ gadget: gadget,
 75+ values: values
7276 } );
7377
7478 $( dialogBody ).submit( function() {

Status & tagging log