r89351 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r89350‎ | r89351 | r89352 >
Date:17:37, 2 June 2011
Author:salvatoreingala
Status:deferred (Comments)
Tags:
Comment:
- Changed the AJAX interaction: now the AJAX request returns an object with all info to build the dialog, instead of the dialog code.
- added the jquery.validate plugin (from http://bassistance.de/jquery-plugins/jquery-plugin-validation/)
- added jquery.formBuilder, written on top of jquery.validate, to manage dialog building and client-side validation
- added server-side validation of preference descriptions
- implemented the "assume-default" policy for gadget options which fail validation
Modified paths:
  • /branches/salvatoreingala/Gadgets/Gadgets.php (modified) (history)
  • /branches/salvatoreingala/Gadgets/GadgetsAjax.php (modified) (history)
  • /branches/salvatoreingala/Gadgets/Gadgets_body.php (modified) (history)
  • /branches/salvatoreingala/Gadgets/modules/ext.gadgets.preferences.js (modified) (history)
  • /branches/salvatoreingala/Gadgets/modules/jquery.formBuilder.js (added) (history)
  • /branches/salvatoreingala/Gadgets/modules/jquery.validate.js (added) (history)

Diff [purge]

Index: branches/salvatoreingala/Gadgets/Gadgets_body.php
@@ -160,9 +160,10 @@
161161 * @param &$options
162162 */
163163 public static function userLoadOptions( $user, &$options ) {
164 - //Remove gadget-*-config options, since they must not be delivered like other user preferences
 164+ //Remove gadget-*-config options, since they must not be delivered
 165+ //via mw.user.options like other user preferences
165166 foreach ( $options as $option => $value ){
166 - //TODO: Regexsp not coherent with current gadget's naming rules
 167+ //TODO: Regexp not coherent with current gadget's naming rules
167168 if ( preg_match( '/gadget-[a-zA-Z][a-zA-Z0-9_]*-config/', $option ) ) {
168169 //TODO: cache them before unsetting
169170 unset( $options[$option] );
@@ -206,6 +207,7 @@
207208 }
208209 }
209210
 211+
210212 /**
211213 * Wrapper for one gadget.
212214 */
@@ -227,20 +229,66 @@
228230 $category;
229231
230232
231 - //Mandatory arguments for any kind of preferences
232 - private static $prefsMandatoryArgs = array(
233 - 'type',
234 - 'default'
235 - );
 233+ //Syntax specifications of preference description language
 234+ private static $prefsDescriptionSpecifications = array(
 235+ 'boolean' => array(
 236+ 'default' => array(
 237+ 'isMandatory' => true,
 238+ 'checker' => 'is_bool'
 239+ ),
 240+ 'label' => array(
 241+ 'isMandatory' => true,
 242+ 'checker' => 'is_string'
 243+ )
 244+ ),
 245+ 'string' => array(
 246+ 'default' => array(
 247+ 'isMandatory' => true,
 248+ 'checker' => 'is_string'
 249+ ),
 250+ 'label' => array(
 251+ 'isMandatory' => true,
 252+ 'checker' => 'is_string'
 253+ ),
 254+ 'required' => array(
 255+ 'isMandatory' => false,
 256+ 'checker' => 'is_bool'
 257+ ),
 258+ 'minlength' => array(
 259+ 'isMandatory' => false,
 260+ 'checker' => 'is_integer'
 261+ ),
 262+ 'maxlength' => array(
 263+ 'isMandatory' => false,
 264+ 'checker' => 'is_integer'
 265+ )
 266+
 267+ )
 268+ );
236269
237 - //Other mandatory arguments for specific types
238 - private static $prefsMandatoryArgsByType = array(
239 - 'select' => array( 'options' ),
240 - 'selectorother' => array( 'options' ),
241 - 'selectandother' => array( 'options' ),
242 - 'multiselect' => array( 'options' )
243 - );
 270+ //Type-specific checkers for finer validation
 271+ private static $typeCheckers = array(
 272+ 'string' => array( 'Gadget', 'checkStringOption' )
 273+ );
 274+
 275+ private static function checkStringOption( $option ) {
 276+ if ( isset( $option['minlength'] ) && $option['minlength'] < 0 ) {
 277+ return false;
 278+ }
244279
 280+ if ( isset( $option['maxlength'] ) && $option['maxlength'] <= 0 ) {
 281+ return false;
 282+ }
 283+
 284+ if ( isset( $option['minlength']) && isset( $option['maxlength'] ) ) {
 285+ if ( $option['minlength'] > $option['maxlength'] ) {
 286+ return false;
 287+ }
 288+ }
 289+
 290+ return true;
 291+ }
 292+
245293 /**
246294 * Creates an instance of this class from definition in MediaWiki:Gadgets-definition
247295 * @param $definition String: Gadget definition
@@ -568,28 +616,75 @@
569617
570618 //TODO: put the following static methods somewhere else
571619
572 - public static function isGadgetPrefsDescriptionValid( $prefsJson ) {
573 - $prefs = FormatJson::decode( $prefsJson, true );
 620+ //Checks if the given description of the preferences is valid
 621+ public static function isGadgetPrefsDescriptionValid( &$prefsDescriptionJson ) {
 622+ $prefsDescription = FormatJson::decode( $prefsDescriptionJson, true );
574623
575 - if ( $prefs === null ) {
 624+ if ( $prefsDescription === null || !isset( $prefsDescription['fields'] ) ) {
576625 return false;
577626 }
 627+
 628+ //Count of mandatory members for each type
 629+ $mandatoryCount = array();
 630+ foreach ( self::$prefsDescriptionSpecifications as $type => $typeSpec ) {
 631+ $mandatoryCount[$type] = 0;
 632+ foreach ( $typeSpec as $fieldName => $fieldSpec ) {
 633+ if ( $fieldSpec['isMandatory'] === true ) {
 634+ ++$mandatoryCount[$type];
 635+ }
 636+ }
 637+ }
578638
579 - //TODO: improve validation
580 - foreach ( $prefs as $option => $optionDefinition ) {
581 - foreach ( self::$prefsMandatoryArgs as $arg ) {
582 - if ( !isset( $optionDefinition[$arg] ) ) {
 639+ //TODO: validation of members other than $prefs['fields']
 640+
 641+ foreach ( $prefsDescription['fields'] as $option => $optionDefinition ) {
 642+
 643+ //Check if 'type' is set and valid
 644+ if ( !isset( $optionDefinition['type'] ) ) {
 645+ return false;
 646+ }
 647+
 648+ $type = $optionDefinition['type'];
 649+
 650+ if ( !isset( self::$prefsDescriptionSpecifications[$type] ) ) {
 651+ return false;
 652+ }
 653+
 654+ //TODO: check $option name compliance
 655+
 656+
 657+ //Check if all fields satisfy specification
 658+ $typeSpec = self::$prefsDescriptionSpecifications[$type];
 659+ $count = 0; //count of present mandatory members
 660+ foreach ( $optionDefinition as $fieldName => $fieldValue ) {
 661+
 662+ if ( strcmp( $fieldName, 'type' ) == 0) {
 663+ continue; //'type' must not be checked
 664+ }
 665+
 666+ if ( !isset( $typeSpec[$fieldName] ) ) {
583667 return false;
584668 }
 669+
 670+ if ( $typeSpec[$fieldName]['isMandatory'] ) {
 671+ ++$count;
 672+ }
 673+
 674+ $checker = $typeSpec[$fieldName]['checker'];
 675+
 676+ if ( !$checker( $fieldValue ) ) {
 677+ return false;
 678+ }
585679 }
586680
587 - $type = $option['type'];
 681+ if ( $count != $mandatoryCount[$type] ) {
 682+ return false; //not all mandatory members are given
 683+ }
588684
589 - if ( isset( self::$prefsMandatoryArgsByType[$type] ) ) {
590 - foreach ( self::$prefsMandatoryArgsByType[$type] as $arg ) {
591 - if ( !isset( $optionDefinition[$arg] ) ) {
592 - return false;
593 - }
 685+ if ( isset( self::$typeCheckers[$type] ) ) {
 686+ //Call type-specific checker for finer validation
 687+ if ( !call_user_func( self::$typeCheckers[$type], $optionDefinition ) ) {
 688+ return false;
594689 }
595690 }
596691 }
@@ -628,6 +723,69 @@
629724 return null; //gadget not found
630725 }
631726
 727+ //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 ) ) {
 731+ return false;
 732+ }
 733+
 734+ switch ( $prefDescription['type'] ) {
 735+ case 'boolean':
 736+ return is_bool( $pref );
 737+ case 'string':
 738+ if ( !is_string( $pref ) ) {
 739+ return false;
 740+ }
 741+
 742+ $len = strlen( $pref );
 743+
 744+ //Checks the "required" option, if present
 745+ $required = isset( $prefDescription['required'] ) ? $prefDescription['required'] : true;
 746+ if ( $required === true && $len == 0 ) {
 747+ return false;
 748+ } elseif ( $required === false && $len == 0 ) {
 749+ return true; //overriding 'minlength'
 750+ }
 751+
 752+ //Checks the "minlength" option, if present
 753+ $minlength = isset( $prefDescription['minlength'] ) ? $prefDescription['minlength'] : 0;
 754+ if ( is_integer( $minlength ) && $len < $minlength ){
 755+ return false;
 756+ }
 757+
 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 ){
 761+ return false;
 762+ }
 763+
 764+ return true;
 765+ default:
 766+ return false; //unexisting type
 767+ }
 768+ }
 769+
 770+ //Returns true if $prefs is an array of preferences that passes validation
 771+ private static function checkPrefsAgainstDescription( &$prefsDescription, &$prefs ) {
 772+ foreach ( $prefsDescription['fields'] as $prefName => $prefDescription ) {
 773+ if ( !self::checkSinglePref( $prefDescription, $prefs[$prefName] ) ) {
 774+ return false;
 775+ }
 776+ }
 777+ return true;
 778+ }
 779+
 780+ //Fixes $prefs so that it matches the description given by $prefsDescription.
 781+ //All values of $prefs that fail validation are replaced with default values.
 782+ private static function matchPrefsWithDescription( &$prefsDescription, &$prefs ) {
 783+ foreach ( $prefsDescription['fields'] as $prefName => $prefDescription ) {
 784+ if ( !self::checkSinglePref( $prefDescription, $prefs[$prefName] ) ) {
 785+ $prefs[$prefName] = $prefDescription['default'];
 786+ }
 787+ }
 788+ }
 789+
632790 //Get user's preferences for a specific gadget
633791 public static function getUserPrefs( $user, $gadget ) {
634792 //TODO: cache!
@@ -664,15 +822,31 @@
665823
666824 $userPrefs = FormatJson::decode( $userPrefsJson, true );
667825
668 - //TODO: validate against description, fix mismatches
 826+ self::matchPrefsWithDescription( $prefsDescription, $userPrefs );
669827
670828 return $userPrefs;
671829 }
672830
673 - //Set user's preferences for a specific gadget
674 - public static function setUserPrefs( $user, $gadget, $preferences ) {
675 - //TODO: proper param checking
 831+ //Set user's preferences for a specific gadget.
 832+ //Returns false if preferences are rejected (that is, they don't pass validation)
 833+ public static function setUserPrefs( $user, $gadget, &$preferences ) {
676834
 835+ $prefsDescriptionJson = Gadget::getGadgetPrefsDescription( $gadget );
 836+
 837+ if ( $prefsDescriptionJson === null || $prefsDescriptionJson === '' ) {
 838+ return false; //nothing to save
 839+ }
 840+
 841+ $prefsDescription = FormatJson::decode( $prefsDescriptionJson, true );
 842+
 843+ if ( !self::checkPrefsAgainstDescription( $prefsDescription, $preferences ) ) {
 844+ return false; //validation failed
 845+ }
 846+
 847+ //TODO: should remove preferences that are equal to their default?
 848+
 849+ //Save preferences to DB
 850+
677851 $preferencesJson = FormatJson::encode( $preferences );
678852
679853 $dbw = wfGetDB( DB_MASTER );
@@ -711,6 +885,8 @@
712886
713887 //Invalidate cache and update user_touched
714888 $user->invalidateCache( true );
 889+
 890+ return true;
715891 }
716892
717893 }
@@ -749,7 +925,6 @@
750926 * @return Array: Names of resources this module depends on
751927 */
752928 public function getDependencies() {
753 - //return array_merge( $this->dependencies, array( 'mediawiki.user' ) );
754929 return $this->dependencies;
755930 }
756931
@@ -766,11 +941,11 @@
767942 //configuration object (or to "window" for non-configurable gadgets)
768943 $header = '(function(){';
769944
 945+ //TODO: it may be nice add other metadata for the gadget
770946 $boundObject = array( 'config' => $prefs );
771947
772948 if ( $prefs !== NULL ) {
773949 //Bind configuration object to "this".
774 - //TODO: would be nice add other metadata for the gadget
775950 $footer = '}).' . Xml::encodeJsCall( 'apply',
776951 array( $boundObject, array() )
777952 ) . ';';
Index: branches/salvatoreingala/Gadgets/Gadgets.php
@@ -55,16 +55,30 @@
5656 $wgAPIListModules['gadgetcategories'] = 'ApiQueryGadgetCategories';
5757 $wgAPIListModules['gadgets'] = 'ApiQueryGadgets';
5858
59 -$wgAjaxExportList[] = 'GadgetsAjax::getUI';
 59+$wgAjaxExportList[] = 'GadgetsAjax::getPreferences';
6060 $wgAjaxExportList[] = 'GadgetsAjax::setPreferences';
6161
6262 $wgResourceModules['ext.gadgets'] = array(
6363 'class' => 'GadgetsGlobalModule'
6464 );
6565
 66+$wgResourceModules['jquery.validate'] = array(
 67+ 'scripts' => array( 'jquery.validate.js' ), //TODO: include i18n scripts?
 68+ 'dependencies' => array( 'jquery' ),
 69+ 'localBasePath' => $dir . 'modules/',
 70+ 'remoteExtPath' => 'Gadgets/modules'
 71+);
 72+
 73+$wgResourceModules['jquery.formBuilder'] = array(
 74+ 'scripts' => array( 'jquery.formBuilder.js' ),
 75+ 'dependencies' => array( 'jquery', 'jquery.validate' ),
 76+ 'localBasePath' => $dir . 'modules/',
 77+ 'remoteExtPath' => 'Gadgets/modules'
 78+);
 79+
6680 $wgResourceModules['ext.gadgets.preferences'] = array(
6781 'scripts' => array( 'ext.gadgets.preferences.js' ),
68 - 'dependencies' => array( 'jquery', 'jquery.json', 'jquery.ui.dialog', 'mediawiki.htmlform', 'ext.gadgets' ),
 82+ 'dependencies' => array( 'jquery', 'jquery.json', 'jquery.ui.dialog', 'jquery.formBuilder', 'mediawiki.htmlform', 'ext.gadgets' ),
6983 'localBasePath' => $dir . 'modules/',
7084 'remoteExtPath' => 'Gadgets/modules'
7185 );
Index: branches/salvatoreingala/Gadgets/GadgetsAjax.php
@@ -22,7 +22,7 @@
2323 return true;
2424 }
2525
26 - public static function getUI( /*$args...*/ ) {
 26+ public static function getPreferences( /* args */ ) {
2727 //params are in the format "param|val"
2828 $args = func_get_args();
2929
@@ -47,30 +47,24 @@
4848 return '<err#>' . wfMsgExt( 'gadgets-ajax-wrongparams', 'parseinline' );
4949 }
5050
51 - $prefsJson = Gadget::getGadgetPrefsDescription( $gadget );
5251
53 - //If $gadget doesn't exists or it doesn't have preferences, something is wrong
54 - if ( $prefsJson === null || $prefsJson === '' ) {
 52+ $prefsDescriptionJson = Gadget::getGadgetPrefsDescription( $gadget );
 53+ $prefsDescription = FormatJson::decode( $prefsDescriptionJson, true );
 54+
 55+ if ( $prefsDescription === null ) {
 56+ //either the gadget doesn't exists or it exists but it has no prefs
5557 return '<err#>' . wfMsgExt( 'gadgets-ajax-wrongparams', 'parseinline' );
5658 }
57 -
58 - $prefs = FormatJson::decode( $prefsJson, true );
59 -
60 - //If it's not valid JSON, signal an error
61 - if ( $prefs === null ) {
62 - return '<err#>' . wfMsgExt( 'gadgets-ajax-wrongsyntax', 'parseinline' );
63 - }
64 -
65 - //TODO: options of "select" and similar fields cannot be passed as messages
66 - $form = new HTMLForm( $prefs, RequestContext::getMain() );
67 -
 59+
6860 $user = RequestContext::getMain()->getUser();
69 -
70 - $form->mFieldData = Gadget::getUserPrefs( $user, $gadget );
 61+ $userPrefs = Gadget::getUserPrefs( $user, $gadget );
7162
72 - //TODO: HTMLForm::getBody is not meant to be public, a refactoring is needed
73 - // (or a completely different solution)
74 - return $form->getBody();
 63+ //Add user preferences to preference description
 64+ foreach ( $userPrefs as $pref => $value ) {
 65+ $prefsDescription['fields'][$pref]['value'] = $value;
 66+ }
 67+
 68+ return FormatJson::encode( $prefsDescription );
7569 }
7670
7771 public static function setPreferences( /* args */ ) {
@@ -117,19 +111,12 @@
118112 return '<err#>' . wfMsgExt( 'gadgets-ajax-wrongparams', 'parseinline' );
119113 }
120114
121 - foreach ( $userPrefs as $pref => $value ) {
122 - if ( !isset( $prefsDescription[$pref] ) ){
123 - //Nonexisting configuration parameter; ignore it
124 - unset( $userPrefs[$pref] );
125 - } else {
126 - //TODO: convert values to proper type, check coherency with specification
127 - // and fix fields that don't pass validation
128 - }
129 - }
130 -
131115 $user = RequestContext::getMain()->getUser();
132 - Gadget::setUserPrefs( $user, $gadget, $userPrefs );
133116
134 - return 'true';
 117+ if ( Gadget::setUserPrefs( $user, $gadget, $userPrefs ) ) {
 118+ return 'true';
 119+ } else {
 120+ return 'false';
 121+ }
135122 }
136123 }
Index: branches/salvatoreingala/Gadgets/modules/jquery.validate.js
@@ -0,0 +1,1166 @@
 2+/**
 3+ * jQuery Validation Plugin 1.8.1
 4+ *
 5+ * http://bassistance.de/jquery-plugins/jquery-plugin-validation/
 6+ * http://docs.jquery.com/Plugins/Validation
 7+ *
 8+ * Copyright (c) 2006 - 2011 Jörn Zaefferer
 9+ *
 10+ * Dual licensed under the MIT and GPL licenses:
 11+ * http://www.opensource.org/licenses/mit-license.php
 12+ * http://www.gnu.org/licenses/gpl.html
 13+ */
 14+
 15+(function($) {
 16+
 17+$.extend($.fn, {
 18+ // http://docs.jquery.com/Plugins/Validation/validate
 19+ validate: function( options ) {
 20+
 21+ // if nothing is selected, return nothing; can't chain anyway
 22+ if (!this.length) {
 23+ options && options.debug && window.console && console.warn( "nothing selected, can't validate, returning nothing" );
 24+ return;
 25+ }
 26+
 27+ // check if a validator for this form was already created
 28+ var validator = $.data(this[0], 'validator');
 29+ if ( validator ) {
 30+ return validator;
 31+ }
 32+
 33+ validator = new $.validator( options, this[0] );
 34+ $.data(this[0], 'validator', validator);
 35+
 36+ if ( validator.settings.onsubmit ) {
 37+
 38+ // allow suppresing validation by adding a cancel class to the submit button
 39+ this.find("input, button").filter(".cancel").click(function() {
 40+ validator.cancelSubmit = true;
 41+ });
 42+
 43+ // when a submitHandler is used, capture the submitting button
 44+ if (validator.settings.submitHandler) {
 45+ this.find("input, button").filter(":submit").click(function() {
 46+ validator.submitButton = this;
 47+ });
 48+ }
 49+
 50+ // validate the form on submit
 51+ this.submit( function( event ) {
 52+ if ( validator.settings.debug )
 53+ // prevent form submit to be able to see console output
 54+ event.preventDefault();
 55+
 56+ function handle() {
 57+ if ( validator.settings.submitHandler ) {
 58+ if (validator.submitButton) {
 59+ // insert a hidden input as a replacement for the missing submit button
 60+ var hidden = $("<input type='hidden'/>").attr("name", validator.submitButton.name).val(validator.submitButton.value).appendTo(validator.currentForm);
 61+ }
 62+ validator.settings.submitHandler.call( validator, validator.currentForm );
 63+ if (validator.submitButton) {
 64+ // and clean up afterwards; thanks to no-block-scope, hidden can be referenced
 65+ hidden.remove();
 66+ }
 67+ return false;
 68+ }
 69+ return true;
 70+ }
 71+
 72+ // prevent submit for invalid forms or custom submit handlers
 73+ if ( validator.cancelSubmit ) {
 74+ validator.cancelSubmit = false;
 75+ return handle();
 76+ }
 77+ if ( validator.form() ) {
 78+ if ( validator.pendingRequest ) {
 79+ validator.formSubmitted = true;
 80+ return false;
 81+ }
 82+ return handle();
 83+ } else {
 84+ validator.focusInvalid();
 85+ return false;
 86+ }
 87+ });
 88+ }
 89+
 90+ return validator;
 91+ },
 92+ // http://docs.jquery.com/Plugins/Validation/valid
 93+ valid: function() {
 94+ if ( $(this[0]).is('form')) {
 95+ return this.validate().form();
 96+ } else {
 97+ var valid = true;
 98+ var validator = $(this[0].form).validate();
 99+ this.each(function() {
 100+ valid &= validator.element(this);
 101+ });
 102+ return valid;
 103+ }
 104+ },
 105+ // attributes: space seperated list of attributes to retrieve and remove
 106+ removeAttrs: function(attributes) {
 107+ var result = {},
 108+ $element = this;
 109+ $.each(attributes.split(/\s/), function(index, value) {
 110+ result[value] = $element.attr(value);
 111+ $element.removeAttr(value);
 112+ });
 113+ return result;
 114+ },
 115+ // http://docs.jquery.com/Plugins/Validation/rules
 116+ rules: function(command, argument) {
 117+ var element = this[0];
 118+
 119+ if (command) {
 120+ var settings = $.data(element.form, 'validator').settings;
 121+ var staticRules = settings.rules;
 122+ var existingRules = $.validator.staticRules(element);
 123+ switch(command) {
 124+ case "add":
 125+ $.extend(existingRules, $.validator.normalizeRule(argument));
 126+ staticRules[element.name] = existingRules;
 127+ if (argument.messages)
 128+ settings.messages[element.name] = $.extend( settings.messages[element.name], argument.messages );
 129+ break;
 130+ case "remove":
 131+ if (!argument) {
 132+ delete staticRules[element.name];
 133+ return existingRules;
 134+ }
 135+ var filtered = {};
 136+ $.each(argument.split(/\s/), function(index, method) {
 137+ filtered[method] = existingRules[method];
 138+ delete existingRules[method];
 139+ });
 140+ return filtered;
 141+ }
 142+ }
 143+
 144+ var data = $.validator.normalizeRules(
 145+ $.extend(
 146+ {},
 147+ $.validator.metadataRules(element),
 148+ $.validator.classRules(element),
 149+ $.validator.attributeRules(element),
 150+ $.validator.staticRules(element)
 151+ ), element);
 152+
 153+ // make sure required is at front
 154+ if (data.required) {
 155+ var param = data.required;
 156+ delete data.required;
 157+ data = $.extend({required: param}, data);
 158+ }
 159+
 160+ return data;
 161+ }
 162+});
 163+
 164+// Custom selectors
 165+$.extend($.expr[":"], {
 166+ // http://docs.jquery.com/Plugins/Validation/blank
 167+ blank: function(a) {return !$.trim("" + a.value);},
 168+ // http://docs.jquery.com/Plugins/Validation/filled
 169+ filled: function(a) {return !!$.trim("" + a.value);},
 170+ // http://docs.jquery.com/Plugins/Validation/unchecked
 171+ unchecked: function(a) {return !a.checked;}
 172+});
 173+
 174+// constructor for validator
 175+$.validator = function( options, form ) {
 176+ this.settings = $.extend( true, {}, $.validator.defaults, options );
 177+ this.currentForm = form;
 178+ this.init();
 179+};
 180+
 181+$.validator.format = function(source, params) {
 182+ if ( arguments.length == 1 )
 183+ return function() {
 184+ var args = $.makeArray(arguments);
 185+ args.unshift(source);
 186+ return $.validator.format.apply( this, args );
 187+ };
 188+ if ( arguments.length > 2 && params.constructor != Array ) {
 189+ params = $.makeArray(arguments).slice(1);
 190+ }
 191+ if ( params.constructor != Array ) {
 192+ params = [ params ];
 193+ }
 194+ $.each(params, function(i, n) {
 195+ source = source.replace(new RegExp("\\{" + i + "\\}", "g"), n);
 196+ });
 197+ return source;
 198+};
 199+
 200+$.extend($.validator, {
 201+
 202+ defaults: {
 203+ messages: {},
 204+ groups: {},
 205+ rules: {},
 206+ errorClass: "error",
 207+ validClass: "valid",
 208+ errorElement: "label",
 209+ focusInvalid: true,
 210+ errorContainer: $( [] ),
 211+ errorLabelContainer: $( [] ),
 212+ onsubmit: true,
 213+ ignore: [],
 214+ ignoreTitle: false,
 215+ onfocusin: function(element) {
 216+ this.lastActive = element;
 217+
 218+ // hide error label and remove error class on focus if enabled
 219+ if ( this.settings.focusCleanup && !this.blockFocusCleanup ) {
 220+ this.settings.unhighlight && this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass );
 221+ this.addWrapper(this.errorsFor(element)).hide();
 222+ }
 223+ },
 224+ onfocusout: function(element) {
 225+ if ( !this.checkable(element) && (element.name in this.submitted || !this.optional(element)) ) {
 226+ this.element(element);
 227+ }
 228+ },
 229+ onkeyup: function(element) {
 230+ if ( element.name in this.submitted || element == this.lastElement ) {
 231+ this.element(element);
 232+ }
 233+ },
 234+ onclick: function(element) {
 235+ // click on selects, radiobuttons and checkboxes
 236+ if ( element.name in this.submitted )
 237+ this.element(element);
 238+ // or option elements, check parent select in that case
 239+ else if (element.parentNode.name in this.submitted)
 240+ this.element(element.parentNode);
 241+ },
 242+ highlight: function(element, errorClass, validClass) {
 243+ if (element.type === 'radio') {
 244+ this.findByName(element.name).addClass(errorClass).removeClass(validClass);
 245+ } else {
 246+ $(element).addClass(errorClass).removeClass(validClass);
 247+ }
 248+ },
 249+ unhighlight: function(element, errorClass, validClass) {
 250+ if (element.type === 'radio') {
 251+ this.findByName(element.name).removeClass(errorClass).addClass(validClass);
 252+ } else {
 253+ $(element).removeClass(errorClass).addClass(validClass);
 254+ }
 255+ }
 256+ },
 257+
 258+ // http://docs.jquery.com/Plugins/Validation/Validator/setDefaults
 259+ setDefaults: function(settings) {
 260+ $.extend( $.validator.defaults, settings );
 261+ },
 262+
 263+ messages: {
 264+ required: "This field is required.",
 265+ remote: "Please fix this field.",
 266+ email: "Please enter a valid email address.",
 267+ url: "Please enter a valid URL.",
 268+ date: "Please enter a valid date.",
 269+ dateISO: "Please enter a valid date (ISO).",
 270+ number: "Please enter a valid number.",
 271+ digits: "Please enter only digits.",
 272+ creditcard: "Please enter a valid credit card number.",
 273+ equalTo: "Please enter the same value again.",
 274+ accept: "Please enter a value with a valid extension.",
 275+ maxlength: $.validator.format("Please enter no more than {0} characters."),
 276+ minlength: $.validator.format("Please enter at least {0} characters."),
 277+ rangelength: $.validator.format("Please enter a value between {0} and {1} characters long."),
 278+ range: $.validator.format("Please enter a value between {0} and {1}."),
 279+ max: $.validator.format("Please enter a value less than or equal to {0}."),
 280+ min: $.validator.format("Please enter a value greater than or equal to {0}.")
 281+ },
 282+
 283+ autoCreateRanges: false,
 284+
 285+ prototype: {
 286+
 287+ init: function() {
 288+ this.labelContainer = $(this.settings.errorLabelContainer);
 289+ this.errorContext = this.labelContainer.length && this.labelContainer || $(this.currentForm);
 290+ this.containers = $(this.settings.errorContainer).add( this.settings.errorLabelContainer );
 291+ this.submitted = {};
 292+ this.valueCache = {};
 293+ this.pendingRequest = 0;
 294+ this.pending = {};
 295+ this.invalid = {};
 296+ this.reset();
 297+
 298+ var groups = (this.groups = {});
 299+ $.each(this.settings.groups, function(key, value) {
 300+ $.each(value.split(/\s/), function(index, name) {
 301+ groups[name] = key;
 302+ });
 303+ });
 304+ var rules = this.settings.rules;
 305+ $.each(rules, function(key, value) {
 306+ rules[key] = $.validator.normalizeRule(value);
 307+ });
 308+
 309+ function delegate(event) {
 310+ var validator = $.data(this[0].form, "validator"),
 311+ eventType = "on" + event.type.replace(/^validate/, "");
 312+ validator.settings[eventType] && validator.settings[eventType].call(validator, this[0] );
 313+ }
 314+ $(this.currentForm)
 315+ .validateDelegate(":text, :password, :file, select, textarea", "focusin focusout keyup", delegate)
 316+ .validateDelegate(":radio, :checkbox, select, option", "click", delegate);
 317+
 318+ if (this.settings.invalidHandler)
 319+ $(this.currentForm).bind("invalid-form.validate", this.settings.invalidHandler);
 320+ },
 321+
 322+ // http://docs.jquery.com/Plugins/Validation/Validator/form
 323+ form: function() {
 324+ this.checkForm();
 325+ $.extend(this.submitted, this.errorMap);
 326+ this.invalid = $.extend({}, this.errorMap);
 327+ if (!this.valid())
 328+ $(this.currentForm).triggerHandler("invalid-form", [this]);
 329+ this.showErrors();
 330+ return this.valid();
 331+ },
 332+
 333+ checkForm: function() {
 334+ this.prepareForm();
 335+ for ( var i = 0, elements = (this.currentElements = this.elements()); elements[i]; i++ ) {
 336+ this.check( elements[i] );
 337+ }
 338+ return this.valid();
 339+ },
 340+
 341+ // http://docs.jquery.com/Plugins/Validation/Validator/element
 342+ element: function( element ) {
 343+ element = this.clean( element );
 344+ this.lastElement = element;
 345+ this.prepareElement( element );
 346+ this.currentElements = $(element);
 347+ var result = this.check( element );
 348+ if ( result ) {
 349+ delete this.invalid[element.name];
 350+ } else {
 351+ this.invalid[element.name] = true;
 352+ }
 353+ if ( !this.numberOfInvalids() ) {
 354+ // Hide error containers on last error
 355+ this.toHide = this.toHide.add( this.containers );
 356+ }
 357+ this.showErrors();
 358+ return result;
 359+ },
 360+
 361+ // http://docs.jquery.com/Plugins/Validation/Validator/showErrors
 362+ showErrors: function(errors) {
 363+ if(errors) {
 364+ // add items to error list and map
 365+ $.extend( this.errorMap, errors );
 366+ this.errorList = [];
 367+ for ( var name in errors ) {
 368+ this.errorList.push({
 369+ message: errors[name],
 370+ element: this.findByName(name)[0]
 371+ });
 372+ }
 373+ // remove items from success list
 374+ this.successList = $.grep( this.successList, function(element) {
 375+ return !(element.name in errors);
 376+ });
 377+ }
 378+ this.settings.showErrors
 379+ ? this.settings.showErrors.call( this, this.errorMap, this.errorList )
 380+ : this.defaultShowErrors();
 381+ },
 382+
 383+ // http://docs.jquery.com/Plugins/Validation/Validator/resetForm
 384+ resetForm: function() {
 385+ if ( $.fn.resetForm )
 386+ $( this.currentForm ).resetForm();
 387+ this.submitted = {};
 388+ this.prepareForm();
 389+ this.hideErrors();
 390+ this.elements().removeClass( this.settings.errorClass );
 391+ },
 392+
 393+ numberOfInvalids: function() {
 394+ return this.objectLength(this.invalid);
 395+ },
 396+
 397+ objectLength: function( obj ) {
 398+ var count = 0;
 399+ for ( var i in obj )
 400+ count++;
 401+ return count;
 402+ },
 403+
 404+ hideErrors: function() {
 405+ this.addWrapper( this.toHide ).hide();
 406+ },
 407+
 408+ valid: function() {
 409+ return this.size() == 0;
 410+ },
 411+
 412+ size: function() {
 413+ return this.errorList.length;
 414+ },
 415+
 416+ focusInvalid: function() {
 417+ if( this.settings.focusInvalid ) {
 418+ try {
 419+ $(this.findLastActive() || this.errorList.length && this.errorList[0].element || [])
 420+ .filter(":visible")
 421+ .focus()
 422+ // manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find
 423+ .trigger("focusin");
 424+ } catch(e) {
 425+ // ignore IE throwing errors when focusing hidden elements
 426+ }
 427+ }
 428+ },
 429+
 430+ findLastActive: function() {
 431+ var lastActive = this.lastActive;
 432+ return lastActive && $.grep(this.errorList, function(n) {
 433+ return n.element.name == lastActive.name;
 434+ }).length == 1 && lastActive;
 435+ },
 436+
 437+ elements: function() {
 438+ var validator = this,
 439+ rulesCache = {};
 440+
 441+ // select all valid inputs inside the form (no submit or reset buttons)
 442+ return $(this.currentForm)
 443+ .find("input, select, textarea")
 444+ .not(":submit, :reset, :image, [disabled]")
 445+ .not( this.settings.ignore )
 446+ .filter(function() {
 447+ !this.name && validator.settings.debug && window.console && console.error( "%o has no name assigned", this);
 448+
 449+ // select only the first element for each name, and only those with rules specified
 450+ if ( this.name in rulesCache || !validator.objectLength($(this).rules()) )
 451+ return false;
 452+
 453+ rulesCache[this.name] = true;
 454+ return true;
 455+ });
 456+ },
 457+
 458+ clean: function( selector ) {
 459+ return $( selector )[0];
 460+ },
 461+
 462+ errors: function() {
 463+ return $( this.settings.errorElement + "." + this.settings.errorClass, this.errorContext );
 464+ },
 465+
 466+ reset: function() {
 467+ this.successList = [];
 468+ this.errorList = [];
 469+ this.errorMap = {};
 470+ this.toShow = $([]);
 471+ this.toHide = $([]);
 472+ this.currentElements = $([]);
 473+ },
 474+
 475+ prepareForm: function() {
 476+ this.reset();
 477+ this.toHide = this.errors().add( this.containers );
 478+ },
 479+
 480+ prepareElement: function( element ) {
 481+ this.reset();
 482+ this.toHide = this.errorsFor(element);
 483+ },
 484+
 485+ check: function( element ) {
 486+ element = this.clean( element );
 487+
 488+ // if radio/checkbox, validate first element in group instead
 489+ if (this.checkable(element)) {
 490+ element = this.findByName( element.name ).not(this.settings.ignore)[0];
 491+ }
 492+
 493+ var rules = $(element).rules();
 494+ var dependencyMismatch = false;
 495+ for (var method in rules ) {
 496+ var rule = { method: method, parameters: rules[method] };
 497+ try {
 498+ var result = $.validator.methods[method].call( this, element.value.replace(/\r/g, ""), element, rule.parameters );
 499+
 500+ // if a method indicates that the field is optional and therefore valid,
 501+ // don't mark it as valid when there are no other rules
 502+ if ( result == "dependency-mismatch" ) {
 503+ dependencyMismatch = true;
 504+ continue;
 505+ }
 506+ dependencyMismatch = false;
 507+
 508+ if ( result == "pending" ) {
 509+ this.toHide = this.toHide.not( this.errorsFor(element) );
 510+ return;
 511+ }
 512+
 513+ if( !result ) {
 514+ this.formatAndAdd( element, rule );
 515+ return false;
 516+ }
 517+ } catch(e) {
 518+ this.settings.debug && window.console && console.log("exception occured when checking element " + element.id
 519+ + ", check the '" + rule.method + "' method", e);
 520+ throw e;
 521+ }
 522+ }
 523+ if (dependencyMismatch)
 524+ return;
 525+ if ( this.objectLength(rules) )
 526+ this.successList.push(element);
 527+ return true;
 528+ },
 529+
 530+ // return the custom message for the given element and validation method
 531+ // specified in the element's "messages" metadata
 532+ customMetaMessage: function(element, method) {
 533+ if (!$.metadata)
 534+ return;
 535+
 536+ var meta = this.settings.meta
 537+ ? $(element).metadata()[this.settings.meta]
 538+ : $(element).metadata();
 539+
 540+ return meta && meta.messages && meta.messages[method];
 541+ },
 542+
 543+ // return the custom message for the given element name and validation method
 544+ customMessage: function( name, method ) {
 545+ var m = this.settings.messages[name];
 546+ return m && (m.constructor == String
 547+ ? m
 548+ : m[method]);
 549+ },
 550+
 551+ // return the first defined argument, allowing empty strings
 552+ findDefined: function() {
 553+ for(var i = 0; i < arguments.length; i++) {
 554+ if (arguments[i] !== undefined)
 555+ return arguments[i];
 556+ }
 557+ return undefined;
 558+ },
 559+
 560+ defaultMessage: function( element, method) {
 561+ return this.findDefined(
 562+ this.customMessage( element.name, method ),
 563+ this.customMetaMessage( element, method ),
 564+ // title is never undefined, so handle empty string as undefined
 565+ !this.settings.ignoreTitle && element.title || undefined,
 566+ $.validator.messages[method],
 567+ "<strong>Warning: No message defined for " + element.name + "</strong>"
 568+ );
 569+ },
 570+
 571+ formatAndAdd: function( element, rule ) {
 572+ var message = this.defaultMessage( element, rule.method ),
 573+ theregex = /\$?\{(\d+)\}/g;
 574+ if ( typeof message == "function" ) {
 575+ message = message.call(this, rule.parameters, element);
 576+ } else if (theregex.test(message)) {
 577+ message = jQuery.format(message.replace(theregex, '{$1}'), rule.parameters);
 578+ }
 579+ this.errorList.push({
 580+ message: message,
 581+ element: element
 582+ });
 583+
 584+ this.errorMap[element.name] = message;
 585+ this.submitted[element.name] = message;
 586+ },
 587+
 588+ addWrapper: function(toToggle) {
 589+ if ( this.settings.wrapper )
 590+ toToggle = toToggle.add( toToggle.parent( this.settings.wrapper ) );
 591+ return toToggle;
 592+ },
 593+
 594+ defaultShowErrors: function() {
 595+ for ( var i = 0; this.errorList[i]; i++ ) {
 596+ var error = this.errorList[i];
 597+ this.settings.highlight && this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass );
 598+ this.showLabel( error.element, error.message );
 599+ }
 600+ if( this.errorList.length ) {
 601+ this.toShow = this.toShow.add( this.containers );
 602+ }
 603+ if (this.settings.success) {
 604+ for ( var i = 0; this.successList[i]; i++ ) {
 605+ this.showLabel( this.successList[i] );
 606+ }
 607+ }
 608+ if (this.settings.unhighlight) {
 609+ for ( var i = 0, elements = this.validElements(); elements[i]; i++ ) {
 610+ this.settings.unhighlight.call( this, elements[i], this.settings.errorClass, this.settings.validClass );
 611+ }
 612+ }
 613+ this.toHide = this.toHide.not( this.toShow );
 614+ this.hideErrors();
 615+ this.addWrapper( this.toShow ).show();
 616+ },
 617+
 618+ validElements: function() {
 619+ return this.currentElements.not(this.invalidElements());
 620+ },
 621+
 622+ invalidElements: function() {
 623+ return $(this.errorList).map(function() {
 624+ return this.element;
 625+ });
 626+ },
 627+
 628+ showLabel: function(element, message) {
 629+ var label = this.errorsFor( element );
 630+ if ( label.length ) {
 631+ // refresh error/success class
 632+ label.removeClass().addClass( this.settings.errorClass );
 633+
 634+ // check if we have a generated label, replace the message then
 635+ label.attr("generated") && label.html(message);
 636+ } else {
 637+ // create label
 638+ label = $("<" + this.settings.errorElement + "/>")
 639+ .attr({"for": this.idOrName(element), generated: true})
 640+ .addClass(this.settings.errorClass)
 641+ .html(message || "");
 642+ if ( this.settings.wrapper ) {
 643+ // make sure the element is visible, even in IE
 644+ // actually showing the wrapped element is handled elsewhere
 645+ label = label.hide().show().wrap("<" + this.settings.wrapper + "/>").parent();
 646+ }
 647+ if ( !this.labelContainer.append(label).length )
 648+ this.settings.errorPlacement
 649+ ? this.settings.errorPlacement(label, $(element) )
 650+ : label.insertAfter(element);
 651+ }
 652+ if ( !message && this.settings.success ) {
 653+ label.text("");
 654+ typeof this.settings.success == "string"
 655+ ? label.addClass( this.settings.success )
 656+ : this.settings.success( label );
 657+ }
 658+ this.toShow = this.toShow.add(label);
 659+ },
 660+
 661+ errorsFor: function(element) {
 662+ var name = this.idOrName(element);
 663+ return this.errors().filter(function() {
 664+ return $(this).attr('for') == name;
 665+ });
 666+ },
 667+
 668+ idOrName: function(element) {
 669+ return this.groups[element.name] || (this.checkable(element) ? element.name : element.id || element.name);
 670+ },
 671+
 672+ checkable: function( element ) {
 673+ return /radio|checkbox/i.test(element.type);
 674+ },
 675+
 676+ findByName: function( name ) {
 677+ // select by name and filter by form for performance over form.find("[name=...]")
 678+ var form = this.currentForm;
 679+ return $(document.getElementsByName(name)).map(function(index, element) {
 680+ return element.form == form && element.name == name && element || null;
 681+ });
 682+ },
 683+
 684+ getLength: function(value, element) {
 685+ switch( element.nodeName.toLowerCase() ) {
 686+ case 'select':
 687+ return $("option:selected", element).length;
 688+ case 'input':
 689+ if( this.checkable( element) )
 690+ return this.findByName(element.name).filter(':checked').length;
 691+ }
 692+ return value.length;
 693+ },
 694+
 695+ depend: function(param, element) {
 696+ return this.dependTypes[typeof param]
 697+ ? this.dependTypes[typeof param](param, element)
 698+ : true;
 699+ },
 700+
 701+ dependTypes: {
 702+ "boolean": function(param, element) {
 703+ return param;
 704+ },
 705+ "string": function(param, element) {
 706+ return !!$(param, element.form).length;
 707+ },
 708+ "function": function(param, element) {
 709+ return param(element);
 710+ }
 711+ },
 712+
 713+ optional: function(element) {
 714+ return !$.validator.methods.required.call(this, $.trim(element.value), element) && "dependency-mismatch";
 715+ },
 716+
 717+ startRequest: function(element) {
 718+ if (!this.pending[element.name]) {
 719+ this.pendingRequest++;
 720+ this.pending[element.name] = true;
 721+ }
 722+ },
 723+
 724+ stopRequest: function(element, valid) {
 725+ this.pendingRequest--;
 726+ // sometimes synchronization fails, make sure pendingRequest is never < 0
 727+ if (this.pendingRequest < 0)
 728+ this.pendingRequest = 0;
 729+ delete this.pending[element.name];
 730+ if ( valid && this.pendingRequest == 0 && this.formSubmitted && this.form() ) {
 731+ $(this.currentForm).submit();
 732+ this.formSubmitted = false;
 733+ } else if (!valid && this.pendingRequest == 0 && this.formSubmitted) {
 734+ $(this.currentForm).triggerHandler("invalid-form", [this]);
 735+ this.formSubmitted = false;
 736+ }
 737+ },
 738+
 739+ previousValue: function(element) {
 740+ return $.data(element, "previousValue") || $.data(element, "previousValue", {
 741+ old: null,
 742+ valid: true,
 743+ message: this.defaultMessage( element, "remote" )
 744+ });
 745+ }
 746+
 747+ },
 748+
 749+ classRuleSettings: {
 750+ required: {required: true},
 751+ email: {email: true},
 752+ url: {url: true},
 753+ date: {date: true},
 754+ dateISO: {dateISO: true},
 755+ dateDE: {dateDE: true},
 756+ number: {number: true},
 757+ numberDE: {numberDE: true},
 758+ digits: {digits: true},
 759+ creditcard: {creditcard: true}
 760+ },
 761+
 762+ addClassRules: function(className, rules) {
 763+ className.constructor == String ?
 764+ this.classRuleSettings[className] = rules :
 765+ $.extend(this.classRuleSettings, className);
 766+ },
 767+
 768+ classRules: function(element) {
 769+ var rules = {};
 770+ var classes = $(element).attr('class');
 771+ classes && $.each(classes.split(' '), function() {
 772+ if (this in $.validator.classRuleSettings) {
 773+ $.extend(rules, $.validator.classRuleSettings[this]);
 774+ }
 775+ });
 776+ return rules;
 777+ },
 778+
 779+ attributeRules: function(element) {
 780+ var rules = {};
 781+ var $element = $(element);
 782+
 783+ for (var method in $.validator.methods) {
 784+ var value = $element.attr(method);
 785+ if (value) {
 786+ rules[method] = value;
 787+ }
 788+ }
 789+
 790+ // maxlength may be returned as -1, 2147483647 (IE) and 524288 (safari) for text inputs
 791+ if (rules.maxlength && /-1|2147483647|524288/.test(rules.maxlength)) {
 792+ delete rules.maxlength;
 793+ }
 794+
 795+ return rules;
 796+ },
 797+
 798+ metadataRules: function(element) {
 799+ if (!$.metadata) return {};
 800+
 801+ var meta = $.data(element.form, 'validator').settings.meta;
 802+ return meta ?
 803+ $(element).metadata()[meta] :
 804+ $(element).metadata();
 805+ },
 806+
 807+ staticRules: function(element) {
 808+ var rules = {};
 809+ var validator = $.data(element.form, 'validator');
 810+ if (validator.settings.rules) {
 811+ rules = $.validator.normalizeRule(validator.settings.rules[element.name]) || {};
 812+ }
 813+ return rules;
 814+ },
 815+
 816+ normalizeRules: function(rules, element) {
 817+ // handle dependency check
 818+ $.each(rules, function(prop, val) {
 819+ // ignore rule when param is explicitly false, eg. required:false
 820+ if (val === false) {
 821+ delete rules[prop];
 822+ return;
 823+ }
 824+ if (val.param || val.depends) {
 825+ var keepRule = true;
 826+ switch (typeof val.depends) {
 827+ case "string":
 828+ keepRule = !!$(val.depends, element.form).length;
 829+ break;
 830+ case "function":
 831+ keepRule = val.depends.call(element, element);
 832+ break;
 833+ }
 834+ if (keepRule) {
 835+ rules[prop] = val.param !== undefined ? val.param : true;
 836+ } else {
 837+ delete rules[prop];
 838+ }
 839+ }
 840+ });
 841+
 842+ // evaluate parameters
 843+ $.each(rules, function(rule, parameter) {
 844+ rules[rule] = $.isFunction(parameter) ? parameter(element) : parameter;
 845+ });
 846+
 847+ // clean number parameters
 848+ $.each(['minlength', 'maxlength', 'min', 'max'], function() {
 849+ if (rules[this]) {
 850+ rules[this] = Number(rules[this]);
 851+ }
 852+ });
 853+ $.each(['rangelength', 'range'], function() {
 854+ if (rules[this]) {
 855+ rules[this] = [Number(rules[this][0]), Number(rules[this][1])];
 856+ }
 857+ });
 858+
 859+ if ($.validator.autoCreateRanges) {
 860+ // auto-create ranges
 861+ if (rules.min && rules.max) {
 862+ rules.range = [rules.min, rules.max];
 863+ delete rules.min;
 864+ delete rules.max;
 865+ }
 866+ if (rules.minlength && rules.maxlength) {
 867+ rules.rangelength = [rules.minlength, rules.maxlength];
 868+ delete rules.minlength;
 869+ delete rules.maxlength;
 870+ }
 871+ }
 872+
 873+ // To support custom messages in metadata ignore rule methods titled "messages"
 874+ if (rules.messages) {
 875+ delete rules.messages;
 876+ }
 877+
 878+ return rules;
 879+ },
 880+
 881+ // Converts a simple string to a {string: true} rule, e.g., "required" to {required:true}
 882+ normalizeRule: function(data) {
 883+ if( typeof data == "string" ) {
 884+ var transformed = {};
 885+ $.each(data.split(/\s/), function() {
 886+ transformed[this] = true;
 887+ });
 888+ data = transformed;
 889+ }
 890+ return data;
 891+ },
 892+
 893+ // http://docs.jquery.com/Plugins/Validation/Validator/addMethod
 894+ addMethod: function(name, method, message) {
 895+ $.validator.methods[name] = method;
 896+ $.validator.messages[name] = message != undefined ? message : $.validator.messages[name];
 897+ if (method.length < 3) {
 898+ $.validator.addClassRules(name, $.validator.normalizeRule(name));
 899+ }
 900+ },
 901+
 902+ methods: {
 903+
 904+ // http://docs.jquery.com/Plugins/Validation/Methods/required
 905+ required: function(value, element, param) {
 906+ // check if dependency is met
 907+ if ( !this.depend(param, element) )
 908+ return "dependency-mismatch";
 909+ switch( element.nodeName.toLowerCase() ) {
 910+ case 'select':
 911+ // could be an array for select-multiple or a string, both are fine this way
 912+ var val = $(element).val();
 913+ return val && val.length > 0;
 914+ case 'input':
 915+ if ( this.checkable(element) )
 916+ return this.getLength(value, element) > 0;
 917+ default:
 918+ return $.trim(value).length > 0;
 919+ }
 920+ },
 921+
 922+ // http://docs.jquery.com/Plugins/Validation/Methods/remote
 923+ remote: function(value, element, param) {
 924+ if ( this.optional(element) )
 925+ return "dependency-mismatch";
 926+
 927+ var previous = this.previousValue(element);
 928+ if (!this.settings.messages[element.name] )
 929+ this.settings.messages[element.name] = {};
 930+ previous.originalMessage = this.settings.messages[element.name].remote;
 931+ this.settings.messages[element.name].remote = previous.message;
 932+
 933+ param = typeof param == "string" && {url:param} || param;
 934+
 935+ if ( this.pending[element.name] ) {
 936+ return "pending";
 937+ }
 938+ if ( previous.old === value ) {
 939+ return previous.valid;
 940+ }
 941+
 942+ previous.old = value;
 943+ var validator = this;
 944+ this.startRequest(element);
 945+ var data = {};
 946+ data[element.name] = value;
 947+ $.ajax($.extend(true, {
 948+ url: param,
 949+ mode: "abort",
 950+ port: "validate" + element.name,
 951+ dataType: "json",
 952+ data: data,
 953+ success: function(response) {
 954+ validator.settings.messages[element.name].remote = previous.originalMessage;
 955+ var valid = response === true;
 956+ if ( valid ) {
 957+ var submitted = validator.formSubmitted;
 958+ validator.prepareElement(element);
 959+ validator.formSubmitted = submitted;
 960+ validator.successList.push(element);
 961+ validator.showErrors();
 962+ } else {
 963+ var errors = {};
 964+ var message = response || validator.defaultMessage( element, "remote" );
 965+ errors[element.name] = previous.message = $.isFunction(message) ? message(value) : message;
 966+ validator.showErrors(errors);
 967+ }
 968+ previous.valid = valid;
 969+ validator.stopRequest(element, valid);
 970+ }
 971+ }, param));
 972+ return "pending";
 973+ },
 974+
 975+ // http://docs.jquery.com/Plugins/Validation/Methods/minlength
 976+ minlength: function(value, element, param) {
 977+ return this.optional(element) || this.getLength($.trim(value), element) >= param;
 978+ },
 979+
 980+ // http://docs.jquery.com/Plugins/Validation/Methods/maxlength
 981+ maxlength: function(value, element, param) {
 982+ return this.optional(element) || this.getLength($.trim(value), element) <= param;
 983+ },
 984+
 985+ // http://docs.jquery.com/Plugins/Validation/Methods/rangelength
 986+ rangelength: function(value, element, param) {
 987+ var length = this.getLength($.trim(value), element);
 988+ return this.optional(element) || ( length >= param[0] && length <= param[1] );
 989+ },
 990+
 991+ // http://docs.jquery.com/Plugins/Validation/Methods/min
 992+ min: function( value, element, param ) {
 993+ return this.optional(element) || value >= param;
 994+ },
 995+
 996+ // http://docs.jquery.com/Plugins/Validation/Methods/max
 997+ max: function( value, element, param ) {
 998+ return this.optional(element) || value <= param;
 999+ },
 1000+
 1001+ // http://docs.jquery.com/Plugins/Validation/Methods/range
 1002+ range: function( value, element, param ) {
 1003+ return this.optional(element) || ( value >= param[0] && value <= param[1] );
 1004+ },
 1005+
 1006+ // http://docs.jquery.com/Plugins/Validation/Methods/email
 1007+ email: function(value, element) {
 1008+ // contributed by Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/
 1009+ return this.optional(element) || /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test(value);
 1010+ },
 1011+
 1012+ // http://docs.jquery.com/Plugins/Validation/Methods/url
 1013+ url: function(value, element) {
 1014+ // contributed by Scott Gonzalez: http://projects.scottsplayground.com/iri/
 1015+ return this.optional(element) || /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value);
 1016+ },
 1017+
 1018+ // http://docs.jquery.com/Plugins/Validation/Methods/date
 1019+ date: function(value, element) {
 1020+ return this.optional(element) || !/Invalid|NaN/.test(new Date(value));
 1021+ },
 1022+
 1023+ // http://docs.jquery.com/Plugins/Validation/Methods/dateISO
 1024+ dateISO: function(value, element) {
 1025+ return this.optional(element) || /^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(value);
 1026+ },
 1027+
 1028+ // http://docs.jquery.com/Plugins/Validation/Methods/number
 1029+ number: function(value, element) {
 1030+ return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/.test(value);
 1031+ },
 1032+
 1033+ // http://docs.jquery.com/Plugins/Validation/Methods/digits
 1034+ digits: function(value, element) {
 1035+ return this.optional(element) || /^\d+$/.test(value);
 1036+ },
 1037+
 1038+ // http://docs.jquery.com/Plugins/Validation/Methods/creditcard
 1039+ // based on http://en.wikipedia.org/wiki/Luhn
 1040+ creditcard: function(value, element) {
 1041+ if ( this.optional(element) )
 1042+ return "dependency-mismatch";
 1043+ // accept only digits and dashes
 1044+ if (/[^0-9-]+/.test(value))
 1045+ return false;
 1046+ var nCheck = 0,
 1047+ nDigit = 0,
 1048+ bEven = false;
 1049+
 1050+ value = value.replace(/\D/g, "");
 1051+
 1052+ for (var n = value.length - 1; n >= 0; n--) {
 1053+ var cDigit = value.charAt(n);
 1054+ var nDigit = parseInt(cDigit, 10);
 1055+ if (bEven) {
 1056+ if ((nDigit *= 2) > 9)
 1057+ nDigit -= 9;
 1058+ }
 1059+ nCheck += nDigit;
 1060+ bEven = !bEven;
 1061+ }
 1062+
 1063+ return (nCheck % 10) == 0;
 1064+ },
 1065+
 1066+ // http://docs.jquery.com/Plugins/Validation/Methods/accept
 1067+ accept: function(value, element, param) {
 1068+ param = typeof param == "string" ? param.replace(/,/g, '|') : "png|jpe?g|gif";
 1069+ return this.optional(element) || value.match(new RegExp(".(" + param + ")$", "i"));
 1070+ },
 1071+
 1072+ // http://docs.jquery.com/Plugins/Validation/Methods/equalTo
 1073+ equalTo: function(value, element, param) {
 1074+ // bind to the blur event of the target in order to revalidate whenever the target field is updated
 1075+ // TODO find a way to bind the event just once, avoiding the unbind-rebind overhead
 1076+ var target = $(param).unbind(".validate-equalTo").bind("blur.validate-equalTo", function() {
 1077+ $(element).valid();
 1078+ });
 1079+ return value == target.val();
 1080+ }
 1081+
 1082+ }
 1083+
 1084+});
 1085+
 1086+// deprecated, use $.validator.format instead
 1087+$.format = $.validator.format;
 1088+
 1089+})(jQuery);
 1090+
 1091+// ajax mode: abort
 1092+// usage: $.ajax({ mode: "abort"[, port: "uniqueport"]});
 1093+// if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort()
 1094+;(function($) {
 1095+ var pendingRequests = {};
 1096+ // Use a prefilter if available (1.5+)
 1097+ if ( $.ajaxPrefilter ) {
 1098+ $.ajaxPrefilter(function(settings, _, xhr) {
 1099+ var port = settings.port;
 1100+ if (settings.mode == "abort") {
 1101+ if ( pendingRequests[port] ) {
 1102+ pendingRequests[port].abort();
 1103+ }
 1104+ pendingRequests[port] = xhr;
 1105+ }
 1106+ });
 1107+ } else {
 1108+ // Proxy ajax
 1109+ var ajax = $.ajax;
 1110+ $.ajax = function(settings) {
 1111+ var mode = ( "mode" in settings ? settings : $.ajaxSettings ).mode,
 1112+ port = ( "port" in settings ? settings : $.ajaxSettings ).port;
 1113+ if (mode == "abort") {
 1114+ if ( pendingRequests[port] ) {
 1115+ pendingRequests[port].abort();
 1116+ }
 1117+ return (pendingRequests[port] = ajax.apply(this, arguments));
 1118+ }
 1119+ return ajax.apply(this, arguments);
 1120+ };
 1121+ }
 1122+})(jQuery);
 1123+
 1124+// provides cross-browser focusin and focusout events
 1125+// IE has native support, in other browsers, use event caputuring (neither bubbles)
 1126+
 1127+// provides delegate(type: String, delegate: Selector, handler: Callback) plugin for easier event delegation
 1128+// handler is only called when $(event.target).is(delegate), in the scope of the jquery-object for event.target
 1129+;(function($) {
 1130+ // only implement if not provided by jQuery core (since 1.4)
 1131+ // TODO verify if jQuery 1.4's implementation is compatible with older jQuery special-event APIs
 1132+ if (!jQuery.event.special.focusin && !jQuery.event.special.focusout && document.addEventListener) {
 1133+ $.each({
 1134+ focus: 'focusin',
 1135+ blur: 'focusout'
 1136+ }, function( original, fix ){
 1137+ $.event.special[fix] = {
 1138+ setup:function() {
 1139+ this.addEventListener( original, handler, true );
 1140+ },
 1141+ teardown:function() {
 1142+ this.removeEventListener( original, handler, true );
 1143+ },
 1144+ handler: function(e) {
 1145+ arguments[0] = $.event.fix(e);
 1146+ arguments[0].type = fix;
 1147+ return $.event.handle.apply(this, arguments);
 1148+ }
 1149+ };
 1150+ function handler(e) {
 1151+ e = $.event.fix(e);
 1152+ e.type = fix;
 1153+ return $.event.handle.call(this, e);
 1154+ }
 1155+ });
 1156+ };
 1157+ $.extend($.fn, {
 1158+ validateDelegate: function(delegate, type, handler) {
 1159+ return this.bind(type, function(event) {
 1160+ var target = $(event.target);
 1161+ if (target.is(delegate)) {
 1162+ return handler.apply(target, arguments);
 1163+ }
 1164+ });
 1165+ }
 1166+ });
 1167+})(jQuery);
