Index: branches/salvatoreingala/Gadgets/Gadgets.i18n.php |
— | — | @@ -73,7 +73,7 @@ |
74 | 74 | 'gadgets-formbuilder-editor-duplicate-name' => 'The preference name $1 has been used. Please chose a different name.', |
75 | 75 | 'gadgets-formbuilder-editor-edit-section' => 'Edit this section\'s title', |
76 | 76 | 'gadgets-formbuilder-editor-delete-section' => 'Delete this section and all his content', |
77 | | - 'gadgets-formbuilder-editor-new-section' => 'Delete this section and all his content', |
| 77 | + 'gadgets-formbuilder-editor-new-section' => 'Create a new section', |
78 | 78 | 'gadgets-formbuilder-editor-chose-title' => 'Chose the title of the new section:', |
79 | 79 | 'gadgets-formbuilder-editor-chose-title-title' => 'Chose section title', |
80 | 80 | ); |
Index: branches/salvatoreingala/Gadgets/Gadgets.php |
— | — | @@ -97,7 +97,7 @@ |
98 | 98 | 'gadgets-formbuilder-editor-delete-field', 'gadgets-formbuilder-editor-edit-field', 'gadgets-formbuilder-editor-insert-field', |
99 | 99 | 'gadgets-formbuilder-editor-chose-field', 'gadgets-formbuilder-editor-chose-field-title', 'gadgets-formbuilder-editor-create-field-title', |
100 | 100 | 'gadgets-formbuilder-editor-duplicate-name', 'gadgets-formbuilder-editor-delete-section', 'gadgets-formbuilder-editor-new-section', |
101 | | - 'gadgets-formbuilder-editor-chose-title', 'gadgets-formbuilder-editor-chose-title-title' |
| 101 | + 'gadgets-formbuilder-editor-edit-section', 'gadgets-formbuilder-editor-chose-title', 'gadgets-formbuilder-editor-chose-title-title' |
102 | 102 | ), |
103 | 103 | 'localBasePath' => $dir . 'ui/resources/', |
104 | 104 | 'remoteExtPath' => 'Gadgets/ui/resources' |
Index: branches/salvatoreingala/Gadgets/ui/resources/jquery.formBuilder.css |
— | — | @@ -48,8 +48,6 @@ |
49 | 49 | |
50 | 50 | .formbuilder-slot { |
51 | 51 | border: none; |
52 | | - padding-top: 0.5em; |
53 | | - padding-bottom: 0.5em; |
54 | 52 | } |
55 | 53 | |
56 | 54 | /* formBuilder editor */ |
— | — | @@ -65,11 +63,12 @@ |
66 | 64 | } |
67 | 65 | |
68 | 66 | .formbuilder-slot-can-drop { |
69 | | - border: 1px solid black; |
| 67 | + background: blue; |
| 68 | + opacity: 0.1; |
70 | 69 | } |
71 | 70 | |
72 | 71 | .formbuilder-editor-slot-buttons { |
73 | | - height: 19px; |
| 72 | + height: 17px; |
74 | 73 | } |
75 | 74 | |
76 | 75 | .formbuilder-editor-button { |
— | — | @@ -80,22 +79,21 @@ |
81 | 80 | cursor: move; |
82 | 81 | } |
83 | 82 | |
84 | | -.formbuilder-editor-button-new, .formbuilder-editor-button-new-section { |
| 83 | +.formbuilder-editor-button-new, .formbuilder-editor-button-new-section, |
| 84 | +.formbuilder-editor-button-edit, .formbuilder-editor-button-move |
| 85 | +{ |
85 | 86 | float: left; |
86 | 87 | } |
87 | 88 | |
88 | | -.formbuilder-editor-button-edit { |
89 | | - float: left; |
| 89 | +.formbuilder-editor-button-delete { |
| 90 | + float: right; |
90 | 91 | } |
91 | 92 | |
92 | | -.formbuilder-editor-button-move { |
| 93 | +/* Fixes a minor glitch in Firefox (buttons in tabs not floating properly) */ |
| 94 | +.formbuilder .ui-tabs-nav a > span :not(.formbuilder-editor-button) { |
93 | 95 | float: left; |
94 | 96 | } |
95 | 97 | |
96 | | -.formbuilder-editor-button-delete { |
97 | | - float: right; |
98 | | -} |
99 | | - |
100 | 98 | .formbuilder-editor-button-delete-section { |
101 | 99 | float: right; |
102 | 100 | } |
Index: branches/salvatoreingala/Gadgets/ui/resources/jquery.formBuilder.js |
— | — | @@ -5,20 +5,21 @@ |
6 | 6 | */ |
7 | 7 | |
8 | 8 | (function($, mw) { |
9 | | - |
10 | | - var idPrefix = "mw-gadgets-dialog-"; |
11 | | - |
| 9 | + |
12 | 10 | //Preprocesses strings end possibly replaces them with messages. |
13 | 11 | //If str starts with "@" the rest of the string is assumed to be |
14 | 12 | //a message, and the result of mw.msg is returned. |
15 | 13 | //Two "@@" at the beginning escape for a single "@". |
16 | | - function preproc( prefix, str ) { |
| 14 | + function preproc( msgPrefix, str ) { |
17 | 15 | if ( str.length <= 1 || str[0] !== '@' ) { |
18 | 16 | return str; |
19 | 17 | } else if ( str.substr( 0, 2 ) == '@@' ) { |
20 | 18 | return str.substr( 1 ); |
21 | 19 | } else { |
22 | | - return mw.message( prefix + str.substr( 1 ) ).plain(); |
| 20 | + if ( !msgPrefix ) { |
| 21 | + msgPrefix = ""; |
| 22 | + } |
| 23 | + return mw.message( msgPrefix + str.substr( 1 ) ).plain(); |
23 | 24 | } |
24 | 25 | } |
25 | 26 | |
— | — | @@ -45,6 +46,10 @@ |
46 | 47 | return res; |
47 | 48 | } |
48 | 49 | |
| 50 | + function isInteger( val ) { |
| 51 | + return typeof val == 'number' && val === Math.floor( val ); |
| 52 | + } |
| 53 | + |
49 | 54 | function testOptional( value, element ) { |
50 | 55 | var rules = $( element ).rules(); |
51 | 56 | if ( typeof rules.required == 'undefined' || rules.required === false ) { |
— | — | @@ -146,7 +151,7 @@ |
147 | 152 | "type": "number", |
148 | 153 | "label": "maxlength", |
149 | 154 | "integer": true, |
150 | | - "min": 0, |
| 155 | + "min": 1, |
151 | 156 | "required": false, |
152 | 157 | "default": null |
153 | 158 | } |
— | — | @@ -178,13 +183,54 @@ |
179 | 184 | "required": false, |
180 | 185 | "default": null |
181 | 186 | } |
182 | | - //TODO: other fields |
183 | | - ] ) |
| 187 | + ] ), |
| 188 | + //TODO: "select" is missing |
| 189 | + "range": simpleField.concat( [ |
| 190 | + { |
| 191 | + "name": "min", |
| 192 | + "type": "number", |
| 193 | + "label": "min", |
| 194 | + "required": true, |
| 195 | + }, |
| 196 | + { |
| 197 | + "name": "step", |
| 198 | + "type": "number", |
| 199 | + "label": "step", |
| 200 | + "required": true, |
| 201 | + "default": 1 |
| 202 | + }, |
| 203 | + { |
| 204 | + "name": "max", |
| 205 | + "type": "number", |
| 206 | + "label": "max", |
| 207 | + "required": true, |
| 208 | + } |
| 209 | + ] ), |
| 210 | + "date": simpleField, |
| 211 | + "color": simpleField, |
| 212 | + "bundle": function( options ) { |
| 213 | + return new BundleField( { |
| 214 | + "type": "bundle", |
| 215 | + "sections": [ |
| 216 | + { |
| 217 | + "title": "Section 1", |
| 218 | + "fields": [] |
| 219 | + }, |
| 220 | + { |
| 221 | + "title": "Section 2", |
| 222 | + "fields": [] |
| 223 | + } |
| 224 | + ] |
| 225 | + }, options ) |
| 226 | + } |
184 | 227 | }; |
185 | 228 | |
186 | 229 | /* Basic interface for fields */ |
187 | 230 | function Field( desc, options ) { |
188 | | - this.prefix = options.prefix; |
| 231 | + if ( typeof options.idPrefix == 'undefined' ) { |
| 232 | + options.idPrefix = 'formbuilder-'; |
| 233 | + } |
| 234 | + |
189 | 235 | this.desc = desc; |
190 | 236 | this.options = options; |
191 | 237 | } |
— | — | @@ -241,8 +287,8 @@ |
242 | 288 | } |
243 | 289 | |
244 | 290 | var $label = $( '<label/>' ) |
245 | | - .text( preproc( this.prefix, this.desc.label ) ) |
246 | | - .attr('for', idPrefix + this.desc.name ); |
| 291 | + .text( preproc( this.options.msgPrefix, this.desc.label ) ) |
| 292 | + .attr('for', this.options.idPrefix + this.desc.name ); |
247 | 293 | |
248 | 294 | this.$div.append( $label ); |
249 | 295 | } |
— | — | @@ -252,6 +298,27 @@ |
253 | 299 | SimpleField.prototype.constructor = SimpleField; |
254 | 300 | function SimpleField( desc, options ){ |
255 | 301 | LabelField.call( this, desc, options ); |
| 302 | + |
| 303 | + //Validate the 'name' member |
| 304 | + if ( typeof desc.name != 'string' || /^[a-zA-Z_][a-zA-Z0-9_]*$/.test( desc.name ) === false ) { |
| 305 | + $.error( 'invalid name' ); |
| 306 | + } |
| 307 | + if ( typeof desc.name.length > 40 ) { |
| 308 | + $.error( 'name must be no longer than 40 characters' ); |
| 309 | + } |
| 310 | + |
| 311 | + //Use default if it is given and no value has been set |
| 312 | + if ( ( typeof options.values == 'undefined' || typeof options.values[desc.name] == 'undefined' ) |
| 313 | + && typeof desc['default'] != 'undefined' ) |
| 314 | + { |
| 315 | + if ( typeof options.values == 'undefined' ) { |
| 316 | + options.values = {}; |
| 317 | + } |
| 318 | + |
| 319 | + options.values[desc.name] = desc['default']; |
| 320 | + |
| 321 | + this.options = options; |
| 322 | + } |
256 | 323 | } |
257 | 324 | |
258 | 325 | SimpleField.prototype.getDesc = function( useValuesAsDefaults ) { |
— | — | @@ -274,8 +341,8 @@ |
275 | 342 | |
276 | 343 | this.$c = $( '<input/>' ).attr( { |
277 | 344 | type: 'checkbox', |
278 | | - id: idPrefix + this.desc.name, |
279 | | - name: idPrefix + this.desc.name |
| 345 | + id: this.options.idPrefix + this.desc.name, |
| 346 | + name: this.options.idPrefix + this.desc.name |
280 | 347 | } ); |
281 | 348 | |
282 | 349 | var value = options.values && options.values[this.desc.name]; |
— | — | @@ -302,10 +369,24 @@ |
303 | 370 | function StringField( desc, options ){ |
304 | 371 | SimpleField.call( this, desc, options ); |
305 | 372 | |
| 373 | + //Validate minlength and maxlength |
| 374 | + var minlength = typeof desc.minlength != 'undefined' ? desc.minlength : 0, |
| 375 | + maxlength = typeof desc.maxlength != 'undefined' ? desc.maxlength : 1024; |
| 376 | + |
| 377 | + if ( !isInteger( minlength ) || minlength < 0 ) { |
| 378 | + $.error( "minlength must be a non-negative integer" ); |
| 379 | + } |
| 380 | + if ( !isInteger( maxlength ) || maxlength <= 0 ) { |
| 381 | + $.error( "maxlength must be a positive integer" ); |
| 382 | + } |
| 383 | + if ( maxlength < minlength ) { |
| 384 | + $.error( "maxlength must be no less than minlength" ); |
| 385 | + } |
| 386 | + |
306 | 387 | this.$text = $( '<input/>' ).attr( { |
307 | 388 | type: 'text', |
308 | | - id: idPrefix + this.desc.name, |
309 | | - name: idPrefix + this.desc.name |
| 389 | + id: this.options.idPrefix + this.desc.name, |
| 390 | + name: this.options.idPrefix + this.desc.name |
310 | 391 | } ); |
311 | 392 | |
312 | 393 | var value = options.values && options.values[this.desc.name]; |
— | — | @@ -326,7 +407,7 @@ |
327 | 408 | |
328 | 409 | StringField.prototype.getValidationSettings = function() { |
329 | 410 | var settings = SimpleField.prototype.getValidationSettings.call( this ), |
330 | | - fieldId = idPrefix + this.desc.name; |
| 411 | + fieldId = this.options.idPrefix + this.desc.name; |
331 | 412 | |
332 | 413 | settings.rules[fieldId] = {}; |
333 | 414 | var fieldRules = settings.rules[fieldId], |
— | — | @@ -362,10 +443,25 @@ |
363 | 444 | function NumberField( desc, options ){ |
364 | 445 | SimpleField.call( this, desc, options ); |
365 | 446 | |
| 447 | + //Validation of description |
| 448 | + if ( desc.integer === true ) { |
| 449 | + if ( typeof desc.min != 'undefined' && !isInteger( desc.min ) ){ |
| 450 | + $.error( "min is not an integer" ); |
| 451 | + } |
| 452 | + if ( typeof desc.max != 'undefined' && !isInteger( desc.max ) ){ |
| 453 | + $.error( "max is not an integer" ); |
| 454 | + } |
| 455 | + } |
| 456 | + |
| 457 | + if ( typeof desc.min != 'undefined' && typeof desc.max != 'undefined' && desc.min > desc.max ) { |
| 458 | + $.error( 'max must be no less than min' ); |
| 459 | + } |
| 460 | + |
| 461 | + |
366 | 462 | this.$text = $( '<input/>' ).attr( { |
367 | 463 | type: 'text', |
368 | | - id: idPrefix + this.desc.name, |
369 | | - name: idPrefix + this.desc.name |
| 464 | + id: this.options.idPrefix + this.desc.name, |
| 465 | + name: this.options.idPrefix + this.desc.name |
370 | 466 | } ); |
371 | 467 | |
372 | 468 | var value = options.values && options.values[this.desc.name]; |
— | — | @@ -387,7 +483,7 @@ |
388 | 484 | |
389 | 485 | NumberField.prototype.getValidationSettings = function() { |
390 | 486 | var settings = SimpleField.prototype.getValidationSettings.call( this ), |
391 | | - fieldId = idPrefix + this.desc.name; |
| 487 | + fieldId = this.options.idPrefix + this.desc.name; |
392 | 488 | |
393 | 489 | settings.rules[fieldId] = {}; |
394 | 490 | var fieldRules = settings.rules[fieldId], |
— | — | @@ -429,8 +525,8 @@ |
430 | 526 | SimpleField.call( this, desc, options ); |
431 | 527 | |
432 | 528 | var $select = this.$select = $( '<select/>' ).attr( { |
433 | | - id: idPrefix + this.desc.name, |
434 | | - name: idPrefix + this.desc.name |
| 529 | + id: this.options.idPrefix + this.desc.name, |
| 530 | + name: this.options.idPrefix + this.desc.name |
435 | 531 | } ); |
436 | 532 | |
437 | 533 | var validValues = []; |
— | — | @@ -438,7 +534,7 @@ |
439 | 535 | $.each( this.desc.options, function( idx, option ) { |
440 | 536 | var i = validValues.length; |
441 | 537 | $( '<option/>' ) |
442 | | - .text( preproc( self.prefix, option.name ) ) |
| 538 | + .text( preproc( self.options.msgPrefix, option.name ) ) |
443 | 539 | .val( i ) |
444 | 540 | .appendTo( $select ); |
445 | 541 | validValues.push( option.value ); |
— | — | @@ -472,18 +568,22 @@ |
473 | 569 | function RangeField( desc, options ){ |
474 | 570 | SimpleField.call( this, desc, options ); |
475 | 571 | |
476 | | - if ( typeof this.desc.min != 'number' ) { |
477 | | - $.error( "desc.min is invalid" ); |
| 572 | + //Validation |
| 573 | + if ( desc.min > desc.max ) { |
| 574 | + $.error( "max must be no less than min" ); |
478 | 575 | } |
479 | | - |
480 | | - if ( typeof this.desc.max != 'number' ) { |
481 | | - $.error( "desc.max is invalid" ); |
| 576 | + if ( desc.step <= 0 ) { |
| 577 | + $.error( "step must be a positive number" ); |
482 | 578 | } |
483 | 579 | |
484 | | - if ( typeof this.desc.step != 'undefined' && typeof this.desc.step != 'number' ) { |
485 | | - $.error( "desc.step is invalid" ); |
| 580 | + //Check that max differs from min by an integer multiple of step |
| 581 | + //(that is: (max - min) / step is integer, with good approximation) |
| 582 | + var eps = 1.0e-6; //tolerance |
| 583 | + var tmp = ( desc.max - desc.min ) / desc.step; |
| 584 | + if ( Math.abs( tmp - Math.floor( tmp ) ) > eps ) { |
| 585 | + $.error( "The list {min, min + step, min + 2*step, ...} must contain max" ); |
486 | 586 | } |
487 | | - |
| 587 | + |
488 | 588 | var value = options.values && options.values[this.desc.name]; |
489 | 589 | if ( typeof value != 'undefined' ) { |
490 | 590 | if ( typeof value != 'number' ) { |
— | — | @@ -495,7 +595,7 @@ |
496 | 596 | } |
497 | 597 | |
498 | 598 | var $slider = this.$slider = $( '<div/>' ) |
499 | | - .attr( 'id', idPrefix + this.desc.name ); |
| 599 | + .attr( 'id', this.options.idPrefix + this.desc.name ); |
500 | 600 | |
501 | 601 | var rangeOptions = { |
502 | 602 | min: this.desc.min, |
— | — | @@ -527,11 +627,12 @@ |
528 | 628 | function DateField( desc, options ){ |
529 | 629 | SimpleField.call( this, desc, options ); |
530 | 630 | |
531 | | - this.$text = $( '<input/>' ).attr( { |
532 | | - type: 'text', |
533 | | - id: idPrefix + this.desc.name, |
534 | | - name: idPrefix + this.desc.name |
535 | | - } ).datepicker( { |
| 631 | + this.$text = $( '<input/>' ) |
| 632 | + .attr( { |
| 633 | + type: 'text', |
| 634 | + id: this.options.idPrefix + this.desc.name, |
| 635 | + name: this.options.idPrefix + this.desc.name |
| 636 | + } ).datepicker( { |
536 | 637 | onSelect: function() { |
537 | 638 | //Force validation, so that a previous 'invalid' state is removed |
538 | 639 | $( this ).valid(); |
— | — | @@ -558,7 +659,7 @@ |
559 | 660 | res = {}; |
560 | 661 | |
561 | 662 | if ( d === null ) { |
562 | | - return null; |
| 663 | + return pair( this.desc.name, null ); |
563 | 664 | } |
564 | 665 | |
565 | 666 | //UTC date in ISO 8601 format [YYYY]-[MM]-[DD]T[hh]:[mm]:[ss]Z |
— | — | @@ -573,7 +674,7 @@ |
574 | 675 | |
575 | 676 | DateField.prototype.getValidationSettings = function() { |
576 | 677 | var settings = SimpleField.prototype.getValidationSettings.call( this ), |
577 | | - fieldId = idPrefix + this.desc.name; |
| 678 | + fieldId = this.options.idPrefix + this.desc.name; |
578 | 679 | |
579 | 680 | settings.rules[fieldId] = { |
580 | 681 | "datePicker": true |
— | — | @@ -604,13 +705,17 @@ |
605 | 706 | function ColorField( desc, options ){ |
606 | 707 | SimpleField.call( this, desc, options ); |
607 | 708 | |
608 | | - var value = ( options.values && options.values[this.desc.name] ) || ''; |
| 709 | + if ( typeof options.values != 'undefined' && typeof options.values[this.desc.name] != 'undefined' ) { |
| 710 | + value = options.values[this.desc.name]; |
| 711 | + } else { |
| 712 | + value = ''; |
| 713 | + } |
609 | 714 | |
610 | 715 | this.$text = $( '<input/>' ).attr( { |
611 | | - type: 'text', |
612 | | - id: idPrefix + this.desc.name, |
613 | | - name: idPrefix + this.desc.name |
614 | | - } ) |
| 716 | + type: 'text', |
| 717 | + id: this.options.idPrefix + this.desc.name, |
| 718 | + name: this.options.idPrefix + this.desc.name |
| 719 | + } ) |
615 | 720 | .addClass( 'colorpicker-input' ) |
616 | 721 | .val( value ) |
617 | 722 | .css( 'background-color', value ) |
— | — | @@ -649,7 +754,7 @@ |
650 | 755 | |
651 | 756 | ColorField.prototype.getValidationSettings = function() { |
652 | 757 | var settings = SimpleField.prototype.getValidationSettings.call( this ), |
653 | | - fieldId = idPrefix + this.desc.name; |
| 758 | + fieldId = this.options.idPrefix + this.desc.name; |
654 | 759 | |
655 | 760 | settings.rules[fieldId] = { |
656 | 761 | "color": true |
— | — | @@ -659,8 +764,12 @@ |
660 | 765 | |
661 | 766 | ColorField.prototype.getValues = function() { |
662 | 767 | var color = $.colorUtil.getRGB( this.$text.val() ); |
663 | | - return pair( this.desc.name, '#' + pad( color[0].toString( 16 ), 2 ) + |
664 | | - pad( color[1].toString( 16 ), 2 ) + pad( color[2].toString( 16 ), 2 ) ); |
| 768 | + if ( color ) { |
| 769 | + return pair( this.desc.name, '#' + pad( color[0].toString( 16 ), 2 ) + |
| 770 | + pad( color[1].toString( 16 ), 2 ) + pad( color[2].toString( 16 ), 2 ) ); |
| 771 | + } else { |
| 772 | + return pair( this.desc.name, null ); |
| 773 | + } |
665 | 774 | }; |
666 | 775 | |
667 | 776 | validFieldTypes["color"] = ColorField; |
— | — | @@ -717,14 +826,12 @@ |
718 | 827 | //TODO: kill "intro"s and make "label" fields, instead? |
719 | 828 | if ( typeof this.desc.intro == 'string' ) { |
720 | 829 | $( '<p/>' ) |
721 | | - .text( preproc( this.prefix, this.desc.intro ) ) |
| 830 | + .text( preproc( this.options.msgPrefix, this.desc.intro ) ) |
722 | 831 | .addClass( 'formBuilder-intro' ) |
723 | 832 | .appendTo( this.$div ); |
724 | 833 | } |
725 | 834 | |
726 | 835 | for ( var i = 0; i < this.desc.fields.length; i++ ) { |
727 | | - //TODO: validate fieldName |
728 | | - |
729 | 836 | if ( options.editable === true ) { |
730 | 837 | //add an empty slot |
731 | 838 | this._createSlot( true ).appendTo( this.$div ); |
— | — | @@ -855,8 +962,16 @@ |
856 | 963 | type = params.type; |
857 | 964 | if ( typeof prefsDescriptionSpecifications[type] == 'undefined' ) { |
858 | 965 | $.error( 'createFieldDialog: invalid type: ' + type ); |
| 966 | + } else if ( typeof prefsDescriptionSpecifications[type] == 'function' ) { |
| 967 | + var field = prefsDescriptionSpecifications[type]( this.options ); |
| 968 | + if ( params.callback( field ) === true ) { |
| 969 | + $( this ).dialog( "close" ); |
| 970 | + } |
| 971 | + return; |
859 | 972 | } |
860 | 973 | |
| 974 | + //typeof prefsDescriptionSpecifications[type] == 'object' |
| 975 | + |
861 | 976 | description = { |
862 | 977 | fields: prefsDescriptionSpecifications[type] |
863 | 978 | }; |
— | — | @@ -866,13 +981,6 @@ |
867 | 982 | values = params.values; |
868 | 983 | } else { |
869 | 984 | values = {}; |
870 | | - //Set defaults (if given) as starting values |
871 | | - //TODO: should field constructors just use default if no value is given? |
872 | | - $.each( description.fields, function( idx, field ) { |
873 | | - if ( typeof field['default'] != 'undefined' ) { |
874 | | - values[field.name] = field['default']; |
875 | | - } |
876 | | - } ); |
877 | 985 | } |
878 | 986 | |
879 | 987 | //Create the dialog to set field properties |
— | — | @@ -899,7 +1007,7 @@ |
900 | 1008 | var fieldDescription = $( form ).formBuilder( 'getValues' ); |
901 | 1009 | |
902 | 1010 | if ( typeof type != 'undefined' ) { |
903 | | - //Remove properties the equal their default |
| 1011 | + //Remove properties that equal their default |
904 | 1012 | $.each( description.fields, function( index, fieldSpec ) { |
905 | 1013 | var property = fieldSpec.name; |
906 | 1014 | if ( fieldDescription[property] === fieldSpec['default'] ) { |
— | — | @@ -969,7 +1077,7 @@ |
970 | 1078 | |
971 | 1079 | if ( editable ) { |
972 | 1080 | $slot.addClass( 'formbuilder-slot-nonempty' ); |
973 | | - |
| 1081 | + |
974 | 1082 | //Add the handle for moving slots |
975 | 1083 | $( '<span />' ) |
976 | 1084 | .addClass( 'formbuilder-editor-button formbuilder-editor-button-move ui-icon ui-icon-arrow-4' ) |
— | — | @@ -980,46 +1088,49 @@ |
981 | 1089 | .appendTo( $divButtons ); |
982 | 1090 | |
983 | 1091 | //Add the button for changing existing slots |
984 | | - $( '<a href="javascript:;" />' ) |
985 | | - .addClass( 'formbuilder-editor-button formbuilder-editor-button-edit ui-icon ui-icon-gear' ) |
986 | | - .attr( 'title', mw.msg( 'gadgets-formbuilder-editor-edit-field' ) ) |
987 | | - .click( function() { |
988 | | - self._createFieldDialog( { |
989 | | - type: field.getDesc().type, |
990 | | - values: field.getDesc(), |
991 | | - callback: function( newField ) { |
992 | | - if ( newField !== null ) { |
993 | | - //check that there are no duplicate preference names |
994 | | - var existingValues = self.$div.closest( 'form' ).formBuilder( 'getValues' ), |
995 | | - removedValues = field.getValues(), |
996 | | - duplicateName = null; |
997 | | - $.each( field.getValues(), function( name, val ) { |
998 | | - //Only complain for preference names that are not in names for the field being replaced |
999 | | - if ( typeof existingValues[name] != 'undefined' && removedValues[name] == 'undefined' ) { |
1000 | | - duplicateName = name; |
| 1092 | + var type = field.getDesc().type; |
| 1093 | + if ( typeof prefsDescriptionSpecifications[type] != 'function' ) { |
| 1094 | + $( '<a href="javascript:;" />' ) |
| 1095 | + .addClass( 'formbuilder-editor-button formbuilder-editor-button-edit ui-icon ui-icon-gear' ) |
| 1096 | + .attr( 'title', mw.msg( 'gadgets-formbuilder-editor-edit-field' ) ) |
| 1097 | + .click( function() { |
| 1098 | + self._createFieldDialog( { |
| 1099 | + type: field.getDesc().type, |
| 1100 | + values: field.getDesc(), |
| 1101 | + callback: function( newField ) { |
| 1102 | + if ( newField !== null ) { |
| 1103 | + //check that there are no duplicate preference names |
| 1104 | + var existingValues = self.$div.closest( '.formbuilder' ).formBuilder( 'getValues' ), |
| 1105 | + removedValues = field.getValues(), |
| 1106 | + duplicateName = null; |
| 1107 | + $.each( field.getValues(), function( name, val ) { |
| 1108 | + //Only complain for preference names that are not in names for the field being replaced |
| 1109 | + if ( typeof existingValues[name] != 'undefined' && removedValues[name] == 'undefined' ) { |
| 1110 | + duplicateName = name; |
| 1111 | + return false; |
| 1112 | + } |
| 1113 | + } ); |
| 1114 | + |
| 1115 | + if ( duplicateName !== null ) { |
| 1116 | + alert( mw.msg( 'gadgets-formbuilder-editor-duplicate-name', duplicateName ) ); |
1001 | 1117 | return false; |
1002 | 1118 | } |
1003 | | - } ); |
1004 | | - |
1005 | | - if ( duplicateName !== null ) { |
1006 | | - alert( mw.msg( 'gadgets-formbuilder-editor-duplicate-name', duplicateName ) ); |
1007 | | - return false; |
| 1119 | + |
| 1120 | + var $newSlot = self._createSlot( true, newField ); |
| 1121 | + |
| 1122 | + deleteFieldRules( field ); |
| 1123 | + |
| 1124 | + $slot.replaceWith( $newSlot ); |
| 1125 | + |
| 1126 | + //Add field's validation rules |
| 1127 | + addFieldRules( newField ); |
1008 | 1128 | } |
1009 | | - |
1010 | | - var $newSlot = self._createSlot( true, newField ); |
1011 | | - |
1012 | | - deleteFieldRules( field ); |
1013 | | - |
1014 | | - $slot.replaceWith( $newSlot ); |
1015 | | - |
1016 | | - //Add field's validation rules |
1017 | | - addFieldRules( newField ); |
| 1129 | + return true; |
1018 | 1130 | } |
1019 | | - return true; |
1020 | | - } |
1021 | | - } ); |
1022 | | - } ) |
1023 | | - .appendTo( $divButtons ); |
| 1131 | + } ); |
| 1132 | + } ) |
| 1133 | + .appendTo( $divButtons ); |
| 1134 | + } |
1024 | 1135 | |
1025 | 1136 | //Add the button to delete slots |
1026 | 1137 | $( '<a href="javascript:;" />' ) |
— | — | @@ -1040,7 +1151,8 @@ |
1041 | 1152 | revert: true, |
1042 | 1153 | handle: ".formbuilder-editor-button-move", |
1043 | 1154 | helper: "original", |
1044 | | - zIndex: $slot.closest( 'form' ).zIndex() + 1000, //TODO: ugly, find a better way |
| 1155 | + zIndex: $slot.closest( '.formbuilder' ).zIndex() + 1000, //TODO: ugly, find a better way |
| 1156 | + scroll: false, |
1045 | 1157 | opacity: 0.8, |
1046 | 1158 | cursor: "move", |
1047 | 1159 | cursorAt: { |
— | — | @@ -1085,7 +1197,7 @@ |
1086 | 1198 | callback: function( field ) { |
1087 | 1199 | if ( field !== null ) { |
1088 | 1200 | //check that there are no duplicate preference names |
1089 | | - var existingValues = $slot.closest( 'form' ).formBuilder( 'getValues' ), |
| 1201 | + var existingValues = $slot.closest( '.formbuilder' ).formBuilder( 'getValues' ), |
1090 | 1202 | duplicateName = null; |
1091 | 1203 | $.each( field.getValues(), function( name, val ) { |
1092 | 1204 | if ( typeof existingValues[name] != 'undefined' ) { |
— | — | @@ -1109,6 +1221,9 @@ |
1110 | 1222 | |
1111 | 1223 | //Add field's validation rules |
1112 | 1224 | addFieldRules( field ); |
| 1225 | + |
| 1226 | + //Ensure immediate visual feedback if the current value is invalid |
| 1227 | + self.$div.closest( '.formbuilder' ).formBuilder( 'validate' ); |
1113 | 1228 | } |
1114 | 1229 | return true; |
1115 | 1230 | } |
— | — | @@ -1129,12 +1244,36 @@ |
1130 | 1245 | |
1131 | 1246 | //Create tabs |
1132 | 1247 | var $tabs = this.$tabs = $( '<div><ul></ul></div>' ) |
1133 | | - .attr( 'id', idPrefix + 'tab-' + getIncrementalCounter() ) |
| 1248 | + .attr( 'id', this.options.idPrefix + 'tab-' + getIncrementalCounter() ) |
1134 | 1249 | .tabs( { |
1135 | 1250 | add: function( event, ui ) { |
1136 | 1251 | //Links the anchor to the panel |
1137 | 1252 | $( ui.tab ).data( 'panel', ui.panel ); |
1138 | 1253 | |
| 1254 | + //Allow to drop over tabs to move slots around |
| 1255 | + var section = ui.panel; |
| 1256 | + $( ui.tab ).droppable( { |
| 1257 | + tolerance: 'pointer', |
| 1258 | + accept: '.formbuilder-slot-nonempty', |
| 1259 | + drop: function( event, ui ) { |
| 1260 | + var $slot = $( ui.draggable ), |
| 1261 | + $srcSection = $slot.parent(), |
| 1262 | + $dstSection = $( section ); |
| 1263 | + |
| 1264 | + if ( $dstSection.get( 0 ) !== $srcSection.get( 0 ) ) { |
| 1265 | + //move the slot (and the next empty slot) to dstSection with a nice animation |
| 1266 | + var $slots = $slot.add( $slot.next() ); |
| 1267 | + $slots.slideUp( 'fast' ) |
| 1268 | + .promise().done( function() { |
| 1269 | + $tabs.tabs( 'select', '#' + $dstSection.attr( 'id' ) ); |
| 1270 | + $slots.detach() |
| 1271 | + .appendTo( $dstSection ) |
| 1272 | + .slideDown( 'fast' ); |
| 1273 | + } ); |
| 1274 | + } |
| 1275 | + } |
| 1276 | + } ); |
| 1277 | + |
1139 | 1278 | if ( options.editable === true ) { |
1140 | 1279 | //Add "delete section" button |
1141 | 1280 | $( '<span />' ) |
— | — | @@ -1205,13 +1344,16 @@ |
1206 | 1345 | } |
1207 | 1346 | } ); |
1208 | 1347 | |
| 1348 | + //Save for future reference |
| 1349 | + this.$ui_tabs_nav = $tabs.find( '.ui-tabs-nav' ) |
| 1350 | + |
1209 | 1351 | var self = this; |
1210 | 1352 | $.each( this.desc.sections, function( index, sectionDescription ) { |
1211 | | - var id = idPrefix + 'section-' + getIncrementalCounter(), |
| 1353 | + var id = self.options.idPrefix + 'section-' + getIncrementalCounter(), |
1212 | 1354 | sec = new SectionField( sectionDescription, options, id ); |
1213 | 1355 | |
1214 | 1356 | $tabs.append( sec.getElement() ) |
1215 | | - .tabs( 'add', '#' + id, preproc( options.prefix, sectionDescription.title ) ); |
| 1357 | + .tabs( 'add', '#' + id, preproc( options.msgPrefix, sectionDescription.title ) ); |
1216 | 1358 | } ); |
1217 | 1359 | |
1218 | 1360 | if ( options.editable === true ) { |
— | — | @@ -1238,7 +1380,7 @@ |
1239 | 1381 | text: mw.msg( 'gadgets-formbuilder-editor-ok' ), |
1240 | 1382 | click: function() { |
1241 | 1383 | var title = $( this ).formBuilder( 'getValues' ).title, |
1242 | | - id = idPrefix + 'section-' + getIncrementalCounter(), |
| 1384 | + id = self.options.idPrefix + 'section-' + getIncrementalCounter(), |
1243 | 1385 | newSectionDescription = { |
1244 | 1386 | title: title, |
1245 | 1387 | fields: [] |
— | — | @@ -1246,7 +1388,7 @@ |
1247 | 1389 | newSection = new SectionField( newSectionDescription, options, id ); |
1248 | 1390 | |
1249 | 1391 | $tabs.append( newSection.getElement() ) |
1250 | | - .tabs( 'add', '#' + id, preproc( options.prefix, title ) ); |
| 1392 | + .tabs( 'add', '#' + id, preproc( options.msgPrefix, title ) ); |
1251 | 1393 | |
1252 | 1394 | $( this ).dialog( "close" ); |
1253 | 1395 | } |
— | — | @@ -1261,10 +1403,10 @@ |
1262 | 1404 | } ); |
1263 | 1405 | } ) |
1264 | 1406 | .wrap( '<li />' ).parent() |
1265 | | - .prependTo( $tabs.find('.ui-tabs-nav' ) ); |
| 1407 | + .prependTo( this.$ui_tabs_nav ); |
1266 | 1408 | |
1267 | 1409 | //Make the tabs sortable |
1268 | | - $tabs.tabs().find( '.ui-tabs-nav' ).sortable( { |
| 1410 | + this.$ui_tabs_nav.sortable( { |
1269 | 1411 | axis: 'x', |
1270 | 1412 | items: 'li:not(:has(.formbuilder-editor-button-new-section))' |
1271 | 1413 | } ); |
— | — | @@ -1275,7 +1417,7 @@ |
1276 | 1418 | |
1277 | 1419 | BundleField.prototype.getValidationSettings = function() { |
1278 | 1420 | var settings = {}; |
1279 | | - this.$div.find( '.ui-tabs-nav a' ).each( function( idx, anchor ) { |
| 1421 | + this.$ui_tabs_nav.find( 'a' ).each( function( idx, anchor ) { |
1280 | 1422 | var panel = $( anchor ).data( 'panel' ), |
1281 | 1423 | field = $( panel ).data( 'field' ); |
1282 | 1424 | |
— | — | @@ -1287,7 +1429,7 @@ |
1288 | 1430 | BundleField.prototype.getDesc = function( useValuesAsDefaults ) { |
1289 | 1431 | var desc = this.desc; |
1290 | 1432 | desc.sections = []; |
1291 | | - this.$div.find( '.ui-tabs-nav a' ).each( function( idx, anchor ) { |
| 1433 | + this.$ui_tabs_nav.find( 'a' ).each( function( idx, anchor ) { |
1292 | 1434 | var panel = $( anchor ).data( 'panel' ), |
1293 | 1435 | field = $( panel ).data( 'field' ); |
1294 | 1436 | |
— | — | @@ -1298,7 +1440,7 @@ |
1299 | 1441 | |
1300 | 1442 | BundleField.prototype.getValues = function() { |
1301 | 1443 | var values = {}; |
1302 | | - this.$div.find( '.ui-tabs-nav a' ).each( function( idx, anchor ) { |
| 1444 | + this.$ui_tabs_nav.find( 'a' ).each( function( idx, anchor ) { |
1303 | 1445 | var panel = $( anchor ).data( 'panel' ), |
1304 | 1446 | field = $( panel ).data( 'field' ); |
1305 | 1447 | |
— | — | @@ -1326,7 +1468,6 @@ |
1327 | 1469 | } |
1328 | 1470 | |
1329 | 1471 | var $form = $( '<form/>' ).addClass( 'formbuilder' ); |
1330 | | - var prefix = options.gadget === undefined ? '' : ( 'Gadget-' + options.gadget + '-' ); |
1331 | 1472 | |
1332 | 1473 | if ( typeof description.fields != 'object' ) { |
1333 | 1474 | mw.log( "description.fields should be an object, instead of a " + typeof description.fields ); |
— | — | @@ -1334,7 +1475,8 @@ |
1335 | 1476 | } |
1336 | 1477 | |
1337 | 1478 | var section = new SectionField( description, { |
1338 | | - prefix: prefix, |
| 1479 | + idPrefix: options.idPrefix, |
| 1480 | + msgPrefix: options.msgPrefix, |
1339 | 1481 | values: options.values, |
1340 | 1482 | editable: options.editable === true |
1341 | 1483 | } ); |
Index: branches/salvatoreingala/Gadgets/ui/resources/ext.gadgets.preferences.js |
— | — | @@ -70,7 +70,8 @@ |
71 | 71 | var values = response.values; |
72 | 72 | |
73 | 73 | var dialogBody = $( prefsDescription ).formBuilder( { |
74 | | - gadget: gadget, |
| 74 | + msgPrefix: "Gadget-" + gadget + "-", |
| 75 | + idPrefix: "gadget-" + gadget + "-preferences-", |
75 | 76 | values: values |
76 | 77 | } ); |
77 | 78 | |