1 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 * Functions used in configuration forms and on user preferences pages
6 // default values for fields
7 var defaultValues
= {};
10 var PMA_messages
= {};
15 * @param {Element} field
17 function getFieldType(field
) {
19 var tagName
= field
.prop('tagName');
20 if (tagName
== 'INPUT') {
21 return field
.attr('type');
22 } else if (tagName
== 'SELECT') {
24 } else if (tagName
== 'TEXTAREA') {
33 * value must be of type:
34 * o undefined (omitted) - restore default value (form default, not PMA default)
35 * o String - if field_type is 'text'
36 * o boolean - if field_type is 'checkbox'
37 * o Array of values - if field_type is 'select'
39 * @param {Element} field
40 * @param {String} field_type see {@link #getFieldType}
41 * @param {String|Boolean} [value]
43 function setFieldValue(field
, field_type
, value
) {
47 field
.attr('value', (value
!= undefined ? value
: field
.attr('defaultValue')));
50 field
.attr('checked', (value
!= undefined ? value
: field
.attr('defaultChecked')));
53 var options
= field
.prop('options');
54 var i
, imax
= options
.length
;
55 if (value
== undefined) {
56 for (i
= 0; i
< imax
; i
++) {
57 options
[i
].selected
= options
[i
].defaultSelected
;
60 for (i
= 0; i
< imax
; i
++) {
61 options
[i
].selected
= (value
.indexOf(options
[i
].value
) != -1);
73 * o String - if type is 'text'
74 * o boolean - if type is 'checkbox'
75 * o Array of values - if type is 'select'
77 * @param {Element} field
78 * @param {String} field_type returned by {@link #getFieldType}
79 * @type Boolean|String|String[]
81 function getFieldValue(field
, field_type
) {
85 return field
.prop('value');
87 return field
.prop('checked');
89 var options
= field
.prop('options');
90 var i
, imax
= options
.length
, items
= [];
91 for (i
= 0; i
< imax
; i
++) {
92 if (options
[i
].selected
) {
93 items
.push(options
[i
].value
);
102 * Returns values for all fields in fieldsets
104 function getAllValues() {
105 var elements
= $('fieldset input, fieldset select, fieldset textarea');
108 for (var i
= 0; i
< elements
.length
; i
++) {
109 type
= getFieldType(elements
[i
]);
110 value
= getFieldValue(elements
[i
], type
);
111 if (typeof value
!= 'undefined') {
112 // we only have single selects, fatten array
113 if (type
== 'select') {
116 values
[elements
[i
].name
] = value
;
123 * Checks whether field has its default value
125 * @param {Element} field
126 * @param {String} type
129 function checkFieldDefault(field
, type
) {
131 var field_id
= field
.attr('id');
132 if (typeof defaultValues
[field_id
] == 'undefined') {
135 var isDefault
= true;
136 var currentValue
= getFieldValue(field
, type
);
137 if (type
!= 'select') {
138 isDefault
= currentValue
== defaultValues
[field_id
];
140 // compare arrays, will work for our representation of select values
141 if (currentValue
.length
!= defaultValues
[field_id
].length
) {
145 for (var i
= 0; i
< currentValue
.length
; i
++) {
146 if (currentValue
[i
] != defaultValues
[field_id
][i
]) {
157 * Returns element's id prefix
158 * @param {Element} element
160 function getIdPrefix(element
) {
161 return $(element
).attr('id').replace(/[^-]+$/, '');
164 // ------------------------------------------------------------------
165 // Form validation and field operations
168 // form validator assignments
171 // form validator list
173 // regexp: numeric value
174 _regexp_numeric
: /^[0-9]+$/,
175 // regexp: extract parts from PCRE expression
176 _regexp_pcre_extract
: /(.)(.*)\1(.*)?/,
178 * Validates positive number
180 * @param {boolean} isKeyUp
182 validate_positive_number: function (isKeyUp
) {
183 if (isKeyUp
&& this.value
== '') {
186 var result
= this.value
!= '0' && validators
._regexp_numeric
.test(this.value
);
187 return result
? true : PMA_messages
['error_nan_p'];
190 * Validates non-negative number
192 * @param {boolean} isKeyUp
194 validate_non_negative_number: function (isKeyUp
) {
195 if (isKeyUp
&& this.value
== '') {
198 var result
= validators
._regexp_numeric
.test(this.value
);
199 return result
? true : PMA_messages
['error_nan_nneg'];
202 * Validates port number
204 * @param {boolean} isKeyUp
206 validate_port_number: function(isKeyUp
) {
207 if (this.value
== '') {
210 var result
= validators
._regexp_numeric
.test(this.value
) && this.value
!= '0';
211 return result
&& this.value
<= 65535 ? true : PMA_messages
['error_incorrect_port'];
214 * Validates value according to given regular expression
216 * @param {boolean} isKeyUp
217 * @param {string} regexp
219 validate_by_regex: function(isKeyUp
, regexp
) {
220 if (isKeyUp
&& this.value
== '') {
223 // convert PCRE regexp
224 var parts
= regexp
.match(validators
._regexp_pcre_extract
);
225 var valid
= this.value
.match(new RegExp(parts
[2], parts
[3])) != null;
226 return valid
? true : PMA_messages
['error_invalid_value'];
229 * Validates upper bound for numeric inputs
231 * @param {boolean} isKeyUp
232 * @param {int} max_value
234 validate_upper_bound: function(isKeyUp
, max_value
) {
235 var val
= parseInt(this.value
);
239 return val
<= max_value
? true : PMA_messages
['error_value_lte'].replace('%s', max_value
);
244 // fieldset validators
250 * Registers validator for given field
252 * @param {String} id field id
253 * @param {String} type validator (key in validators object)
254 * @param {boolean} onKeyUp whether fire on key up
255 * @param {Array} params validation function parameters
257 function validateField(id
, type
, onKeyUp
, params
) {
258 if (typeof validators
[type
] == 'undefined') {
261 if (typeof validate
[id
] == 'undefined') {
264 validate
[id
].push([type
, params
, onKeyUp
]);
268 * Returns valdiation functions associated with form field
270 * @param {String} field_id form field id
271 * @param {boolean} onKeyUpOnly see validateField
273 * @return array of [function, paramseters to be passed to function]
275 function getFieldValidators(field_id
, onKeyUpOnly
) {
276 // look for field bound validator
277 var name
= field_id
.match(/[^-]+$/)[0];
278 if (typeof validators
._field
[name
] != 'undefined') {
279 return [[validators
._field
[name
], null]];
282 // look for registered validators
284 if (typeof validate
[field_id
] != 'undefined') {
285 // validate[field_id]: array of [type, params, onKeyUp]
286 for (var i
= 0, imax
= validate
[field_id
].length
; i
< imax
; i
++) {
287 if (onKeyUpOnly
&& !validate
[field_id
][i
][2]) {
290 functions
.push([validators
[validate
[field_id
][i
][0]], validate
[field_id
][i
][1]]);
298 * Displays errors for given form fields
300 * WARNING: created DOM elements must be identical with the ones made by
301 * display_input() in FormDisplay.tpl.php!
303 * @param {Object} error_list list of errors in the form {field id: error array}
305 function displayErrors(error_list
) {
306 for (var field_id
in error_list
) {
307 var errors
= error_list
[field_id
];
308 var field
= $('#'+field_id
);
309 var isFieldset
= field
.attr('tagName') == 'FIELDSET';
310 var errorCnt
= isFieldset
311 ? field
.find('dl.errors')
312 : field
.siblings('.inline_errors');
314 // remove empty errors (used to clear error list)
315 errors
= $.grep(errors
, function(item
) {
321 // checkboxes uses parent <span> for marking
322 var fieldMarker
= (field
.attr('type') == 'checkbox') ? field
.parent() : field
;
323 fieldMarker
[errors
.length
? 'addClass' : 'removeClass']('field-error');
327 // if error container doesn't exist, create it
328 if (errorCnt
.length
== 0) {
330 errorCnt
= $('<dl class="errors" />');
331 field
.find('table').before(errorCnt
);
333 errorCnt
= $('<dl class="inline_errors" />');
334 field
.closest('td').append(errorCnt
);
339 for (var i
= 0, imax
= errors
.length
; i
< imax
; i
++) {
340 html
+= '<dd>' + errors
[i
] + '</dd>';
343 } else if (errorCnt
!== null) {
344 // remove useless error container
351 * Validates fieldset and puts errors in 'errors' object
353 * @param {Element} fieldset
354 * @param {boolean} isKeyUp
355 * @param {Object} errors
357 function validate_fieldset(fieldset
, isKeyUp
, errors
) {
358 fieldset
= $(fieldset
);
359 if (fieldset
.length
&& typeof validators
._fieldset
[fieldset
.attr('id')] != 'undefined') {
360 var fieldset_errors
= validators
._fieldset
[fieldset
.attr('id')].apply(fieldset
[0], [isKeyUp
]);
361 for (var field_id
in fieldset_errors
) {
362 if (typeof errors
[field_id
] == 'undefined') {
363 errors
[field_id
] = [];
365 if (typeof fieldset_errors
[field_id
] == 'string') {
366 fieldset_errors
[field_id
] = [fieldset_errors
[field_id
]];
368 $.merge(errors
[field_id
], fieldset_errors
[field_id
]);
374 * Validates form field and puts errors in 'errors' object
376 * @param {Element} field
377 * @param {boolean} isKeyUp
378 * @param {Object} errors
380 function validate_field(field
, isKeyUp
, errors
) {
382 var field_id
= field
.attr('id');
383 errors
[field_id
] = [];
384 var functions
= getFieldValidators(field_id
, isKeyUp
);
385 for (var i
= 0; i
< functions
.length
; i
++) {
386 var args
= functions
[i
][1] != null
387 ? functions
[i
][1].slice(0)
389 args
.unshift(isKeyUp
);
390 var result
= functions
[i
][0].apply(field
[0], args
);
391 if (result
!== true) {
392 if (typeof result
== 'string') {
395 $.merge(errors
[field_id
], result
);
401 * Validates form field and parent fieldset
403 * @param {Element} field
404 * @param {boolean} isKeyUp
406 function validate_field_and_fieldset(field
, isKeyUp
) {
409 validate_field(field
, isKeyUp
, errors
);
410 validate_fieldset(field
.closest('fieldset'), isKeyUp
, errors
);
411 displayErrors(errors
);
415 * Marks field depending on its value (system default or custom)
417 * @param {Element} field
419 function markField(field
) {
421 var type
= getFieldType(field
);
422 var isDefault
= checkFieldDefault(field
, type
);
424 // checkboxes uses parent <span> for marking
425 var fieldMarker
= (type
== 'checkbox') ? field
.parent() : field
;
426 setRestoreDefaultBtn(field
, !isDefault
);
427 fieldMarker
[isDefault
? 'removeClass' : 'addClass']('custom');
431 * Enables or disables the "restore default value" button
433 * @param {Element} field
434 * @param {boolean} display
436 function setRestoreDefaultBtn(field
, display
) {
437 var el
= $(field
).closest('td').find('.restore-default img');
438 el
[display
? 'show' : 'hide']();
442 // register validators and mark custom values
443 var elements
= $('input[id], select[id], textarea[id]');
444 $('input[id], select[id], textarea[id]').each(function(){
447 el
.bind('change', function() {
448 validate_field_and_fieldset(this, false);
451 var tagName
= el
.attr('tagName');
452 // text fields can be validated after each change
453 if (tagName
== 'INPUT' && el
.attr('type') == 'text') {
454 el
.keyup(function() {
455 validate_field_and_fieldset(el
, true);
459 // disable textarea spellcheck
460 if (tagName
== 'TEXTAREA') {
461 el
.attr('spellcheck', false);
465 // check whether we've refreshed a page and browser remembered modified
467 var check_page_refresh
= $('#check_page_refresh');
468 if (check_page_refresh
.length
== 0 || check_page_refresh
.val() == '1') {
469 // run all field validators
471 for (var i
= 0; i
< elements
.length
; i
++) {
472 validate_field(elements
[i
], false, errors
);
474 // run all fieldset validators
475 $('fieldset').each(function(){
476 validate_fieldset(this, false, errors
);
479 displayErrors(errors
);
480 } else if (check_page_refresh
) {
481 check_page_refresh
.val('1');
486 // END: Form validation and field operations
487 // ------------------------------------------------------------------
489 // ------------------------------------------------------------------
496 * @param {String} tab_id
498 function setTab(tab_id
) {
499 $('.tabs a').removeClass('active').filter('[href=' + tab_id
+ ']').addClass('active');
500 $('.tabs_contents fieldset').hide().filter(tab_id
).show();
501 location
.hash
= 'tab_' + tab_id
.substr(1);
502 $('.config-form input[name=tab_hash]').val(location
.hash
);
506 var tabs
= $('.tabs');
510 // add tabs events and activate one tab (the first one or indicated by location hash)
514 setTab($(this).attr('href'));
518 $('.tabs_contents fieldset').hide().filter(':first').show();
520 // tab links handling, check each 200ms
521 // (works with history in FF, further browser support here would be an overkill)
523 var tab_check_fnc = function() {
524 if (location
.hash
!= prev_hash
) {
525 prev_hash
= location
.hash
;
526 if (location
.hash
.match(/^#tab_.+/) && $('#' + location
.hash
.substr(5)).length
) {
527 setTab('#' + location
.hash
.substr(5));
532 setInterval(tab_check_fnc
, 200);
537 // ------------------------------------------------------------------
539 // ------------------------------------------------------------------
540 // Form reset buttons
544 $('input[type=button][name=submit_reset]').click(function() {
545 var fields
= $(this).closest('fieldset').find('input, select, textarea');
546 for (var i
= 0, imax
= fields
.length
; i
< imax
; i
++) {
547 setFieldValue(fields
[i
], getFieldType(fields
[i
]));
553 // END: Form reset buttons
554 // ------------------------------------------------------------------
556 // ------------------------------------------------------------------
557 // "Restore default" and "set value" buttons
561 * Restores field's default value
563 * @param {String} field_id
565 function restoreField(field_id
) {
566 var field
= $('#'+field_id
);
567 if (field
.length
== 0 || defaultValues
[field_id
] == undefined) {
570 setFieldValue(field
, getFieldType(field
), defaultValues
[field_id
]);
575 .delegate('.restore-default, .set-value', 'mouseenter', function(){$(this).css('opacity', 1)})
576 .delegate('.restore-default, .set-value', 'mouseleave', function(){$(this).css('opacity', 0.25)})
577 .delegate('.restore-default, .set-value', 'click', function(e
) {
579 var href
= $(this).attr('href');
581 if ($(this).hasClass('restore-default')) {
583 restoreField(field_sel
.substr(1));
585 field_sel
= href
.match(/^[^=]+/)[0];
586 var value
= href
.match(/=(.+)$/)[1];
587 setFieldValue($(field_sel
), 'text', value
);
589 $(field_sel
).trigger('change');
591 .find('.restore-default, .set-value')
592 // inline-block for IE so opacity inheritance works
593 .css({display
: 'inline-block', opacity
: 0.25});
597 // END: "Restore default" and "set value" buttons
598 // ------------------------------------------------------------------
600 // ------------------------------------------------------------------
601 // User preferences import/export
605 offerPrefsAutoimport();
606 var radios
= $('#import_local_storage, #export_local_storage');
607 if (!radios
.length
) {
611 // enable JavaScript dependent fields
613 .attr('disabled', false)
614 .add('#export_text_file, #import_text_file')
616 var enable_id
= $(this).attr('id');
617 var disable_id
= enable_id
.match(/local_storage$/)
618 ? enable_id
.replace(/local_storage$/, 'text_file')
619 : enable_id
.replace(/text_file$/, 'local_storage');
620 $('#opts_'+disable_id
).addClass('disabled').find('input').attr('disabled', true);
621 $('#opts_'+enable_id
).removeClass('disabled').find('input').attr('disabled', false);
624 // detect localStorage state
625 var ls_supported
= window
.localStorage
|| false;
626 var ls_exists
= ls_supported
? (window
.localStorage
['config'] || false) : false;
627 $('.localStorage-'+(ls_supported
? 'un' : '')+'supported').hide();
628 $('.localStorage-'+(ls_exists
? 'empty' : 'exists')).hide();
632 $('form.prefs-form').change(function(){
634 var disabled
= false;
636 disabled
= form
.find('input[type=radio][value$=local_storage]').attr('checked');
637 } else if (!ls_exists
&& form
.attr('name') == 'prefs_import'
638 && $('#import_local_storage')[0].checked
) {
641 form
.find('input[type=submit]').attr('disabled', disabled
);
642 }).submit(function(e
) {
644 if (form
.attr('name') == 'prefs_export' && $('#export_local_storage')[0].checked
) {
646 // use AJAX to read JSON settings and save them
647 savePrefsToLocalStorage(form
);
648 } else if (form
.attr('name') == 'prefs_import' && $('#import_local_storage')[0].checked
) {
649 // set 'json' input and submit form
650 form
.find('input[name=json]').val(window
.localStorage
['config']);
654 $('.click-hide-message').live('click', function(){
656 div
.hide().parent('.group').css('height', '');
657 div
.next('form').show();
662 * Saves user preferences to localStorage
664 * @param {Element} form
666 function savePrefsToLocalStorage(form
)
669 var submit
= form
.find('input[type=submit]');
670 submit
.attr('disabled', true);
672 url
: 'prefs_manage.php',
676 token
: form
.find('input[name=token]').val(),
677 submit_get_json
: true
679 success: function(response
) {
680 window
.localStorage
['config'] = response
.prefs
;
681 window
.localStorage
['config_mtime'] = response
.mtime
;
682 window
.localStorage
['config_mtime_local'] = (new Date()).toUTCString();
684 $('.localStorage-empty').hide();
685 $('.localStorage-exists').show();
686 var group
= form
.parent('.group');
687 group
.css('height', group
.height() + 'px');
689 form
.prev('.click-hide-message').show('fast');
691 complete: function() {
692 submit
.attr('disabled', false);
698 * Updates preferences timestamp in Import form
700 function updatePrefsDate()
702 var d
= new Date(window
.localStorage
['config_mtime_local']);
703 var msg
= PMA_messages
['strSavedOn'].replace('@DATE@', formatDate(d
));
704 $('#opts_import_local_storage .localStorage-exists').html(msg
);
708 * Returns date formatted as YYYY-MM-DD HH:II
712 function formatDate(d
)
714 return d
.getFullYear() + '-'
715 + (d
.getMonth() < 10 ? '0'+d
.getMonth() : d
.getMonth())
716 + '-' + (d
.getDate() < 10 ? '0'+d
.getDate() : d
.getDate())
717 + ' ' + (d
.getHours() < 10 ? '0'+d
.getHours() : d
.getHours())
718 + ':' + (d
.getMinutes() < 10 ? '0'+d
.getMinutes() : d
.getMinutes());
722 * Prepares message which informs that localStorage preferences are available and can be imported
724 function offerPrefsAutoimport()
726 var has_config
= (window
.localStorage
|| false) && (window
.localStorage
['config'] || false);
727 var cnt
= $('#prefs_autoload');
728 if (!cnt
.length
|| !has_config
) {
731 cnt
.find('a').click(function(e
) {
734 if (a
.attr('href') == '#no') {
737 token
: cnt
.find('input[name=token]').val(),
738 prefs_autoload
: 'hide'});
741 cnt
.find('input[name=json]').val(window
.localStorage
['config']);
742 cnt
.find('form').submit();
748 // END: User preferences import/export
749 // ------------------------------------------------------------------