http://www.mediawiki.org/wiki/Special:Code/MediaWiki/94994
Revision: 94994 Author: salvatoreingala Date: 2011-08-19 10:54:30 +0000 (Fri, 19 Aug 2011) Log Message: ----------- JSHint, coding conventions, comments and minor refactoring Modified Paths: -------------- branches/salvatoreingala/Gadgets/ui/resources/ext.gadgets.preferences.js branches/salvatoreingala/Gadgets/ui/resources/jquery.formBuilder.js Modified: branches/salvatoreingala/Gadgets/ui/resources/ext.gadgets.preferences.js =================================================================== --- branches/salvatoreingala/Gadgets/ui/resources/ext.gadgets.preferences.js 2011-08-19 10:21:56 UTC (rev 94993) +++ branches/salvatoreingala/Gadgets/ui/resources/ext.gadgets.preferences.js 2011-08-19 10:54:30 UTC (rev 94994) @@ -40,8 +40,8 @@ function removeStylesheet( styleSheet ) { var owner = styleSheet.ownerNode ? - styleSheet.ownerNode : //not-IE or IE >= 9 - styleSheet.owningElement //IE < 9 + styleSheet.ownerNode : //not-IE or IE >= 9 + styleSheet.owningElement; //IE < 9 owner.parentNode.removeChild( owner ); } @@ -69,7 +69,7 @@ //just to avoid code duplication function error() { //Remove "wait" cursor - removeStylesheet( waitCSS ) + removeStylesheet( waitCSS ); //Warn the user showMsg( mw.msg( 'gadgets-save-failed' ) ); @@ -92,7 +92,7 @@ success: function( response ) { if ( typeof response.error == 'undefined' ) { //Remove "wait" cursor - removeStylesheet( waitCSS ) + removeStylesheet( waitCSS ); //Notify success to user showMsg( mw.msg( 'gadgets-save-success' ) ); @@ -102,7 +102,7 @@ //update 'savedConfig' $dialog.data( 'savedValues', config ); } else { - error() + error(); } }, error: error @@ -134,16 +134,16 @@ dataType: "json", // response type success: function( response ) { - if ( typeof response.description != 'object' - || typeof response.values != 'object') + if ( typeof response.description != 'object' || + typeof response.values != 'object') { - alert( mw.msg( 'gadgets-unexpected-error' ) ) + alert( mw.msg( 'gadgets-unexpected-error' ) ); return; } //Create and show dialog - var prefsDescription = response.description, + var prefsDescription = response.description, values = response.values, $dialogBody; @@ -155,9 +155,9 @@ //Hide current message, if any showMsg( null ); - var savedValues = $dialogBody.data( 'savedValues' ), + var savedValues = $dialogBody.data( 'savedValues' ), currentValues = $form.formBuilder( 'getValues' ); - //TODO: use a better way of comparing values... + if ( !deepEquals( currentValues, savedValues ) ) { $( '#mw-gadgets-prefsDialog-save' ).button( 'enable' ); } else { Modified: branches/salvatoreingala/Gadgets/ui/resources/jquery.formBuilder.js =================================================================== --- branches/salvatoreingala/Gadgets/ui/resources/jquery.formBuilder.js 2011-08-19 10:21:56 UTC (rev 94993) +++ branches/salvatoreingala/Gadgets/ui/resources/jquery.formBuilder.js 2011-08-19 10:54:30 UTC (rev 94994) @@ -4,244 +4,12 @@ * Released under the MIT and GPL licenses. */ -(function($, mw) { +( function( $, mw ) { //Field types that can be referred to by preference descriptions - var validFieldTypes = {}; + var validFieldTypes = {}, //filled when costructors are initialized + prefsSpecifications; //defined later, declaring here to avoid references to undeclared variable - //Describes 'name' and 'label' field members, common to all "simple" fields - var simpleFields = [ - { - "name": "name", - "type": "string", - "label": "name", - "required": true, - "maxlength": 40, - "default": "" - }, - { - "name": "label", - "type": "string", - "label": "label", - "required": false, - "default": "" - } - ]; - - //Used by preference editor to build field properties dialogs - //TODO: document - var prefsDescriptionSpecifications = { - "label": { - "simple": false, - "builder": [ { - "name": "label", - "type": "string", - "label": "label", - "required": false, - "default": "" - } ] - }, - "boolean": { - "simple": true, - "builder": simpleFields - }, - "string": { - "simple": true, - "builder": simpleFields.concat( [ - { - "name": "required", - "type": "boolean", - "label": "required", - "default": false - }, - { - "name": "minlength", - "type": "number", - "label": "minlength", - "integer": true, - "min": 0, - "required": false, - "default": null - }, - { - "name": "maxlength", - "type": "number", - "label": "maxlength", - "integer": true, - "min": 1, - "required": false, - "default": null - } - ] ) - }, - "number": { - "simple": true, - "builder": simpleFields.concat( [ - { - "name": "required", - "type": "boolean", - "label": "required", - "default": true - }, - { - "name": "integer", - "type": "boolean", - "label": "integer", - "default": false - }, - { - "name": "min", - "type": "number", - "label": "min", - "required": false, - "default": null - }, - { - "name": "max", - "type": "number", - "label": "max", - "required": false, - "default": null - } - ] ) - }, - "range": { - "simple": true, - "builder": simpleFields.concat( [ - { - "name": "min", - "type": "number", - "label": "min", - "required": true, - }, - { - "name": "step", - "type": "number", - "label": "step", - "required": true, - "default": 1 - }, - { - "name": "max", - "type": "number", - "label": "max", - "required": true, - } - ] ) - }, - "date": { - "simple": true, - "builder": simpleFields - }, - "color": { - "simple": true, - "builder": simpleFields - }, - "bundle": { - "simple": false, - "builder": function( options, callback ) { - callback( - new BundleField( { - "type": "bundle", - "sections": [ - { - "title": "Section 1", - "fields": [] - }, - { - "title": "Section 2", - "fields": [] - } - ] - }, options ) - ); - } - }, - "composite": { - "simple": true, - "builder": [ { - "name": "name", - "type": "string", - "label": "name", - "required": true, - "maxlength": 40, - "default": "" - } ] - }, - "list": { - "simple": true, - "builder": function( options, callback ) { - - //Create list of "simple" types - var selectOptions = []; - $.each( prefsDescriptionSpecifications, function( type, typeInfo ) { - if ( typeInfo.simple === true ) { - selectOptions.push( { "name": type, "value": type } ); - } - } ); - - //Create the dialog to chose the field type - var $form = $( { - fields: [ { - "name": "name", - "type": "string", - "label": "name", - "required": true, - "maxlength": 40, - "default": "" - }, - { - "name": "type", - "type": "select", - "label": "type", - "options": selectOptions - } ] - } ).formBuilder( { idPrefix: 'list-chose-type-' } ) - .submit( function() { - return false; //prevent form submission - } ); - - $form.dialog( { - width: 450, - modal: true, - resizable: false, - title: mw.msg( 'gadgets-formbuilder-editor-create-field-title', 'list' ), - close: function() { - $( this ).remove(); - }, - buttons: [ - { - text: mw.msg( 'gadgets-formbuilder-editor-ok' ), - click: function() { - var values = $( this ).formBuilder( 'getValues' ); - $( this ).dialog( "close" ); - - var dialog = this; - createFieldDialog( { - type: values.type, - values: { - "name": values.name - }, - callback: function( field ) { - $( dialog ).dialog( 'close' ); - showEditFieldDialog( field.getDesc(), options, callback ); - return true; - } - }, { editable: true } ); - } - }, - { - text: mw.msg( 'gadgets-formbuilder-editor-cancel' ), - click: function() { - $( this ).dialog( "close" ); - } - } - ] - } ); - } - } - }; - /* Utility functions */ //Preprocesses strings end possibly replaces them with messages. @@ -249,7 +17,7 @@ //a message, and the result of mw.msg is returned. //Two "@@" at the beginning escape for a single "@". function preproc( msgPrefix, str ) { - if ( str.length <= 1 || str[0] !== '@' ) { + if ( str.length <= 1 || str.charAt( 0 ) !== '@' ) { return str; } else if ( str.substr( 0, 2 ) == '@@' ) { return str.substr( 1 ); @@ -269,6 +37,7 @@ }; } )(); + //Pads a number with leading zeroes until length is n characters function pad( n, len ) { var res = '' + n; while ( res.length < len ) { @@ -299,15 +68,41 @@ return $.extend( true, {}, obj ); } + //EcmaScript 5 standard function, emulate for older browsers + //From https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/create + if ( !Object.create ) { + Object.create = function ( o ) { + if ( arguments.length > 1 ) { + $.error('Object.create implementation only accepts the first parameter.'); + } + function F() {} + F.prototype = o; + return new F(); + }; + } + + //Add a "smart" listener to watch for changes to an <input /> element + //This binds to several events, but calls the callback only if the value actually changed + function addSmartChangeListener( $input, callback ) { + var oldValue = $input.val(); + //bind all events that may change the value of the field (some are brower-specific) + $input.bind( 'keyup change propertychange input paste', function() { + var newValue = $input.val(); + if ( oldValue !== newValue ) { + oldValue = newValue; + callback(); + } + } ); + } + + /* Validator plugin utility functions and methods */ + function deleteFieldRules( field ) { //Remove all its validation rules var validationSettings = field.getValidationSettings(); if ( validationSettings.rules ) { $.each( validationSettings.rules, function( name, value ) { - var $input = $( '#' + name ); - if ( $input.length > 0 ) { - $( '#' + name ).rules( 'remove' ); - } + $( '#' + name ).rules( 'remove' ); } ); } } @@ -319,7 +114,7 @@ var $input = $( '#' + name ); //Find messages associated to this rule, if any - if ( typeof validationSettings.messages != 'undefined' && + if ( typeof validationSettings.messages != 'undefined' && typeof validationSettings.messages[name] != 'undefined') { rules.messages = validationSettings.messages[name]; @@ -332,7 +127,55 @@ } } - function createFieldDialog( params, options ) { + + function testOptional( value, element ) { + var rules = $( element ).rules(); + if ( typeof rules.required == 'undefined' || rules.required === false ) { + if ( value.length === 0 ) { + return true; + } + } + return false; + } + + //validator for "required" fields (without trimming whitespaces) + $.validator.addMethod( "requiredStrict", function( value, element ) { + return value.length > 0; + }, mw.msg( 'gadgets-formbuilder-required' ) ); + + //validator for "minlength" fields (without trimming whitespaces) + $.validator.addMethod( "minlengthStrict", function( value, element, param ) { + return testOptional( value, element ) || value.length >= param; + } ); + + //validator for "maxlength" fields (without trimming whitespaces) + $.validator.addMethod( "maxlengthStrict", function( value, element, param ) { + return testOptional( value, element ) || value.length <= param; + } ); + + //validator for integer fields + $.validator.addMethod( "integer", function( value, element ) { + return testOptional( value, element ) || /^-?\d+$/.test(value); + }, mw.msg( 'gadgets-formbuilder-integer' ) ); + + //validator for datepicker fields + $.validator.addMethod( "datePicker", function( value, element ) { + var format = $( element ).datepicker( 'option', 'dateFormat' ); + try { + var date = $.datepicker.parseDate( format, value ); + return true; + } catch ( e ) { + return false; + } + }, mw.msg( 'gadgets-formbuilder-date' ) ); + + //validator for colorpicker fields + $.validator.addMethod( "color", function( value, element ) { + return $.colorUtil.getRGB( value ) !== undefined; + }, mw.msg( 'gadgets-formbuilder-color' ) ); + + /* Functions used by the preferences editor */ + function createFieldDialog( params, options ) { var self = this; if ( typeof params.callback != 'function' ) { @@ -399,10 +242,10 @@ return; } else { type = params.type; - if ( typeof prefsDescriptionSpecifications[type] == 'undefined' ) { + if ( typeof prefsSpecifications[type] == 'undefined' ) { $.error( 'createFieldDialog: invalid type: ' + type ); - } else if ( typeof prefsDescriptionSpecifications[type].builder == 'function' ) { - prefsDescriptionSpecifications[type].builder( options, function( field ) { + } else if ( typeof prefsSpecifications[type].builder == 'function' ) { + prefsSpecifications[type].builder( options, function( field ) { if ( field !== null ) { params.callback( field ); } @@ -410,10 +253,10 @@ return; } - //typeof prefsDescriptionSpecifications[type].builder == 'object' + //typeof prefsSpecifications[type].builder == 'object' description = { - fields: prefsDescriptionSpecifications[type].builder + fields: prefsSpecifications[type].builder }; } @@ -472,8 +315,8 @@ } ); } - var FieldConstructor = validFieldTypes[type]; - var field; + var FieldConstructor = validFieldTypes[type], + field; try { field = new FieldConstructor( fieldDescription, options ); @@ -521,13 +364,14 @@ { text: mw.msg( 'gadgets-formbuilder-editor-ok' ), click: function() { - var fieldDesc = $( this ).formBuilder( 'getDescription' ).fields[0]; + var fieldDesc = $( this ).formBuilder( 'getDescription' ).fields[0], name = fieldDesc.name; delete fieldDesc.name; $( this ).dialog( "close" ); + var ListField = validFieldTypes.list; callback( new ListField( { type: 'list', name: name, @@ -546,73 +390,6 @@ } ); } - function testOptional( value, element ) { - var rules = $( element ).rules(); - if ( typeof rules.required == 'undefined' || rules.required === false ) { - if ( value.length == 0 ) { - return true; - } - } - return false; - } - - //validator for "required" fields (without trimming whitespaces) - $.validator.addMethod( "requiredStrict", function( value, element ) { - return value.length > 0; - }, mw.msg( 'gadgets-formbuilder-required' ) ); - - //validator for "minlength" fields (without trimming whitespaces) - $.validator.addMethod( "minlengthStrict", function( value, element, param ) { - return testOptional( value, element ) || value.length >= param; - } ); - - //validator for "maxlength" fields (without trimming whitespaces) - $.validator.addMethod( "maxlengthStrict", function( value, element, param ) { - return testOptional( value, element ) || value.length <= param; - } ); - - //validator for integer fields - $.validator.addMethod( "integer", function( value, element ) { - return testOptional( value, element ) || /^-?\d+$/.test(value); - }, mw.msg( 'gadgets-formbuilder-integer' ) ); - - //validator for datepicker fields - $.validator.addMethod( "datePicker", function( value, element ) { - var format = $( element ).datepicker( 'option', 'dateFormat' ); - try { - var date = $.datepicker.parseDate( format, value ); - return true; - } catch ( e ) { - return false; - } - }, mw.msg( 'gadgets-formbuilder-date' ) ); - - //validator for colorpicker fields - $.validator.addMethod( "color", function( value, element ) { - return $.colorUtil.getRGB( value ) !== undefined; - }, mw.msg( 'gadgets-formbuilder-color' ) ); - - //Helper function for inheritance, see http://javascript.crockford.com/prototypal.html - function object(o) { - function F() {} - F.prototype = o; - return new F(); - } - - //Add a "smart" listener to watch for changes to an <input /> element - //This binds to several events, but calls the callback only if the value actually changed - function addSmartChangeListener( $input, callback ) { - var oldValue = $input.val(); - //bind all events that may change the value of the field (some are brower-specific) - $input.bind( 'keyup change propertychange input paste', function() { - var newValue = $input.val(); - if ( oldValue !== newValue ) { - oldValue = newValue; - callback(); - } - } ); - } - /* Basic interface for fields */ function Field( desc, options ) { if ( typeof options.idPrefix == 'undefined' ) { @@ -645,9 +422,11 @@ }; }; - /* A field with no content, generating an empty container */ - EmptyField.prototype = object( Field.prototype ); - EmptyField.prototype.constructor = EmptyField; + /* + * A field with no content, generating an empty container + * and checking existence and type of the 'type' member of description. + * + **/ function EmptyField( desc, options ) { Field.call( this, desc, options ); @@ -660,14 +439,14 @@ .addClass( 'formbuilder-field formbuilder-field-' + this.desc.type ) .data( 'field', this ); } + EmptyField.prototype = Object.create( Field.prototype ); + EmptyField.prototype.constructor = EmptyField; EmptyField.prototype.getElement = function() { return this.$div; }; /* A field with just a label */ - LabelField.prototype = object( EmptyField.prototype ); - LabelField.prototype.constructor = LabelField; function LabelField( desc, options ) { EmptyField.call( this, desc, options ); @@ -681,13 +460,13 @@ this.$div.append( this.$label ); } + LabelField.prototype = Object.create( EmptyField.prototype ); + LabelField.prototype.constructor = LabelField; - validFieldTypes["label"] = LabelField; + validFieldTypes.label = LabelField; /* Abstract base class for all "simple" fields. Should not be instantiated. */ - SimpleField.prototype = object( LabelField.prototype ); - SimpleField.prototype.constructor = SimpleField; - function SimpleField( desc, options ){ + function SimpleField( desc, options ) { LabelField.call( this, desc, options ); //Validate the 'name' member @@ -710,6 +489,8 @@ this.options = options; } } + SimpleField.prototype = Object.create( LabelField.prototype ); + SimpleField.prototype.constructor = SimpleField; SimpleField.prototype.getDesc = function( useValuesAsDefaults ) { var desc = clone( LabelField.prototype.getDesc.call( this, useValuesAsDefaults ) ); @@ -724,9 +505,7 @@ /* A field with a label and a checkbox */ - BooleanField.prototype = object( SimpleField.prototype ); - BooleanField.prototype.constructor = BooleanField; - function BooleanField( desc, options ){ + function BooleanField( desc, options ) { SimpleField.call( this, desc, options ); this.$c = $( '<input/>' ).attr( { @@ -737,7 +516,7 @@ if ( options.change ) { this.$c.change( function() { - options.change() + options.change(); } ); } @@ -752,21 +531,21 @@ this.$div.append( this.$c ); } + BooleanField.prototype = Object.create( SimpleField.prototype ); + BooleanField.prototype.constructor = BooleanField; BooleanField.prototype.getValues = function() { return pair( this.desc.name, this.$c.is( ':checked' ) ); }; - validFieldTypes["boolean"] = BooleanField; + validFieldTypes.boolean = BooleanField; /* A field with a textbox accepting string values */ - StringField.prototype = object( SimpleField.prototype ); - StringField.prototype.constructor = StringField; - function StringField( desc, options ){ + function StringField( desc, options ) { SimpleField.call( this, desc, options ); //Validate minlength and maxlength - var minlength = typeof desc.minlength != 'undefined' ? desc.minlength : 0, + var minlength = typeof desc.minlength != 'undefined' ? desc.minlength : 0, maxlength = typeof desc.maxlength != 'undefined' ? desc.maxlength : 1024; if ( !isInteger( minlength ) || minlength < 0 ) { @@ -801,6 +580,8 @@ this.$div.append( this.$text ); } + StringField.prototype = Object.create( SimpleField.prototype ); + StringField.prototype.constructor = StringField; StringField.prototype.getValues = function() { return pair( this.desc.name, this.$text.val() ); @@ -835,21 +616,19 @@ return settings; }; - validFieldTypes["string"] = StringField; + validFieldTypes.string = StringField; /* A field with a textbox accepting numeric values */ - NumberField.prototype = object( SimpleField.prototype ); - NumberField.prototype.constructor = NumberField; - function NumberField( desc, options ){ + function NumberField( desc, options ) { SimpleField.call( this, desc, options ); //Validation of description if ( desc.integer === true ) { - if ( typeof desc.min != 'undefined' && !isInteger( desc.min ) ){ + if ( typeof desc.min != 'undefined' && !isInteger( desc.min ) ) { $.error( "min is not an integer" ); } - if ( typeof desc.max != 'undefined' && !isInteger( desc.max ) ){ + if ( typeof desc.max != 'undefined' && !isInteger( desc.max ) ) { $.error( "max is not an integer" ); } } @@ -881,6 +660,8 @@ this.$div.append( this.$text ); } + NumberField.prototype = Object.create( SimpleField.prototype ); + NumberField.prototype.constructor = NumberField; NumberField.prototype.getValues = function() { var val = parseFloat( this.$text.val() ); @@ -922,12 +703,10 @@ return settings; }; - validFieldTypes["number"] = NumberField; + validFieldTypes.number = NumberField; /* A field with a drop-down list */ - SelectField.prototype = object( SimpleField.prototype ); - SelectField.prototype.constructor = SelectField; - function SelectField( desc, options ){ + function SelectField( desc, options ) { SimpleField.call( this, desc, options ); var $select = this.$select = $( '<select/>' ).attr( { @@ -935,8 +714,8 @@ name: this.options.idPrefix + this.desc.name } ); - var validValues = []; - var self = this; + var validValues = [], + self = this; $.each( this.desc.options, function( idx, option ) { var i = validValues.length; $( '<option/>' ) @@ -967,18 +746,18 @@ this.$div.append( $select ); } + SelectField.prototype = Object.create( SimpleField.prototype ); + SelectField.prototype.constructor = SelectField; SelectField.prototype.getValues = function() { var i = parseInt( this.$select.val(), 10 ); return pair( this.desc.name, this.validValues[i] ); }; - validFieldTypes["select"] = SelectField; + validFieldTypes.select = SelectField; /* A field with a slider, representing ranges of numbers */ - RangeField.prototype = object( SimpleField.prototype ); - RangeField.prototype.constructor = RangeField; - function RangeField( desc, options ){ + function RangeField( desc, options ) { SimpleField.call( this, desc, options ); //Validation @@ -1065,11 +844,13 @@ }, 1 ); }, stop: function( event, ui ) { + //After a delay, hide tooltip if the handle doesn't have focus and pointer isn't over the handle. setTimeout( function() { - if ( !$handle.is( ':focus' ) && !mouseOver) { + if ( !$slider.find( '.ui-slider-handle' ).is( ':focus' ) && !mouseOver ) { refreshTooltip( false, $slider.find( '.ui-slider-handle' ), ui.value ); } }, 300 ); + sliding = false; }, change: function( event, ui ) { @@ -1103,17 +884,17 @@ this.$div.append( $slider ); } + RangeField.prototype = Object.create( SimpleField.prototype ); + RangeField.prototype.constructor = RangeField; RangeField.prototype.getValues = function() { return pair( this.desc.name, this.$slider.slider( 'value' ) ); }; - validFieldTypes["range"] = RangeField; + validFieldTypes.range = RangeField; /* A field with a textbox with a datepicker */ - DateField.prototype = object( SimpleField.prototype ); - DateField.prototype.constructor = DateField; - function DateField( desc, options ){ + function DateField( desc, options ) { SimpleField.call( this, desc, options ); var $text = this.$text = $( '<input/>' ) @@ -1130,8 +911,8 @@ } } ); - var value = options.values && options.values[this.desc.name]; - var date; + var value = options.values && options.values[this.desc.name], + date; if ( typeof value != 'undefined' && value !== null ) { date = new Date( value ); @@ -1149,9 +930,11 @@ this.$div.append( this.$text ); } + DateField.prototype = Object.create( SimpleField.prototype ); + DateField.prototype.constructor = DateField; DateField.prototype.getValues = function() { - var d = this.$text.datepicker( 'getDate' ), + var d = this.$text.datepicker( 'getDate' ), res = {}; if ( d === null ) { @@ -1178,7 +961,7 @@ return settings; }; - validFieldTypes["date"] = DateField; + validFieldTypes.date = DateField; /* A field with color picker */ @@ -1191,16 +974,15 @@ //If a click happens outside the colorpicker while it is showed, remove it $( document ).mousedown( function( event ) { var $target = $( event.target ); - if ( $target.parents( '#colorpicker' ).length == 0 ) { + if ( $target.parents( '#colorpicker' ).length === 0 ) { closeColorPicker(); } } ); - ColorField.prototype = object( SimpleField.prototype ); - ColorField.prototype.constructor = ColorField; - function ColorField( desc, options ){ + function ColorField( desc, options ) { SimpleField.call( this, desc, options ); + var value; if ( typeof options.values != 'undefined' && typeof options.values[this.desc.name] != 'undefined' ) { value = options.values[this.desc.name]; } else { @@ -1252,6 +1034,8 @@ this.$div.append( this.$text ); } + ColorField.prototype = Object.create( SimpleField.prototype ); + ColorField.prototype.constructor = ColorField; ColorField.prototype.getValidationSettings = function() { var settings = SimpleField.prototype.getValidationSettings.call( this ), @@ -1273,13 +1057,11 @@ } }; - validFieldTypes["color"] = ColorField; + validFieldTypes.color = ColorField; /* A field that represent a section (group of fields) */ - SectionField.prototype = object( Field.prototype ); - SectionField.prototype.constructor = SectionField; function SectionField( desc, options, id ) { Field.call( this, desc, options ); @@ -1295,7 +1077,7 @@ this._createSlot( 'yes' ).appendTo( this.$div ); } - var field = this.desc.fields[i], + var field = this.desc.fields[i], FieldConstructor = validFieldTypes[field.type]; if ( typeof FieldConstructor != 'function' ) { @@ -1309,7 +1091,7 @@ editable = 'no'; } - var f = new FieldConstructor( field, options ), + var f = new FieldConstructor( field, options ), $slot = this._createSlot( editable, f ); $slot.appendTo( this.$div ); @@ -1320,6 +1102,8 @@ this._createSlot( 'yes' ).appendTo( this.$div ); } } + SectionField.prototype = Object.create( Field.prototype ); + SectionField.prototype.constructor = SectionField; SectionField.prototype.getElement = function() { return this.$div; @@ -1379,7 +1163,7 @@ }; SectionField.prototype._createSlot = function( editable, field ) { - var self = this, + var self = this, $slot = $( '<div/>' ).addClass( 'formbuilder-slot ui-widget' ), $divButtons; @@ -1413,7 +1197,7 @@ //Add the button for changing existing slots var type = field.getDesc().type; //TODO: using the 'builder' info is not optimal - if ( typeof prefsDescriptionSpecifications[type].builder != 'function' ) { + if ( typeof prefsSpecifications[type].builder != 'function' ) { $( '<a href="javascript:;" />' ) .addClass( 'formbuilder-button formbuilder-editor-button-edit ui-icon ui-icon-gear' ) .attr( 'title', mw.msg( 'gadgets-formbuilder-editor-edit-field' ) ) @@ -1425,7 +1209,7 @@ callback: function( newField ) { if ( newField !== null ) { //check that there are no duplicate preference names - var existingValues = self.$div.closest( '.formbuilder' ).formBuilder( 'getValues' ), + var existingValues = self.$div.closest( '.formbuilder' ).formBuilder( 'getValues' ), removedValues = field.getValues(), duplicateName = null; $.each( field.getValues(), function( name, val ) { @@ -1463,13 +1247,13 @@ .addClass( 'formbuilder-button formbuilder-editor-button-delete ui-icon ui-icon-trash' ) .attr( 'title', mw.msg( 'gadgets-formbuilder-editor-delete-field' ) ) .click( function( event, ui ) { - //Make both slots disappear, then delete them + //Make both slots disappear, then delete them $.each( [$slot, $slot.prev()], function( idx, $s ) { $s.slideUp( function() { self._deleteSlot( $s ); } ); } ); - } ) + } ) .appendTo( $divButtons ); //Make this slot draggable to allow moving it @@ -1495,7 +1279,8 @@ hoverClass: 'formbuilder-slot-can-drop', tolerance: 'pointer', drop: function( event, ui ) { - var srcSlot = ui.draggable, dstSlot = this; + var srcSlot = ui.draggable, + dstSlot = this; //Remove one empty slot surrounding source $( srcSlot ).prev().remove(); @@ -1524,7 +1309,7 @@ callback: function( field ) { if ( field !== null ) { //check that there are no duplicate preference names - var existingValues = $slot.closest( '.formbuilder' ).formBuilder( 'getValues' ), + var existingValues = $slot.closest( '.formbuilder' ).formBuilder( 'getValues' ), duplicateName = null; $.each( field.getValues(), function( name, val ) { if ( typeof existingValues[name] != 'undefined' ) { @@ -1538,7 +1323,7 @@ return false; } - var $newSlot = self._createSlot( 'yes', field ).hide(), + var $newSlot = self._createSlot( 'yes', field ).hide(), $newEmptySlot = self._createSlot( 'yes' ).hide(); $slot.after( $newSlot, $newEmptySlot ); @@ -1563,9 +1348,7 @@ }; - /* A field for 'bundle's */ - BundleField.prototype = object( EmptyField.prototype ); - BundleField.prototype.constructor = BundleField; + /* A field for 'bundle' type fields */ function BundleField( desc, options ) { EmptyField.call( this, desc, options ); @@ -1583,7 +1366,7 @@ tolerance: 'pointer', accept: '.formbuilder-slot-nonempty', drop: function( event, ui ) { - var $slot = $( ui.draggable ), + var $slot = $( ui.draggable ), $srcSection = $slot.parent(), $dstSection = $( section ); @@ -1622,7 +1405,7 @@ .addClass( 'formbuilder-button formbuilder-editor-button-edit-section ui-icon ui-icon-gear' ) .attr( 'title', mw.msg( 'gadgets-formbuilder-editor-edit-section' ) ) .click( function() { - var button = this, + var button = this, sectionField = $( ui.panel ).data( 'field' ); $( { @@ -1673,15 +1456,15 @@ } ); //Save for future reference - this.$ui_tabs_nav = $tabs.find( '.ui-tabs-nav' ) + this.$ui_tabs_nav = $tabs.find( '.ui-tabs-nav' ); var self = this; $.each( this.desc.sections, function( index, sectionDescription ) { - var id = self.options.idPrefix + 'section-' + getIncrementalCounter(), + var id = self.options.idPrefix + 'section-' + getIncrementalCounter(), sec = new SectionField( sectionDescription, options, id ); $tabs.append( sec.getElement() ) - .tabs( 'add', '#' + id, preproc( options.msgPrefix, sectionDescription.title ) ); + .tabs( 'add', '#' + id, preproc( options.msgPrefix, sectionDescription.title ) ); } ); if ( options.editable === true ) { @@ -1707,7 +1490,7 @@ { text: mw.msg( 'gadgets-formbuilder-editor-ok' ), click: function() { - var title = $( this ).formBuilder( 'getValues' ).title, + var title = $( this ).formBuilder( 'getValues' ).title, id = self.options.idPrefix + 'section-' + getIncrementalCounter(), newSectionDescription = { title: title, @@ -1742,11 +1525,13 @@ this.$div.append( $tabs ); } + BundleField.prototype = Object.create( EmptyField.prototype ); + BundleField.prototype.constructor = BundleField; BundleField.prototype.getValidationSettings = function() { var settings = {}; this.$ui_tabs_nav.find( 'a' ).each( function( idx, anchor ) { - var panel = $( anchor ).data( 'panel' ), + var panel = $( anchor ).data( 'panel' ), field = $( panel ).data( 'field' ); $.extend( true, settings, field.getValidationSettings() ); @@ -1758,7 +1543,7 @@ var desc = clone( this.desc ); desc.sections = []; this.$ui_tabs_nav.find( 'a' ).each( function( idx, anchor ) { - var panel = $( anchor ).data( 'panel' ), + var panel = $( anchor ).data( 'panel' ), field = $( panel ).data( 'field' ); desc.sections.push( field.getDesc( useValuesAsDefaults ) ); @@ -1769,7 +1554,7 @@ BundleField.prototype.getValues = function() { var values = {}; this.$ui_tabs_nav.find( 'a' ).each( function( idx, anchor ) { - var panel = $( anchor ).data( 'panel' ), + var panel = $( anchor ).data( 'panel' ), field = $( panel ).data( 'field' ); $.extend( values, field.getValues() ); @@ -1777,13 +1562,11 @@ return values; }; - validFieldTypes["bundle"] = BundleField; + 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 ); @@ -1811,6 +1594,8 @@ this._section = new SectionField( desc, sectionOptions ); this.$div.append( this._section.getElement() ); } + CompositeField.prototype = Object.create( EmptyField.prototype ); + CompositeField.prototype.constructor = CompositeField; CompositeField.prototype.getDesc = function( useValuesAsDefaults ) { var desc = clone( this.desc ); @@ -1826,12 +1611,10 @@ return this._section.getValidationSettings(); }; - validFieldTypes["composite"] = CompositeField; + validFieldTypes.composite = CompositeField; /* A field for 'composite' fields */ - ListField.prototype = object( EmptyField.prototype ); - ListField.prototype.constructor = ListField; function ListField( desc, options ) { EmptyField.call( this, desc, options ); @@ -1844,7 +1627,7 @@ } if ( ( typeof desc.field.type != 'string' ) - || prefsDescriptionSpecifications[desc.field.type].simple !== true ) + || prefsSpecifications[desc.field.type].simple !== true ) { $.error( "Missing or invalid field type specified in 'field' parameter." ); } @@ -1855,8 +1638,8 @@ options.values = {}; } - var value = ( typeof options.values[desc.name] != 'undefined' ) ? options.values[desc.name] : desc['default']; - var self = this; + var value = ( typeof options.values[desc.name] != 'undefined' ) ? options.values[desc.name] : desc['default'], + self = this; if ( typeof value != 'undefined' ) { $.each( value, function( index, itemValue ) { self._createItem( false, itemValue ); @@ -1880,12 +1663,14 @@ } ) .appendTo( this.$div ); } + ListField.prototype = Object.create( EmptyField.prototype ); + ListField.prototype.constructor = ListField; ListField.prototype._createItem = function( animated, itemValue ) { - var itemDesc = $.extend( {}, this.desc.field, { + var itemDesc = $.extend( {}, this.desc.field, { "name": this.desc.name - } ); - var itemOptions = $.extend( {}, this.options, { + } ), + itemOptions = $.extend( {}, this.options, { editable: false, idPrefix: this.options.idPrefix + getIncrementalCounter() + "-" } ); @@ -1896,15 +1681,14 @@ itemOptions.values = pair( this.desc.name, this.desc.field['default'] ); } - var FieldConstructor = validFieldTypes[this.desc.field.type]; - var itemField = new FieldConstructor( itemDesc, itemOptions ); - var $itemDiv = $( '<div/>' ) - .addClass( 'formbuilder-list-item' ) - .data( 'field', itemField ); - - var $itemContent = $( '<div/>' ) - .addClass( 'formbuilder-list-item-content' ) - .append( itemField.getElement() ); + var FieldConstructor = validFieldTypes[this.desc.field.type], + itemField = new FieldConstructor( itemDesc, itemOptions ), + $itemDiv = $( '<div/>' ) + .addClass( 'formbuilder-list-item' ) + .data( 'field', itemField ), + $itemContent = $( '<div/>' ) + .addClass( 'formbuilder-list-item-content' ) + .append( itemField.getElement() ); $( '<div/>' ) .addClass( 'formbuilder-list-item-container' ) @@ -1974,9 +1758,244 @@ return validationSettings; }; - validFieldTypes["list"] = ListField; + validFieldTypes.list = ListField; + /* Specifications of preferences descriptions syntax and field types */ + + //Describes 'name' and 'label' field members, common to all "simple" fields + var simpleFields = [ + { + "name": "name", + "type": "string", + "label": "name", + "required": true, + "maxlength": 40, + "default": "" + }, + { + "name": "label", + "type": "string", + "label": "label", + "required": false, + "default": "" + } + ]; + + //Used by preference editor to build field properties dialogs + //TODO: document + prefsSpecifications = { + "label": { + "simple": false, + "builder": [ { + "name": "label", + "type": "string", + "label": "label", + "required": false, + "default": "" + } ] + }, + "boolean": { + "simple": true, + "builder": simpleFields + }, + "string": { + "simple": true, + "builder": simpleFields.concat( [ + { + "name": "required", + "type": "boolean", + "label": "required", + "default": false + }, + { + "name": "minlength", + "type": "number", + "label": "minlength", + "integer": true, + "min": 0, + "required": false, + "default": null + }, + { + "name": "maxlength", + "type": "number", + "label": "maxlength", + "integer": true, + "min": 1, + "required": false, + "default": null + } + ] ) + }, + "number": { + "simple": true, + "builder": simpleFields.concat( [ + { + "name": "required", + "type": "boolean", + "label": "required", + "default": true + }, + { + "name": "integer", + "type": "boolean", + "label": "integer", + "default": false + }, + { + "name": "min", + "type": "number", + "label": "min", + "required": false, + "default": null + }, + { + "name": "max", + "type": "number", + "label": "max", + "required": false, + "default": null + } + ] ) + }, + "range": { + "simple": true, + "builder": simpleFields.concat( [ + { + "name": "min", + "type": "number", + "label": "min", + "required": true + }, + { + "name": "step", + "type": "number", + "label": "step", + "required": true, + "default": 1 + }, + { + "name": "max", + "type": "number", + "label": "max", + "required": true + } + ] ) + }, + "date": { + "simple": true, + "builder": simpleFields + }, + "color": { + "simple": true, + "builder": simpleFields + }, + "bundle": { + "simple": false, + "builder": function( options, callback ) { + callback( + new BundleField( { + "type": "bundle", + "sections": [ + { + "title": "Section 1", + "fields": [] + }, + { + "title": "Section 2", + "fields": [] + } + ] + }, options ) + ); + } + }, + "composite": { + "simple": true, + "builder": [ { + "name": "name", + "type": "string", + "label": "name", + "required": true, + "maxlength": 40, + "default": "" + } ] + }, + "list": { + "simple": true, + "builder": function( options, callback ) { + + //Create list of "simple" types + var selectOptions = []; + $.each( prefsSpecifications, function( type, typeInfo ) { + if ( typeInfo.simple === true ) { + selectOptions.push( { "name": type, "value": type } ); + } + } ); + + //Create the dialog to chose the field type + var $form = $( { + fields: [ { + "name": "name", + "type": "string", + "label": "name", + "required": true, + "maxlength": 40, + "default": "" + }, + { + "name": "type", + "type": "select", + "label": "type", + "options": selectOptions + } ] + } ).formBuilder( { idPrefix: 'list-chose-type-' } ) + .submit( function() { + return false; //prevent form submission + } ); + + $form.dialog( { + width: 450, + modal: true, + resizable: false, + title: mw.msg( 'gadgets-formbuilder-editor-create-field-title', 'list' ), + close: function() { + $( this ).remove(); + }, + buttons: [ + { + text: mw.msg( 'gadgets-formbuilder-editor-ok' ), + click: function() { + var values = $( this ).formBuilder( 'getValues' ); + $( this ).dialog( "close" ); + + var $dialog = $( this ); + createFieldDialog( { + type: values.type, + values: { + "name": values.name + }, + callback: function( field ) { + $dialog.dialog( 'close' ); + showEditFieldDialog( field.getDesc(), options, callback ); + return true; + } + }, { editable: true } ); + } + }, + { + text: mw.msg( 'gadgets-formbuilder-editor-cancel' ), + click: function() { + $( this ).dialog( "close" ); + } + } + ] + } ); + } + } + }; + /* Public methods */ /** @@ -2072,5 +2091,5 @@ $.error( 'Method ' + method + ' does not exist on jQuery.formBuilder' ); } }; -})( jQuery, mediaWiki ); +} )( jQuery, mediaWiki ); _______________________________________________ MediaWiki-CVS mailing list MediaWiki-CVS@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs