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
)
20 var tagName
= field
.prop('tagName');
21 if (tagName
== 'INPUT') {
22 return field
.attr('type');
23 } else if (tagName
== 'SELECT') {
25 } else if (tagName
== 'TEXTAREA') {
34 * value must be of type:
35 * o undefined (omitted) - restore default value (form default, not PMA default)
36 * o String - if field_type is 'text'
37 * o boolean - if field_type is 'checkbox'
38 * o Array of values - if field_type is 'select'
40 * @param {Element} field
41 * @param {String} field_type see {@link #getFieldType}
42 * @param {String|Boolean} [value]
44 function setFieldValue(field
, field_type
, value
)
49 field
.attr('value', (value
!= undefined ? value
: field
.attr('defaultValue')));
52 field
.attr('checked', (value
!= undefined ? value
: field
.attr('defaultChecked')));
55 var options
= field
.prop('options');
56 var i
, imax
= options
.length
;
57 if (value
== undefined) {
58 for (i
= 0; i
< imax
; i
++) {
59 options
[i
].selected
= options
[i
].defaultSelected
;
62 for (i
= 0; i
< imax
; i
++) {
63 options
[i
].selected
= (value
.indexOf(options
[i
].value
) != -1);
75 * o String - if type is 'text'
76 * o boolean - if type is 'checkbox'
77 * o Array of values - if type is 'select'
79 * @param {Element} field
80 * @param {String} field_type returned by {@link #getFieldType}
81 * @type Boolean|String|String[]
83 function getFieldValue(field
, field_type
)
88 return field
.prop('value');
90 return field
.prop('checked');
92 var options
= field
.prop('options');
93 var i
, imax
= options
.length
, items
= [];
94 for (i
= 0; i
< imax
; i
++) {
95 if (options
[i
].selected
) {
96 items
.push(options
[i
].value
);
105 * Returns values for all fields in fieldsets
107 function getAllValues()
109 var elements
= $('fieldset input, fieldset select, fieldset textarea');
112 for (var i
= 0; i
< elements
.length
; i
++) {
113 type
= getFieldType(elements
[i
]);
114 value
= getFieldValue(elements
[i
], type
);
115 if (typeof value
!= 'undefined') {
116 // we only have single selects, fatten array
117 if (type
== 'select') {
120 values
[elements
[i
].name
] = value
;
127 * Checks whether field has its default value
129 * @param {Element} field
130 * @param {String} type
133 function checkFieldDefault(field
, type
)
136 var field_id
= field
.attr('id');
137 if (typeof defaultValues
[field_id
] == 'undefined') {
140 var isDefault
= true;
141 var currentValue
= getFieldValue(field
, type
);
142 if (type
!= 'select') {
143 isDefault
= currentValue
== defaultValues
[field_id
];
145 // compare arrays, will work for our representation of select values
146 if (currentValue
.length
!= defaultValues
[field_id
].length
) {
150 for (var i
= 0; i
< currentValue
.length
; i
++) {
151 if (currentValue
[i
] != defaultValues
[field_id
][i
]) {
162 * Returns element's id prefix
163 * @param {Element} element
165 function getIdPrefix(element
)
167 return $(element
).attr('id').replace(/[^-]+$/, '');
170 // ------------------------------------------------------------------
171 // Form validation and field operations
174 // form validator assignments
177 // form validator list
179 // regexp: numeric value
180 _regexp_numeric
: /^[0-9]+$/,
181 // regexp: extract parts from PCRE expression
182 _regexp_pcre_extract
: /(.)(.*)\1(.*)?/,
184 * Validates positive number
186 * @param {boolean} isKeyUp
188 validate_positive_number: function (isKeyUp
) {
189 if (isKeyUp
&& this.value
== '') {
192 var result
= this.value
!= '0' && validators
._regexp_numeric
.test(this.value
);
193 return result
? true : PMA_messages
['error_nan_p'];
196 * Validates non-negative number
198 * @param {boolean} isKeyUp
200 validate_non_negative_number: function (isKeyUp
) {
201 if (isKeyUp
&& this.value
== '') {
204 var result
= validators
._regexp_numeric
.test(this.value
);
205 return result
? true : PMA_messages
['error_nan_nneg'];
208 * Validates port number
210 * @param {boolean} isKeyUp
212 validate_port_number: function(isKeyUp
) {
213 if (this.value
== '') {
216 var result
= validators
._regexp_numeric
.test(this.value
) && this.value
!= '0';
217 return result
&& this.value
<= 65535 ? true : PMA_messages
['error_incorrect_port'];
220 * Validates value according to given regular expression
222 * @param {boolean} isKeyUp
223 * @param {string} regexp
225 validate_by_regex: function(isKeyUp
, regexp
) {
226 if (isKeyUp
&& this.value
== '') {
229 // convert PCRE regexp
230 var parts
= regexp
.match(validators
._regexp_pcre_extract
);
231 var valid
= this.value
.match(new RegExp(parts
[2], parts
[3])) != null;
232 return valid
? true : PMA_messages
['error_invalid_value'];
235 * Validates upper bound for numeric inputs
237 * @param {boolean} isKeyUp
238 * @param {int} max_value
240 validate_upper_bound: function(isKeyUp
, max_value
) {
241 var val
= parseInt(this.value
);
245 return val
<= max_value
? true : PMA_messages
['error_value_lte'].replace('%s', max_value
);
250 // fieldset validators
256 * Registers validator for given field
258 * @param {String} id field id
259 * @param {String} type validator (key in validators object)
260 * @param {boolean} onKeyUp whether fire on key up
261 * @param {Array} params validation function parameters
263 function validateField(id
, type
, onKeyUp
, params
)
265 if (typeof validators
[type
] == 'undefined') {
268 if (typeof validate
[id
] == 'undefined') {
271 validate
[id
].push([type
, params
, onKeyUp
]);
275 * Returns valdiation functions associated with form field
277 * @param {String} field_id form field id
278 * @param {boolean} onKeyUpOnly see validateField
280 * @return array of [function, paramseters to be passed to function]
282 function getFieldValidators(field_id
, onKeyUpOnly
)
284 // look for field bound validator
285 var name
= field_id
.match(/[^-]+$/)[0];
286 if (typeof validators
._field
[name
] != 'undefined') {
287 return [[validators
._field
[name
], null]];
290 // look for registered validators
292 if (typeof validate
[field_id
] != 'undefined') {
293 // validate[field_id]: array of [type, params, onKeyUp]
294 for (var i
= 0, imax
= validate
[field_id
].length
; i
< imax
; i
++) {
295 if (onKeyUpOnly
&& !validate
[field_id
][i
][2]) {
298 functions
.push([validators
[validate
[field_id
][i
][0]], validate
[field_id
][i
][1]]);
306 * Displays errors for given form fields
308 * WARNING: created DOM elements must be identical with the ones made by
309 * display_input() in FormDisplay.tpl.php!
311 * @param {Object} error_list list of errors in the form {field id: error array}
313 function displayErrors(error_list
)
315 for (var field_id
in error_list
) {
316 var errors
= error_list
[field_id
];
317 var field
= $('#'+field_id
);
318 var isFieldset
= field
.attr('tagName') == 'FIELDSET';
319 var errorCnt
= isFieldset
320 ? field
.find('dl.errors')
321 : field
.siblings('.inline_errors');
323 // remove empty errors (used to clear error list)
324 errors
= $.grep(errors
, function(item
) {
330 // checkboxes uses parent <span> for marking
331 var fieldMarker
= (field
.attr('type') == 'checkbox') ? field
.parent() : field
;
332 fieldMarker
[errors
.length
? 'addClass' : 'removeClass']('field-error');
336 // if error container doesn't exist, create it
337 if (errorCnt
.length
== 0) {
339 errorCnt
= $('<dl class="errors" />');
340 field
.find('table').before(errorCnt
);
342 errorCnt
= $('<dl class="inline_errors" />');
343 field
.closest('td').append(errorCnt
);
348 for (var i
= 0, imax
= errors
.length
; i
< imax
; i
++) {
349 html
+= '<dd>' + errors
[i
] + '</dd>';
352 } else if (errorCnt
!== null) {
353 // remove useless error container
360 * Validates fieldset and puts errors in 'errors' object
362 * @param {Element} fieldset
363 * @param {boolean} isKeyUp
364 * @param {Object} errors
366 function validate_fieldset(fieldset
, isKeyUp
, errors
)
368 fieldset
= $(fieldset
);
369 if (fieldset
.length
&& typeof validators
._fieldset
[fieldset
.attr('id')] != 'undefined') {
370 var fieldset_errors
= validators
._fieldset
[fieldset
.attr('id')].apply(fieldset
[0], [isKeyUp
]);
371 for (var field_id
in fieldset_errors
) {
372 if (typeof errors
[field_id
] == 'undefined') {
373 errors
[field_id
] = [];
375 if (typeof fieldset_errors
[field_id
] == 'string') {
376 fieldset_errors
[field_id
] = [fieldset_errors
[field_id
]];
378 $.merge(errors
[field_id
], fieldset_errors
[field_id
]);
384 * Validates form field and puts errors in 'errors' object
386 * @param {Element} field
387 * @param {boolean} isKeyUp
388 * @param {Object} errors
390 function validate_field(field
, isKeyUp
, errors
)
393 var field_id
= field
.attr('id');
394 errors
[field_id
] = [];
395 var functions
= getFieldValidators(field_id
, isKeyUp
);
396 for (var i
= 0; i
< functions
.length
; i
++) {
397 var args
= functions
[i
][1] != null
398 ? functions
[i
][1].slice(0)
400 args
.unshift(isKeyUp
);
401 var result
= functions
[i
][0].apply(field
[0], args
);
402 if (result
!== true) {
403 if (typeof result
== 'string') {
406 $.merge(errors
[field_id
], result
);
412 * Validates form field and parent fieldset
414 * @param {Element} field
415 * @param {boolean} isKeyUp
417 function validate_field_and_fieldset(field
, isKeyUp
)
421 validate_field(field
, isKeyUp
, errors
);
422 validate_fieldset(field
.closest('fieldset'), isKeyUp
, errors
);
423 displayErrors(errors
);
427 * Marks field depending on its value (system default or custom)
429 * @param {Element} field
431 function markField(field
)
434 var type
= getFieldType(field
);
435 var isDefault
= checkFieldDefault(field
, type
);
437 // checkboxes uses parent <span> for marking
438 var fieldMarker
= (type
== 'checkbox') ? field
.parent() : field
;
439 setRestoreDefaultBtn(field
, !isDefault
);
440 fieldMarker
[isDefault
? 'removeClass' : 'addClass']('custom');
444 * Enables or disables the "restore default value" button
446 * @param {Element} field
447 * @param {boolean} display
449 function setRestoreDefaultBtn(field
, display
)
451 var el
= $(field
).closest('td').find('.restore-default img');
452 el
[display
? 'show' : 'hide']();
456 // register validators and mark custom values
457 var elements
= $('input[id], select[id], textarea[id]');
458 $('input[id], select[id], textarea[id]').each(function(){
461 el
.bind('change', function() {
462 validate_field_and_fieldset(this, false);
465 var tagName
= el
.attr('tagName');
466 // text fields can be validated after each change
467 if (tagName
== 'INPUT' && el
.attr('type') == 'text') {
468 el
.keyup(function() {
469 validate_field_and_fieldset(el
, true);
473 // disable textarea spellcheck
474 if (tagName
== 'TEXTAREA') {
475 el
.attr('spellcheck', false);
479 // check whether we've refreshed a page and browser remembered modified
481 var check_page_refresh
= $('#check_page_refresh');
482 if (check_page_refresh
.length
== 0 || check_page_refresh
.val() == '1') {
483 // run all field validators
485 for (var i
= 0; i
< elements
.length
; i
++) {
486 validate_field(elements
[i
], false, errors
);
488 // run all fieldset validators
489 $('fieldset').each(function(){
490 validate_fieldset(this, false, errors
);
493 displayErrors(errors
);
494 } else if (check_page_refresh
) {
495 check_page_refresh
.val('1');
500 // END: Form validation and field operations
501 // ------------------------------------------------------------------
503 // ------------------------------------------------------------------
510 * @param {String} tab_id
512 function setTab(tab_id
)
514 $('.tabs a').removeClass('active').filter('[href=' + tab_id
+ ']').addClass('active');
515 $('.tabs_contents fieldset').hide().filter(tab_id
).show();
516 location
.hash
= 'tab_' + tab_id
.substr(1);
517 $('.config-form input[name=tab_hash]').val(location
.hash
);
521 var tabs
= $('.tabs');
525 // add tabs events and activate one tab (the first one or indicated by location hash)
529 setTab($(this).attr('href'));
533 $('.tabs_contents fieldset').hide().filter(':first').show();
535 // tab links handling, check each 200ms
536 // (works with history in FF, further browser support here would be an overkill)
538 var tab_check_fnc = function() {
539 if (location
.hash
!= prev_hash
) {
540 prev_hash
= location
.hash
;
541 if (location
.hash
.match(/^#tab_.+/) && $('#' + location
.hash
.substr(5)).length
) {
542 setTab('#' + location
.hash
.substr(5));
547 setInterval(tab_check_fnc
, 200);
552 // ------------------------------------------------------------------
554 // ------------------------------------------------------------------
555 // Form reset buttons
559 $('input[type=button][name=submit_reset]').click(function() {
560 var fields
= $(this).closest('fieldset').find('input, select, textarea');
561 for (var i
= 0, imax
= fields
.length
; i
< imax
; i
++) {
562 setFieldValue(fields
[i
], getFieldType(fields
[i
]));
568 // END: Form reset buttons
569 // ------------------------------------------------------------------
571 // ------------------------------------------------------------------
572 // "Restore default" and "set value" buttons
576 * Restores field's default value
578 * @param {String} field_id
580 function restoreField(field_id
)
582 var field
= $('#'+field_id
);
583 if (field
.length
== 0 || defaultValues
[field_id
] == undefined) {
586 setFieldValue(field
, getFieldType(field
), defaultValues
[field_id
]);
591 .delegate('.restore-default, .set-value', 'mouseenter', function(){$(this).css('opacity', 1)})
592 .delegate('.restore-default, .set-value', 'mouseleave', function(){$(this).css('opacity', 0.25)})
593 .delegate('.restore-default, .set-value', 'click', function(e
) {
595 var href
= $(this).attr('href');
597 if ($(this).hasClass('restore-default')) {
599 restoreField(field_sel
.substr(1));
601 field_sel
= href
.match(/^[^=]+/)[0];
602 var value
= href
.match(/=(.+)$/)[1];
603 setFieldValue($(field_sel
), 'text', value
);
605 $(field_sel
).trigger('change');
607 .find('.restore-default, .set-value')
608 // inline-block for IE so opacity inheritance works
609 .css({display
: 'inline-block', opacity
: 0.25});
613 // END: "Restore default" and "set value" buttons
614 // ------------------------------------------------------------------
616 // ------------------------------------------------------------------
617 // User preferences import/export
621 offerPrefsAutoimport();
622 var radios
= $('#import_local_storage, #export_local_storage');
623 if (!radios
.length
) {
627 // enable JavaScript dependent fields
629 .attr('disabled', false)
630 .add('#export_text_file, #import_text_file')
632 var enable_id
= $(this).attr('id');
633 var disable_id
= enable_id
.match(/local_storage$/)
634 ? enable_id
.replace(/local_storage$/, 'text_file')
635 : enable_id
.replace(/text_file$/, 'local_storage');
636 $('#opts_'+disable_id
).addClass('disabled').find('input').attr('disabled', true);
637 $('#opts_'+enable_id
).removeClass('disabled').find('input').attr('disabled', false);
640 // detect localStorage state
641 var ls_supported
= window
.localStorage
|| false;
642 var ls_exists
= ls_supported
? (window
.localStorage
['config'] || false) : false;
643 $('.localStorage-'+(ls_supported
? 'un' : '')+'supported').hide();
644 $('.localStorage-'+(ls_exists
? 'empty' : 'exists')).hide();
648 $('form.prefs-form').change(function(){
650 var disabled
= false;
652 disabled
= form
.find('input[type=radio][value$=local_storage]').attr('checked');
653 } else if (!ls_exists
&& form
.attr('name') == 'prefs_import'
654 && $('#import_local_storage')[0].checked
) {
657 form
.find('input[type=submit]').attr('disabled', disabled
);
658 }).submit(function(e
) {
660 if (form
.attr('name') == 'prefs_export' && $('#export_local_storage')[0].checked
) {
662 // use AJAX to read JSON settings and save them
663 savePrefsToLocalStorage(form
);
664 } else if (form
.attr('name') == 'prefs_import' && $('#import_local_storage')[0].checked
) {
665 // set 'json' input and submit form
666 form
.find('input[name=json]').val(window
.localStorage
['config']);
670 $('.click-hide-message').live('click', function(){
672 div
.hide().parent('.group').css('height', '');
673 div
.next('form').show();
678 * Saves user preferences to localStorage
680 * @param {Element} form
682 function savePrefsToLocalStorage(form
)
685 var submit
= form
.find('input[type=submit]');
686 submit
.attr('disabled', true);
688 url
: 'prefs_manage.php',
692 token
: form
.find('input[name=token]').val(),
693 submit_get_json
: true
695 success: function(response
) {
696 window
.localStorage
['config'] = response
.prefs
;
697 window
.localStorage
['config_mtime'] = response
.mtime
;
698 window
.localStorage
['config_mtime_local'] = (new Date()).toUTCString();
700 $('.localStorage-empty').hide();
701 $('.localStorage-exists').show();
702 var group
= form
.parent('.group');
703 group
.css('height', group
.height() + 'px');
705 form
.prev('.click-hide-message').show('fast');
707 complete: function() {
708 submit
.attr('disabled', false);
714 * Updates preferences timestamp in Import form
716 function updatePrefsDate()
718 var d
= new Date(window
.localStorage
['config_mtime_local']);
719 var msg
= PMA_messages
['strSavedOn'].replace('@DATE@', formatDate(d
));
720 $('#opts_import_local_storage .localStorage-exists').html(msg
);
724 * Returns date formatted as YYYY-MM-DD HH:II
728 function formatDate(d
)
730 return d
.getFullYear() + '-'
731 + (d
.getMonth() < 10 ? '0'+d
.getMonth() : d
.getMonth())
732 + '-' + (d
.getDate() < 10 ? '0'+d
.getDate() : d
.getDate())
733 + ' ' + (d
.getHours() < 10 ? '0'+d
.getHours() : d
.getHours())
734 + ':' + (d
.getMinutes() < 10 ? '0'+d
.getMinutes() : d
.getMinutes());
738 * Prepares message which informs that localStorage preferences are available and can be imported
740 function offerPrefsAutoimport()
742 var has_config
= (window
.localStorage
|| false) && (window
.localStorage
['config'] || false);
743 var cnt
= $('#prefs_autoload');
744 if (!cnt
.length
|| !has_config
) {
747 cnt
.find('a').click(function(e
) {
750 if (a
.attr('href') == '#no') {
753 token
: cnt
.find('input[name=token]').val(),
754 prefs_autoload
: 'hide'});
757 cnt
.find('input[name=json]').val(window
.localStorage
['config']);
758 cnt
.find('form').submit();
764 // END: User preferences import/export
765 // ------------------------------------------------------------------