http://www.mediawiki.org/wiki/Special:Code/MediaWiki/94150

Revision: 94150
Author:   salvatoreingala
Date:     2011-08-10 11:23:16 +0000 (Wed, 10 Aug 2011)
Log Message:
-----------
Added preferences of type 'composite'.

Modified Paths:
--------------
    branches/salvatoreingala/Gadgets/Gadgets_tests.php
    branches/salvatoreingala/Gadgets/backend/GadgetPrefs.php
    branches/salvatoreingala/Gadgets/ui/resources/jquery.formBuilder.css
    branches/salvatoreingala/Gadgets/ui/resources/jquery.formBuilder.js

Modified: branches/salvatoreingala/Gadgets/Gadgets_tests.php
===================================================================
--- branches/salvatoreingala/Gadgets/Gadgets_tests.php  2011-08-10 10:11:24 UTC 
(rev 94149)
+++ branches/salvatoreingala/Gadgets/Gadgets_tests.php  2011-08-10 11:23:16 UTC 
(rev 94150)
@@ -553,6 +553,69 @@
                }
        }
 
+       //Tests for 'composite' type fields
+       function testPrefsDescriptionsComposite() {
+               $correct = array(
+                       'fields' => array(
+                               array(
+                                       'name' => 'foo',
+                                       'type' => 'composite',
+                                       'fields' => array(
+                                               array(
+                                                       'name' => 'bar',
+                                                       'type' => 'boolean',
+                                                       'label' => '@msg1',
+                                                       'default' => true
+                                               ),
+                                               array(
+                                                       'name' => 'car',
+                                                       'type' => 'color',
+                                                       'label' => '@msg2',
+                                                       'default' => '#123456'
+                                               )
+                                       )
+                               )
+                       )
+               );
+               
+               $this->assertTrue( GadgetPrefs::isPrefsDescriptionValid( 
$correct ) );
+               $this->assertEquals(
+                       GadgetPrefs::getDefaults( $correct ),
+                       array( 'foo' => array( 'bar' => true, 'car' => 
'#123456' ) )
+               );
+               $this->assertEquals( GadgetPrefs::getMessages( $correct ), 
array( 'msg1', 'msg2' ) );
+               
+               $this->assertTrue( GadgetPrefs::checkPrefsAgainstDescription(
+                       $correct,
+                       array( 'foo' => array( 'bar' => false, 'car' => 
'#00aaff' ) )
+               ) );
+
+               $this->assertFalse( GadgetPrefs::checkPrefsAgainstDescription(
+                       $correct,
+                       array( 'foo' => array( 'bar' => null, 'car' => 
'#00aaff' ) )
+               ) );
+
+               $this->assertFalse( GadgetPrefs::checkPrefsAgainstDescription(
+                       $correct,
+                       array( 'foo' => array( 'bar' => false, 'car' => 
'#00aafz' ) )
+               ) );
+
+               $this->assertFalse( GadgetPrefs::checkPrefsAgainstDescription(
+                       $correct,
+                       array( 'bar' => false, 'car' => '#00aaff' )
+               ) );
+
+               $prefs = array(
+                       'foo' => array(
+                               'bar' => false,
+                               'car' => null //wrong
+                       )
+               );
+               
+               GadgetPrefs::matchPrefsWithDescription( $correct, $prefs );
+               //Check if only the wrong subfield has been reset to default 
value
+               $this->assertEquals( $prefs, array( 'foo' => array( 'bar' => 
false, 'car' => '#123456' ) ) );
+       }
        
        //Data provider to be able to reuse a complex preference description 
