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 // ------------------------------------------------------------------