2 * Functions used in configuration forms and on user preferences pages
5 // default values for fields
6 var defaultValues
= {};
14 * @param {Element} field
16 function getFieldType(field
) {
18 var tagName
= field
.attr('tagName');
19 if (tagName
== 'INPUT') {
20 return field
.attr('type');
21 } else if (tagName
== 'SELECT') {
23 } else if (tagName
== 'TEXTAREA') {
32 * value must be of type:
33 * o undefined (omitted) - restore default value (form default, not PMA default)
34 * o String - if field_type is 'text'
35 * o boolean - if field_type is 'checkbox'
36 * o Array of values - if field_type is 'select'
38 * @param {Element} field
39 * @param {String} field_type see {@link #getFieldType}
40 * @param {String|Boolean} [value]
42 function setFieldValue(field
, field_type
, value
) {
46 field
.attr('value', (value
!= undefined ? value
: field
.attr('defaultValue')));
49 field
.attr('checked', (value
!= undefined ? value
: field
.attr('defaultChecked')));
52 var options
= field
.attr('options');
53 var i
, imax
= options
.length
;
54 if (value
== undefined) {
55 for (i
= 0; i
< imax
; i
++) {
56 options
[i
].selected
= options
[i
].defaultSelected
;
59 for (i
= 0; i
< imax
; i
++) {
60 options
[i
].selected
= (value
.indexOf(options
[i
].value
) != -1);
72 * o String - if type is 'text'
73 * o boolean - if type is 'checkbox'
74 * o Array of values - if type is 'select'
76 * @param {Element} field
77 * @param {String} field_type returned by {@link #getFieldType}
78 * @type Boolean|String|String[]
80 function getFieldValue(field
, field_type
) {
84 return field
.attr('value');
86 return field
.attr('checked');
88 var options
= field
.attr('options');
89 var i
, imax
= options
.length
, items
= [];
90 for (i
= 0; i
< imax
; i
++) {
91 if (options
[i
].selected
) {
92 items
.push(options
[i
].value
);
101 * Returns values for all fields in fieldsets
103 function getAllValues() {
104 var elements
= $('fieldset input, fieldset select, fieldset textarea');
107 for (var i
= 0; i
< elements
.length
; i
++) {
108 type
= getFieldType(elements
[i
]);
109 value
= getFieldValue(elements
[i
], type
);
110 if (typeof value
!= 'undefined') {
111 // we only have single selects, fatten array
112 if (type
== 'select') {
115 values
[elements
[i
].name
] = value
;
122 * Checks whether field has its default value
124 * @param {Element} field
125 * @param {String} type
128 function checkFieldDefault(field
, type
) {
130 var field_id
= field
.attr('id');
131 if (typeof defaultValues
[field_id
] == 'undefined') {
134 var isDefault
= true;
135 var currentValue
= getFieldValue(field
, type
);
136 if (type
!= 'select') {
137 isDefault
= currentValue
== defaultValues
[field_id
];
139 // compare arrays, will work for our representation of select values
140 if (currentValue
.length
!= defaultValues
[field_id
].length
) {
144 for (var i
= 0; i
< currentValue
.length
; i
++) {
145 if (currentValue
[i
] != defaultValues
[field_id
][i
]) {
156 * Returns element's id prefix
157 * @param {Element} element
159 function getIdPrefix(element
) {
160 return $(element
).attr('id').replace(/[^-]+$/, '');
163 // ------------------------------------------------------------------
164 // Form validation and field operations
167 // form validator assignments
170 // form validator list
172 // regexp: numeric value
173 _regexp_numeric
: /^[0-9]+$/,
174 // regexp: extract parts from PCRE expression
175 _regexp_pcre_extract
: /(.)(.*)\1(.*)?/,
177 * Validates positive number
179 * @param {boolean} isKeyUp
181 validate_positive_number: function (isKeyUp
) {
182 if (isKeyUp
&& this.value
== '') {
185 var result
= this.value
!= '0' && validators
._regexp_numeric
.test(this.value
);
186 return result
? true : PMA_messages
['error_nan_p'];
189 * Validates non-negative number
191 * @param {boolean} isKeyUp
193 validate_non_negative_number: function (isKeyUp
) {
194 if (isKeyUp
&& this.value
== '') {
197 var result
= validators
._regexp_numeric
.test(this.value
);
198 return result
? true : PMA_messages
['error_nan_nneg'];
201 * Validates port number
203 * @param {boolean} isKeyUp
205 validate_port_number: function(isKeyUp
) {
206 if (this.value
== '') {
209 var result
= validators
._regexp_numeric
.test(this.value
) && this.value
!= '0';
210 return result
&& this.value
<= 65535 ? true : PMA_messages
['error_incorrect_port'];
213 * Validates value according to given regular expression
215 * @param {boolean} isKeyUp
216 * @param {string} regexp
218 validate_by_regex: function(isKeyUp
, regexp
) {
219 if (isKeyUp
&& this.value
== '') {
222 // convert PCRE regexp
223 var parts
= regexp
.match(validators
._regexp_pcre_extract
);
224 var valid
= this.value
.match(new RegExp(parts
[2], parts
[3])) != null;
225 return valid
? true : PMA_messages
['error_invalid_value'];
228 * Validates upper bound for numeric inputs
230 * @param {boolean} isKeyUp
231 * @param {int} max_value
233 validate_upper_bound: function(isKeyUp
, max_value
) {
234 var val
= parseInt(this.value
);
238 return val
<= max_value
? true : PMA_messages
['error_value_lte'].replace('%s', max_value
);
243 // fieldset validators
249 * Registers validator for given field
251 * @param {String} id field id
252 * @param {String} type validator (key in validators object)
253 * @param {boolean} onKeyUp whether fire on key up
254 * @param {Array} params validation function parameters
256 function validateField(id
, type
, onKeyUp
, params
) {
257 if (typeof validators
[type
] == 'undefined') {
260 if (typeof validate
[id
] == 'undefined') {
263 validate
[id
].push([type
, params
, onKeyUp
]);
267 * Returns valdiation functions associated with form field
269 * @param {String} field_id form field id
270 * @param {boolean} onKeyUpOnly see validateField
272 * @return array of [function, paramseters to be passed to function]
274 function getFieldValidators(field_id
, onKeyUpOnly
) {
275 // look for field bound validator
276 var name
= field_id
.match(/[^-]+$/)[0];
277 if (typeof validators
._field
[name
] != 'undefined') {
278 return [[validators
._field
[name
], null]];
281 // look for registered validators
283 if (typeof validate
[field_id
] != 'undefined') {
284 // validate[field_id]: array of [type, params, onKeyUp]
285 for (var i
= 0, imax
= validate
[field_id
].length
; i
< imax
; i
++) {
286 if (onKeyUpOnly
&& !validate
[field_id
][i
][2]) {
289 functions
.push([validators
[validate
[field_id
][i
][0]], validate
[field_id
][i
][1]]);
297 * Displays errors for given form fields
299 * WARNING: created DOM elements must be identical with the ones made by
300 * display_input() in FormDisplay.tpl.php!
302 * @param {Object} error_list list of errors in the form {field id: error array}
304 function displayErrors(error_list
) {
305 for (var field_id
in error_list
) {
306 var errors
= error_list
[field_id
];
307 var field
= $('#'+field_id
);
308 var isFieldset
= field
.attr('tagName') == 'FIELDSET';
309 var errorCnt
= isFieldset
310 ? field
.find('dl.errors')
311 : field
.siblings('.inline_errors');
313 // remove empty errors (used to clear error list)
314 errors
= $.grep(errors
, function(item
) {
320 // checkboxes uses parent <span> for marking
321 var fieldMarker
= (field
.attr('type') == 'checkbox') ? field
.parent() : field
;
322 fieldMarker
[errors
.length
? 'addClass' : 'removeClass']('field-error');
326 // if error container doesn't exist, create it
327 if (errorCnt
.length
== 0) {
329 errorCnt
= $('<dl class="errors" />');
330 field
.find('table').before(errorCnt
);
332 errorCnt
= $('<dl class="inline_errors" />');
333 field
.closest('td').append(errorCnt
);
338 for (var i
= 0, imax
= errors
.length
; i
< imax
; i
++) {
339 html
+= '<dd>' + errors
[i
] + '</dd>';
342 } else if (errorCnt
!== null) {
343 // remove useless error container
350 * Validates fieldset and puts errors in 'errors' object
352 * @param {Element} fieldset
353 * @param {boolean} isKeyUp
354 * @param {Object} errors
356 function validate_fieldset(fieldset
, isKeyUp
, errors
) {
357 fieldset
= $(fieldset
);
358 if (fieldset
.length
&& typeof validators
._fieldset
[fieldset
.attr('id')] != 'undefined') {
359 var fieldset_errors
= validators
._fieldset
[fieldset
.attr('id')].apply(fieldset
[0], [isKeyUp
]);
360 for (var field_id
in fieldset_errors
) {
361 if (typeof errors
[field_id
] == 'undefined') {
362 errors
[field_id
] = [];
364 if (typeof fieldset_errors
[field_id
] == 'string') {
365 fieldset_errors
[field_id
] = [fieldset_errors
[field_id
]];
367 $.merge(errors
[field_id
], fieldset_errors
[field_id
]);
373 * Validates form field and puts errors in 'errors' object
375 * @param {Element} field
376 * @param {boolean} isKeyUp
377 * @param {Object} errors
379 function validate_field(field
, isKeyUp
, errors
) {
381 var field_id
= field
.attr('id');
382 errors
[field_id
] = [];
383 var functions
= getFieldValidators(field_id
, isKeyUp
);
384 for (var i
= 0; i
< functions
.length
; i
++) {
385 var args
= functions
[i
][1] != null
386 ? functions
[i
][1].slice(0)
388 args
.unshift(isKeyUp
);
389 var result
= functions
[i
][0].apply(field
[0], args
);
390 if (result
!== true) {
391 if (typeof result
== 'string') {
394 $.merge(errors
[field_id
], result
);
400 * Validates form field and parent fieldset
402 * @param {Element} field
403 * @param {boolean} isKeyUp
405 function validate_field_and_fieldset(field
, isKeyUp
) {
408 validate_field(field
, isKeyUp
, errors
);
409 validate_fieldset(field
.closest('fieldset'), isKeyUp
, errors
);
410 displayErrors(errors
);
414 * Marks field depending on its value (system default or custom)
416 * @param {Element} field
418 function markField(field
) {
420 var type
= getFieldType(field
);
421 var isDefault
= checkFieldDefault(field
, type
);
423 // checkboxes uses parent <span> for marking
424 var fieldMarker
= (type
== 'checkbox') ? field
.parent() : field
;
425 setRestoreDefaultBtn(field
, !isDefault
);
426 fieldMarker
[isDefault
? 'removeClass' : 'addClass']('custom');
430 * Enables or disables the "restore default value" button
432 * @param {Element} field
433 * @param {boolean} display
435 function setRestoreDefaultBtn(field
, display
) {
436 var el
= $(field
).closest('td').find('.restore-default img');
437 el
[display
? 'show' : 'hide']();
441 // register validators and mark custom values
442 var elements
= $('input[id], select[id], textarea[id]');
443 $('input[id], select[id], textarea[id]').each(function(){
446 el
.bind('change', function() {
447 validate_field_and_fieldset(this, false);
450 var tagName
= el
.attr('tagName');
451 // text fields can be validated after each change
452 if (tagName
== 'INPUT' && el
.attr('type') == 'text') {
453 el
.keyup(function() {
454 validate_field_and_fieldset(el
, true);
458 // disable textarea spellcheck
459 if (tagName
== 'TEXTAREA') {
460 el
.attr('spellcheck', false);
464 // check whether we've refreshed a page and browser remembered modified
466 var check_page_refresh
= $('#check_page_refresh');
467 if (check_page_refresh
.length
== 0 || check_page_refresh
.val() == '1') {
468 // run all field validators
470 for (var i
= 0; i
< elements
.length
; i
++) {
471 validate_field(elements
[i
], false, errors
);
473 // run all fieldset validators
474 $('fieldset').each(function(){
475 validate_fieldset(this, false, errors
);
478 displayErrors(errors
);
479 } else if (check_page_refresh
) {
480 check_page_refresh
.val('1');
485 // END: Form validation and field operations
486 // ------------------------------------------------------------------
488 // ------------------------------------------------------------------
495 * @param {String} tab_id
497 function setTab(tab_id
) {
498 $('.tabs a').removeClass('active').filter('[href=' + tab_id
+ ']').addClass('active');
499 $('.tabs_contents fieldset').hide().filter(tab_id
).show();
500 location
.hash
= 'tab_' + tab_id
.substr(1);
501 $('.config-form input[name=tab_hash]').val(location
.hash
);
505 var tabs
= $('.tabs');
509 // add tabs events and activate one tab (the first one or indicated by location hash)
513 setTab($(this).attr('href'));
517 $('.tabs_contents fieldset').hide().filter(':first').show();
519 // tab links handling, check each 200ms
520 // (works with history in FF, further browser support here would be an overkill)
522 var tab_check_fnc = function() {
523 if (location
.hash
!= prev_hash
) {
524 prev_hash
= location
.hash
;
525 if (location
.hash
.match(/^#tab_.+/) && $('#' + location
.hash
.substr(5)).length
) {
526 setTab('#' + location
.hash
.substr(5));
531 setInterval(tab_check_fnc
, 200);
536 // ------------------------------------------------------------------
538 // ------------------------------------------------------------------
539 // Form reset buttons
543 $('input[type=button][name=submit_reset]').click(function() {
544 var fields
= $(this).closest('fieldset').find('input, select, textarea');
545 for (var i
= 0, imax
= fields
.length
; i
< imax
; i
++) {
546 setFieldValue(fields
[i
], getFieldType(fields
[i
]));
552 // END: Form reset buttons
553 // ------------------------------------------------------------------
555 // ------------------------------------------------------------------
556 // "Restore default" and "set value" buttons
560 * Restores field's default value
562 * @param {String} field_id
564 function restoreField(field_id
) {
565 var field
= $('#'+field_id
);
566 if (field
.length
== 0 || defaultValues
[field_id
] == undefined) {
569 setFieldValue(field
, getFieldType(field
), defaultValues
[field_id
]);
574 .delegate('.restore-default, .set-value', 'mouseenter', function(){$(this).css('opacity', 1)})
575 .delegate('.restore-default, .set-value', 'mouseleave', function(){$(this).css('opacity', 0.25)})
576 .delegate('.restore-default, .set-value', 'click', function(e
) {
578 var href
= $(this).attr('href');
580 if ($(this).hasClass('restore-default')) {
582 restoreField(field_sel
.substr(1));
584 field_sel
= href
.match(/^[^=]+/)[0];
585 var value
= href
.match(/=(.+)$/)[1];
586 setFieldValue($(field_sel
), 'text', value
);
588 $(field_sel
).trigger('change');
590 .find('.restore-default, .set-value')
591 // inline-block for IE so opacity inheritance works
592 .css({display
: 'inline-block', opacity
: 0.25});
596 // END: "Restore default" and "set value" buttons
597 // ------------------------------------------------------------------
599 // ------------------------------------------------------------------
600 // User preferences import/export
604 offerPrefsAutoimport();
605 var radios
= $('#import_local_storage, #export_local_storage');
606 if (!radios
.length
) {
610 // enable JavaScript dependent fields
612 .attr('disabled', false)
613 .add('#export_text_file, #import_text_file')
615 var enable_id
= $(this).attr('id');
616 var disable_id
= enable_id
.match(/local_storage$/)
617 ? enable_id
.replace(/local_storage$/, 'text_file')
618 : enable_id
.replace(/text_file$/, 'local_storage');
619 $('#opts_'+disable_id
).addClass('disabled').find('input').attr('disabled', true);
620 $('#opts_'+enable_id
).removeClass('disabled').find('input').attr('disabled', false);
623 // detect localStorage state
624 var ls_supported
= window
.localStorage
|| false;
625 var ls_exists
= ls_supported
? (window
.localStorage
['config'] || false) : false;
626 $('.localStorage-'+(ls_supported
? 'un' : '')+'supported').hide();
627 $('.localStorage-'+(ls_exists
? 'empty' : 'exists')).hide();
631 $('form.prefs-form').change(function(){
633 var disabled
= false;
635 disabled
= form
.find('input[type=radio][value$=local_storage]').attr('checked');
636 } else if (!ls_exists
&& form
.attr('name') == 'prefs_import'
637 && $('#import_local_storage')[0].checked
) {
640 form
.find('input[type=submit]').attr('disabled', disabled
);
641 }).submit(function(e
) {
643 if (form
.attr('name') == 'prefs_export' && $('#export_local_storage')[0].checked
) {
645 // use AJAX to read JSON settings and save them
646 savePrefsToLocalStorage(form
);
647 } else if (form
.attr('name') == 'prefs_import' && $('#import_local_storage')[0].checked
) {
648 // set 'json' input and submit form
649 form
.find('input[name=json]').val(window
.localStorage
['config']);
653 $('.click-hide-message').live('click', function(){
655 div
.hide().parent('.group').css('height', '');
656 div
.next('form').show();
661 * Saves user preferences to localStorage
663 * @param {Element} form
665 function savePrefsToLocalStorage(form
)
668 var submit
= form
.find('input[type=submit]');
669 submit
.attr('disabled', true);
671 url
: 'prefs_manage.php',
675 token
: form
.find('input[name=token]').val(),
676 submit_get_json
: true
678 success: function(response
) {
679 window
.localStorage
['config'] = response
.prefs
;
680 window
.localStorage
['config_mtime'] = response
.mtime
;
681 window
.localStorage
['config_mtime_local'] = (new Date()).toUTCString();
683 $('.localStorage-empty').hide();
684 $('.localStorage-exists').show();
685 var group
= form
.parent('.group');
686 group
.css('height', group
.height() + 'px');
688 form
.prev('.click-hide-message').show('fast');
690 complete: function() {
691 submit
.attr('disabled', false);
697 * Updates preferences timestamp in Import form
699 function updatePrefsDate()
701 var d
= new Date(window
.localStorage
['config_mtime_local']);
702 var msg
= PMA_messages
['strSavedOn'].replace('@DATE@', formatDate(d
));
703 $('#opts_import_local_storage .localStorage-exists').html(msg
);
707 * Returns date formatted as YYYY-MM-DD HH:II
711 function formatDate(d
)
713 return d
.getFullYear() + '-'
714 + (d
.getMonth() < 10 ? '0'+d
.getMonth() : d
.getMonth())
715 + '-' + (d
.getDate() < 10 ? '0'+d
.getDate() : d
.getDate())
716 + ' ' + (d
.getHours() < 10 ? '0'+d
.getHours() : d
.getHours())
717 + ':' + (d
.getMinutes() < 10 ? '0'+d
.getMinutes() : d
.getMinutes());
721 * Prepares message which informs that localStorage preferences are available and can be imported
723 function offerPrefsAutoimport()
725 var has_config
= (window
.localStorage
|| false) && (window
.localStorage
['config'] || false);
726 var cnt
= $('#prefs_autoload');
727 if (!cnt
.length
|| !has_config
) {
730 cnt
.find('a').click(function(e
) {
733 if (a
.attr('href') == '#no') {
736 token
: cnt
.find('input[name=token]').val(),
737 prefs_autoload
: 'hide'});
740 cnt
.find('input[name=json]').val(window
.localStorage
['config']);
741 cnt
.find('form').submit();
747 // END: User preferences import/export
748 // ------------------------------------------------------------------