for several tests.
        function prefsDescProvider() {

Modified: branches/salvatoreingala/Gadgets/backend/GadgetPrefs.php
===================================================================
--- branches/salvatoreingala/Gadgets/backend/GadgetPrefs.php    2011-08-10 
10:11:24 UTC (rev 94149)
+++ branches/salvatoreingala/Gadgets/backend/GadgetPrefs.php    2011-08-10 
11:23:16 UTC (rev 94150)
@@ -26,6 +26,12 @@
         *   If omitted (for "simple fields"), the default flattener is used.
         * - 'checker', only for "simple" fields, is the name of a function 
that takes a preference description and
         *   a preference value, and returns true if that value passes 
validation, false otherwise.
+        * - 'matcher', only for "simple" fields, is the name of a function 
that takes the description of a field $prefDescription,
+        *   an array of preference values $prefs and the name of a preference 
$preferenceName and returns an array where
+        *   $prefs[$prefName] is changed in a way that passes validation. If 
omitted, the default action is to set $prefs[$prefName]
+        *   to $prefDescription['default'].
+        * - 'getDefault', only for "simple" fields, if a function che takes 
one argument, the descriptio of the field, and
+        *   returns its default value; if omitted, the value of the 'default' 
field is returned.
         * - 'getMessages', if specified, is the name of a function that takes 
a valid description of a field and returns
         *   a list of messages referred to by it. If omitted, only the "label" 
field is returned (if it is a message).
         */
@@ -213,6 +219,23 @@
                        ),
                        'getMessages' => 'GadgetPrefs::getBundleMessages',
                        'flattener' => 'GadgetPrefs::flattenBundleDefinition'
+               ),
+               'composite' => array(
+                       'description' => array(
+                               'name' => array(
+                                       'isMandatory' => true,
+                                       'validator' => 
'GadgetPrefs::isValidPreferenceName'
+                               ),
+                               'fields' => array(
+                                       'isMandatory' => true,
+                                       'validator' => 'is_array'
+                               )
+                       ),
+                       'validator' => 'GadgetPrefs::validateSectionDefinition',
+                       'getMessages' => 'GadgetPrefs::getCompositeMessages',
+                       'getDefault' => 'GadgetPrefs::getCompositeDefault',
+                       'checker' => 'GadgetPrefs::checkCompositePref',
+                       'matcher' => 'GadgetPrefs::matchCompositePref'
                )
        );
        
