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