Property changes on: branches/salvatoreingala/Gadgets/modules/jquery.validate.js
___________________________________________________________________
Added: svn:eol-style
11168 + native
Index: branches/salvatoreingala/Gadgets/modules/jquery.formBuilder.js
@@ -0,0 +1,266 @@
 2+/**
 3+ * jQuery Form Builder
 4+ * Written by Salvatore Ingala in 2011
 5+ * Released under the MIT and GPL licenses.
 6+ */
 7+
 8+
 9+(function($, mw) {
 10+
 11+ var idPrefix = "mw-gadgets-dialog-";
 12+
 13+ function $s( str ) {
 14+ if ( str[0] !== '@' ) {
 15+ return str;
 16+ } else {
 17+ //TODO: better validation
 18+ return mw.msg( str.substring( 1 ) );
 19+ }
 20+ }
 21+
 22+ //Helper function for inheritance, see http://javascript.crockford.com/prototypal.html
 23+ function object(o) {
 24+ function F() {}
 25+ F.prototype = o;
 26+ return new F();
 27+ }
 28+
 29+ //A field with no content
 30+ function EmptyField( name, desc ) {
 31+ //Check existence of compulsory fields
 32+ if ( typeof name == 'undefined' || !desc.type || !desc.label ) {
 33+ throw new Error( "Missing arguments" ); //TODO
 34+ }
 35+
 36+ this.$p = $( '<p/>' );
 37+
 38+ this.name = name;
 39+ this.desc = desc;
 40+ }
 41+
 42+ EmptyField.prototype.getName = function() {
 43+ return this.name;
 44+ };
 45+
 46+ EmptyField.prototype.getDesc = function() {
 47+ return this.desc;
 48+ };
 49+
 50+
 51+ //Override expected
 52+ EmptyField.prototype.getValue = function() {
 53+ return null;
 54+ };
 55+
 56+ EmptyField.prototype.getElement = function() {
 57+ return this.$p;
 58+ };
 59+
 60+ EmptyField.prototype.getValidationSettings = function() {
 61+ return null;
 62+ }
 63+
 64+ //A field with just a label
 65+ LabelField.prototype = object( EmptyField.prototype );
 66+ LabelField.prototype.constructor = LabelField;
 67+ function LabelField( name, desc ) {
 68+ EmptyField.call( this, name, desc );
 69+
 70+ var $label = $( '<label/>' )
 71+ .text( $s( this.desc.label ) )
 72+ .attr('for', idPrefix + this.name );
 73+
 74+ this.$p.append( $label );
 75+ }
 76+
 77+ //A field with a label and a checkbox
 78+ BooleanField.prototype = object( LabelField.prototype );
 79+ BooleanField.prototype.constructor = BooleanField;
 80+ function BooleanField( name, desc ){
 81+ LabelField.call( this, name, desc );
 82+
 83+ if ( typeof desc.value != 'boolean' ) {
 84+ throw new Error( "desc.value is invalid" );
 85+ }
 86+
 87+ this.$c = $( '<input/>' )
 88+ .attr( 'type', 'checkbox' )
 89+ .attr( 'id', idPrefix + this.name )
 90+ .attr( 'name', idPrefix + this.name )
 91+ .attr( 'checked', this.desc.value );
 92+
 93+ this.$p.append( this.$c );
 94+ }
 95+
 96+ BooleanField.prototype.getValue = function() {
 97+ return this.$c.is( ':checked' );
 98+ }
 99+
 100+ //A field with a textbox
 101+
 102+ StringField.prototype = object( LabelField.prototype );
 103+ StringField.prototype.constructor = StringField;
 104+ function StringField( name, desc ){
 105+ LabelField.call( this, name, desc );
 106+
 107+ if ( typeof desc.value != 'string' ) {
 108+ throw new Error( "desc.value is invalid" );
 109+ }
 110+
 111+ this.$text = $( '<input/>' )
 112+ .attr( 'type', 'text' )
 113+ .attr( 'id', idPrefix + this.name )
 114+ .attr( 'name', idPrefix + this.name )
 115+ .val( this.desc.value );
 116+
 117+ this.$p.append( this.$text );
 118+ }
 119+
 120+ StringField.prototype.getValue = function() {
 121+ return this.$text.val();
 122+ };
 123+
 124+ StringField.prototype.getValidationSettings = function() {
 125+ //TODO: messages
 126+
 127+ var settings = {
 128+ rules: {}
 129+ },
 130+ fieldId = idPrefix + this.name;
 131+
 132+ settings.rules[fieldId] = {};
 133+ var fieldRules = settings.rules[fieldId],
 134+ desc = this.desc;
 135+
 136+ if ( desc.required === true ) {
 137+ fieldRules.required = true;
 138+ }
 139+
 140+ if ( typeof desc.minLength != 'undefined' ) {
 141+ fieldRules.minlength = desc.minLength;
 142+ }
 143+ if ( typeof desc.maxLength != 'undefined' ) {
 144+ fieldRules.maxlength = desc.maxLength;
 145+ }
 146+
 147+ return settings;
 148+ }
 149+
 150+
 151+
 152+
 153+ var validFields = {
 154+ "boolean": BooleanField,
 155+ "string" : StringField
 156+ };
 157+
 158+ function log( message ) {
 159+ if ( typeof window.console != 'undefined' ) {
 160+ console.warn( message );
 161+ }
 162+ }
 163+
 164+ function buildFormBody() {
 165+ var description = this.get(0);
 166+ if (typeof description != 'object' ) {
 167+ log( "description should be an object, instead of a " + typeof description );
 168+ return null;
 169+ }
 170+
 171+ var $form = $( '<form/>' );
 172+ var $fieldset = $( '<fieldset/>' ).appendTo( $form );
 173+
 174+ if (typeof description.label == 'string' ) {
 175+ $( '<legend/>' )
 176+ .text( $s( description.label ) )
 177+ .appendTo( $fieldset );
 178+ }
 179+
 180+ //TODO: manage form params
 181+
 182+ if (typeof description.fields != 'object' ) {
 183+ log( "description.fields should be an object, instead of a " + typeof description.fields );
 184+ return null;
 185+ }
 186+
 187+ var $form = $( '<form/>' );
 188+
 189+ var fields = [];
 190+
 191+ var settings = {} //validator settings
 192+
 193+ for ( var fieldName in description.fields ) {
 194+ if ( description.fields.hasOwnProperty( fieldName )) {
 195+ //TODO: validate fieldName
 196+ var field = description.fields[fieldName];
 197+
 198+ var FieldConstructor = validFields[field.type];
 199+
 200+ if ( typeof FieldConstructor != 'function' ) {
 201+ log( "field with invalid type: " + field.type );
 202+ return null;
 203+ }
 204+
 205+ try {
 206+ var f = new FieldConstructor( fieldName, field );
 207+ } catch ( e ) {
 208+ log( e );
 209+ return null; //constructor failed, wrong syntax in field description
 210+ }
 211+
 212+ $form.append( f.getElement() );
 213+
 214+ //If this field has validation rules, add them to settings
 215+ var fieldSettings = f.getValidationSettings();
 216+
 217+ if ( fieldSettings ) {
 218+ $.extend( settings, fieldSettings, true );
 219+ }
 220+
 221+ fields.push( f );
 222+
 223+ //TODO: validation of the field
 224+
 225+ }
 226+ }
 227+
 228+ var validator = $form.validate( settings );
 229+
 230+ $form.data( 'formBuilder', {
 231+ fields: fields,
 232+ validator: validator
 233+ } );
 234+
 235+ return $form;
 236+ }
 237+
 238+ var methods = {
 239+ getValues: function() {
 240+ var data = this.data( 'formBuilder' ),
 241+ result = {};
 242+
 243+ for ( var i = 0; i < data.fields.length; i++ ) {
 244+ var f = data.fields[i];
 245+ result[f.getName()] = f.getValue();
 246+ }
 247+
 248+ return result;
 249+ },
 250+ validate: function() {
 251+ var data = this.data( 'formBuilder' );
 252+ return data.validator.form();
 253+ }
 254+
 255+ };
 256+
 257+ $.fn.formBuilder = function( method ) {
 258+ if ( methods[method] ) {
 259+ return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
 260+ } else if ( typeof method === 'object' || !method ) {
 261+ return buildFormBody.apply( this, arguments ); //TODO
 262+ } else {
 263+ $.error( 'Method ' + method + ' does not exist on jQuery.formBuilder' );
 264+ }
 265+ };
 266+})( jQuery, mediaWiki );
 267+