@@ -470,12 +493,20 @@
                        foreach ( $flt as $prefName => $prefDescription ) {
                                //Finally, check that the 'default' fields 
exists and is valid
                                //for all preferences encoded by this field
-                               if ( !array_key_exists( 'default', 
$prefDescription ) ) {
-                                       return false;
+                               
+                               $type = $prefDescription['type'];
+                               if ( isset( 
self::$prefsDescriptionSpecifications[$type]['getDefault'] ) ) {
+                                       $getDefault = 
self::$prefsDescriptionSpecifications[$type]['getDefault'];
+                                       $value = call_user_func( $getDefault, 
$prefDescription );
+                               } else {
+                                       if ( !array_key_exists( 'default', 
$prefDescription ) ) {
+                                               return false;
+                                       }
+                                       $value = $prefDescription['default'];
                                }
                                
-                               $prefs = array( 'dummy' => 
$prefDescription['default'] );
-                               if ( !self::checkSinglePref( $prefDescription, 
$prefs, 'dummy' ) ) {
+                               $prefs = array( $prefName => $value );
+                               if ( !self::checkSinglePref( $prefDescription, 
$prefs, $prefName ) ) {
                                        return false;
                                }
                        }
@@ -691,8 +722,24 @@
                return is_string( $value ) && preg_match( '/^#[0-9a-f]{6}$/', 
$value );
        }
 
+       //Checker for 'composite' preferences
+       private static function checkCompositePref( $prefDescription, $value ) {
+               if ( !is_array( $value ) ) {
+                       return false;
+               }
 
+               $flattened = self::flattenPrefsDescription( $prefDescription );
 
+               foreach ( $flattened as $subPrefName => $subPrefDescription ) {
+                       if ( !array_key_exists( $subPrefName, $value ) ||
+                               !self::checkSinglePref( $subPrefDescription, 
$value, $subPrefName ) )
+                       {
+                               return false;
+                       }
+               }
+               return true;
+       }
+
        /**
         * Checks if $prefs is an array of preferences that passes validation.
         * It is assumed that $prefsDescription is a valid description of 
preferences.
@@ -724,6 +771,17 @@
                return true;
        }
 
+       //Matcher for 'composite' type preferences
+       private static function matchCompositePref( $prefDescription, $prefs, 
$prefName ) {
+               if ( !array_key_exists( $prefName, $prefs ) || !is_array( 
$prefs[$prefName] ) ) {
+                       $prefs[$prefName] = array();
+               }
+               
+               self::matchPrefsWithDescription( $prefDescription, 
$prefs[$prefName] );
+               
+               return $prefs;
+       }
+
        /**
         * Fixes $prefs so that it matches the description given by 
$prefsDescription.
         * All values of $prefs that fail validation are replaced with default 
values.
@@ -740,7 +798,15 @@
                foreach ( $flattenedPrefs as $prefDescription ) {
                        $prefName = $prefDescription['name'];
                        if ( !self::checkSinglePref( $prefDescription, $prefs, 
$prefName ) ) {
-                               $prefs[$prefName] = $prefDescription['default'];
+                               $type = $prefDescription['type'];
+                               if ( isset( 
self::$prefsDescriptionSpecifications[$type]['matcher'] ) ) {
+                                       //Use specific matcher for this type
+                                       $matcher = 
self::$prefsDescriptionSpecifications[$type]['matcher'];
+                                       $prefs = call_user_func( $matcher, 
$prefDescription, $prefs, $prefName );
+                               } else {
+                                       //Default matcher, just use 'default' 
value
+                                       $prefs[$prefName] = 
$prefDescription['default'];
+                               }
                        }
                        $validPrefs[$prefName] = true;
                }
@@ -831,4 +897,15 @@
                }
                return array_unique( $msgs );
        }
+       
+       //Returns the default value of a 'composite' field, that is the object 
of the
+       //default values of its subfields.
+       private static function getCompositeDefault( $prefDescription ) {
+               return self::getDefaults( $prefDescription );
+       }
+       
+       //Returns the messages for a 'composite' field description
+       private static function getCompositeMessages( $prefDescription ) {
+               return self::getMessages( $prefDescription );
+       }       
 }

Modified: branches/salvatoreingala/Gadgets/ui/resources/jquery.formBuilder.css
===================================================================
--- branches/salvatoreingala/Gadgets/ui/resources/jquery.formBuilder.css        
2011-08-10 10:11:24 UTC (rev 94149)
+++ branches/salvatoreingala/Gadgets/ui/resources/jquery.formBuilder.css        
2011-08-10 11:23:16 UTC (rev 94150)
@@ -111,3 +111,6 @@
        margin-left: 6px;
 }
 
+.formbuilder-slot-editable .formbuilder-slot-type-composite {
+       padding: 1em;
+}

Modified: branches/salvatoreingala/Gadgets/ui/resources/jquery.formBuilder.js
===================================================================
--- branches/salvatoreingala/Gadgets/ui/resources/jquery.formBuilder.js 
2011-08-10 10:11:24 UTC (rev 94149)
+++ branches/salvatoreingala/Gadgets/ui/resources/jquery.formBuilder.js 
2011-08-10 11:23:16 UTC (rev 94150)
@@ -50,6 +50,12 @@
                return typeof val == 'number' && val === Math.floor( val );
        }
 
+       function isValidPreferenceName( name ) {
+               return typeof name == 'string'
+                       && /^[a-zA-Z_][a-zA-Z0-9_]*$/.test( name )
+                       && name.length <= 40;
+       }
+
        function testOptional( value, element ) {
                var rules = $( element ).rules();
                if ( typeof rules.required == 'undefined' || rules.required === 
false ) {
@@ -229,7 +235,15 @@
                                                }
                                        ]
                                }, options )
-               }
+               },
+               "composite": [ {
+                       "name": "name",
+                       "type": "string",
+                       "label": "name",
+                       "required": true,
+                       "maxlength": 40,
+                       "default": ""
+               } ]
        };
 
        /* Basic interface for fields */
@@ -310,12 +324,9 @@
                LabelField.call( this, desc, options );
                
                //Validate the 'name' member
-               if ( typeof desc.name != 'string' || 
/^[a-zA-Z_][a-zA-Z0-9_]*$/.test( desc.name ) === false ) {
+               if ( !isValidPreferenceName( desc.name ) ) {
                        $.error( 'invalid name' );
                }
-               if ( typeof desc.name.length > 40 ) {
-                       $.error( 'name must be no longer than 40 characters' );
-               }
 
                this.$label.attr('for', this.options.idPrefix + this.desc.name 
);
 
@@ -951,6 +962,7 @@
                                                                $( this 
).dialog( "close" );
                                                                
self._createFieldDialog( {
                                                                        type: 
values.type,
+                                                                       
oldDescription: params.oldDescription,
                                                                        
callback: params.callback
                                                                } );
                                                        }
@@ -994,6 +1006,8 @@
                var dlg = $( '<div/>' );
                var form = $( description ).formBuilder( {
                        values: values
+               } ).submit( function() {
+                       return false; //prevent form submission
                } ).appendTo( dlg );
                
                dlg.dialog( {
@@ -1026,6 +1040,16 @@
                                                        //Try to create the 
field. In case of error, warn the user.
                                                        fieldDescription.type = 
type;
                                                        
+                                                       if ( typeof 
params.oldDescription != 'undefined' ) {
+                                                               //If there are 
values in the old description that cannot be set by
+                                                               //the dialog, 
don't lose them (e.g.: 'fields' member in composite fields).
+                                                               $.each( 
params.oldDescription, function( key, value ) {
+                                                                       if ( 
typeof fieldDescription[key] == 'undefined' ) {
+                                                                               
fieldDescription[key] = value;
+                                                                       }
+                                                               } );
+                                                       }
+                                                       
                                                        var FieldConstructor = 
validFieldTypes[type];
                                                        var field;
                                                        
@@ -1104,6 +1128,7 @@
                                                        
self._createFieldDialog( {
                                                                type: 
field.getDesc().type,
                                                                values: 
field.getDesc(),
+                                                               oldDescription: 
field.getDesc(),
                                                                callback: 
function( newField ) {
                                                                        if ( 
newField !== null ) {
                                                                                
//check that there are no duplicate preference names
@@ -1458,6 +1483,55 @@
 
        validFieldTypes["bundle"] = BundleField;
 
+
+       /* A field for 'composite' fields */
+
+       CompositeField.prototype = object( EmptyField.prototype );
+       CompositeField.prototype.constructor = CompositeField;
+       function CompositeField( desc, options ) {
+               EmptyField.call( this, desc, options );
+               
+               //Validate the 'name' member
+               if ( !isValidPreferenceName( desc.name ) ) {
+                       $.error( 'invalid name' );
+               }
+               
+               if ( !$.isArray( desc.fields ) ) {
+                       //Don't throw an error, to allow creating empty 
sections in the editor
+                       desc.fields = [];
+               }
+               
+               //TODO: add something to easily visually identify 'composite' 
fields during editing
+               
+               var sectionOptions = $.extend( {}, options );
+               
+               //Add another chunk to the prefix, to ensure uniqueness
+               sectionOptions.idPrefix += desc.name + '-';
+               if ( typeof options.values != 'undefined' ) {
+                       //Tell the section the actual values it should show
+                       sectionOptions.values = options.values[desc.name];
+               }
+               
+               this._section = new SectionField( desc, sectionOptions );
+               this.$div.append( this._section.getElement() );
+       }
+
+       CompositeField.prototype.getDesc = function( useValuesAsDefaults ) {
+               var desc = this.desc;
+               desc.fields = this._section.getDesc( useValuesAsDefaults 
).fields;
+               return desc;
+       };
+
+       CompositeField.prototype.getValues = function() {
+               return pair( this.desc.name, this._section.getValues() );
+       };
+       
+       CompositeField.prototype.getValidationSettings = function() {
+               return this._section.getValidationSettings();
+       };      
+
+       validFieldTypes["composite"] = CompositeField;
+
        /* Public methods */
        
        /**


_______________________________________________
MediaWiki-CVS mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs

Reply via email to