Property changes on: branches/salvatoreingala/Gadgets/modules/jquery.formBuilder.js
___________________________________________________________________
Added: svn:eol-style
1268 + native
Index: branches/salvatoreingala/Gadgets/modules/ext.gadgets.preferences.js
@@ -4,16 +4,7 @@
55 ( function( $, mw ) {
66
77 //"Save" button click handler
8 - function saveConfig( $dialog, gadget ) {
9 - var config = {};
10 -
11 - //Inputs are all the children of $dialog whose id starts with "mw-input-wp"
12 - //TODO: fix this, there is no warranty that this doesn't conflicts with other existing ids.
13 - $dialog.find( '[id ^= "mw-input-wp"]' ).each( function( i, el ) {
14 - var param = el.id.substring( "mw-input-wp".length );
15 - config[param] = $( el ).val(); //TODO: this only work for simpler fields
16 - } );
17 -
 8+ function saveConfig( $dialog, gadget, config ) {
189 var json = $.toJSON( config );
1910
2011 var postData = 'action=ajax&rs=GadgetsAjax::setPreferences' +
@@ -55,17 +46,24 @@
5647 var $link = $( '<a></a>' )
5748 .text( "Configure" ) //TODO: use a message instead
5849 .click( function() {
59 - var postData = 'action=ajax&rs=GadgetsAjax::getUI' +
 50+ var postData = 'action=ajax&rs=GadgetsAjax::getPreferences' +
6051 '&rsargs[]=gadget|' + encodeURIComponent( gadget );
61 -
 52+
6253 $.ajax( {
6354 url: mw.config.get( 'wgScriptPath' ) + '/index.php',
6455 type: "POST",
6556 data: postData,
66 - dataType: "html", // response type
 57+ dataType: "json", // response type
6758 success: function( response ) {
68 - //Show dialog
69 - $( response ).dialog( {
 59+ //TODO: malformed response?
 60+
 61+ //Create and show dialog
 62+
 63+ var dialogBody = $( response ).formBuilder();
 64+
 65+ //window.validator = $( dialogBody ).validate();
 66+
 67+ $( dialogBody ).dialog( {
7068 modal: true,
7169 width: 'auto',
7270 resizable: false,
@@ -74,9 +72,15 @@
7573 $( this ).dialog( 'destroy' ).empty(); //completely destroy on close
7674 },
7775 buttons: {
78 - //TODO: add "Restore defaults" button
 76+ //TODO: add a "Restore defaults" button
 77+
7978 "Save": function() {
80 - saveConfig( $( this ), gadget );
 79+ var isValid = $( dialogBody ).formBuilder( 'validate' );
 80+
 81+ if ( isValid ) {
 82+ var values = $( dialogBody ).formBuilder( 'getValues' );
 83+ saveConfig( $( this ), gadget, values );
 84+ }
8185 },
8286 "Cancel": function() {
8387 $( this ).dialog( "close" );

Comments

#Comment by MaxSem (talk | contribs)   18:19, 2 June 2011
if ( strcmp( $fieldName, 'type' ) == 0)

Hey, you're not writing in C, just use comparison operator :)

#Comment by Salvatore Ingala (talk | contribs)   18:56, 2 June 2011

D'oh! You spoiled my secret plan of rewriting MW in C :P

Thanks for pointing it out :)

Status & tagging log