Advisor: mark that 'Rate of reading fixed position' may be wrong, requires further...
[phpmyadmin/thilanka.git] / js / config.js
blob53702e3938d668c573057a7c51b7c27281d3c467
1 /* vim: set expandtab sw=4 ts=4 sts=4: */
2 /**
3 * Functions used in configuration forms and on user preferences pages
4 */
6 // default values for fields
7 var defaultValues = {};
9 // language strings
10 var PMA_messages = {};
12 /**
13 * Returns field type
15 * @param {Element} field
17 function getFieldType(field)
19 field = $(field);
20 var tagName = field.prop('tagName');
21 if (tagName == 'INPUT') {
22 return field.attr('type');
23 } else if (tagName == 'SELECT') {
24 return 'select';
25 } else if (tagName == 'TEXTAREA') {
26 return 'text';
28 return '';
31 /**
32 * Sets field value
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)
46 field = $(field);
47 switch (field_type) {
48 case 'text':
49 field.attr('value', (value != undefined ? value : field.attr('defaultValue')));
50 break;
51 case 'checkbox':
52 field.attr('checked', (value != undefined ? value : field.attr('defaultChecked')));
53 break;
54 case 'select':
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;
61 } else {
62 for (i = 0; i < imax; i++) {
63 options[i].selected = (value.indexOf(options[i].value) != -1);
66 break;
68 markField(field);
71 /**
72 * Gets field value
74 * Will return one of:
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)
85 field = $(field);
86 switch (field_type) {
87 case 'text':
88 return field.prop('value');
89 case 'checkbox':
90 return field.prop('checked');
91 case 'select':
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);
99 return items;
101 return null;
105 * Returns values for all fields in fieldsets
107 function getAllValues()
109 var elements = $('fieldset input, fieldset select, fieldset textarea');
110 var values = {};
111 var type, value;
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') {
118 value = value[0];
120 values[elements[i].name] = value;
123 return values;
127 * Checks whether field has its default value
129 * @param {Element} field
130 * @param {String} type
131 * @return boolean
133 function checkFieldDefault(field, type)
135 field = $(field);
136 var field_id = field.attr('id');
137 if (typeof defaultValues[field_id] == 'undefined') {
138 return true;
140 var isDefault = true;
141 var currentValue = getFieldValue(field, type);
142 if (type != 'select') {
143 isDefault = currentValue == defaultValues[field_id];
144 } else {
145 // compare arrays, will work for our representation of select values
146 if (currentValue.length != defaultValues[field_id].length) {
147 isDefault = false;
149 else {
150 for (var i = 0; i < currentValue.length; i++) {
151 if (currentValue[i] != defaultValues[field_id][i]) {
152 isDefault = false;
153 break;
158 return isDefault;
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
175 var validate = {};
177 // form validator list
178 var validators = {
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 == '') {
190 return true;
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 == '') {
202 return true;
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 == '') {
214 return true;
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 == '') {
227 return true;
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);
242 if (isNaN(val)) {
243 return true;
245 return val <= max_value ? true : PMA_messages['error_value_lte'].replace('%s', max_value);
247 // field validators
248 _field: {
250 // fieldset validators
251 _fieldset: {
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') {
266 return;
268 if (typeof validate[id] == 'undefined') {
269 validate[id] = [];
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
279 * @type Array
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
291 var functions = [];
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]) {
296 continue;
298 functions.push([validators[validate[field_id][i][0]], validate[field_id][i][1]]);
302 return functions;
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) {
325 return item != '';
328 // CSS error class
329 if (!isFieldset) {
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');
335 if (errors.length) {
336 // if error container doesn't exist, create it
337 if (errorCnt.length == 0) {
338 if (isFieldset) {
339 errorCnt = $('<dl class="errors" />');
340 field.find('table').before(errorCnt);
341 } else {
342 errorCnt = $('<dl class="inline_errors" />');
343 field.closest('td').append(errorCnt);
347 var html = '';
348 for (var i = 0, imax = errors.length; i < imax; i++) {
349 html += '<dd>' + errors[i] + '</dd>';
351 errorCnt.html(html);
352 } else if (errorCnt !== null) {
353 // remove useless error container
354 errorCnt.remove();
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)
392 field = $(field);
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)
399 : [];
400 args.unshift(isKeyUp);
401 var result = functions[i][0].apply(field[0], args);
402 if (result !== true) {
403 if (typeof result == 'string') {
404 result = [result];
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)
419 field = $(field);
420 var errors = {};
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)
433 field = $(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']();
455 $(function() {
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(){
459 markField(this);
460 var el = $(this);
461 el.bind('change', function() {
462 validate_field_and_fieldset(this, false);
463 markField(this);
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);
470 markField(el);
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
480 // form values
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
484 var errors = {};
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 // ------------------------------------------------------------------
504 // Tabbed forms
508 * Sets active tab
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);
520 $(function() {
521 var tabs = $('.tabs');
522 if (!tabs.length) {
523 return;
525 // add tabs events and activate one tab (the first one or indicated by location hash)
526 tabs.find('a')
527 .click(function(e) {
528 e.preventDefault();
529 setTab($(this).attr('href'));
531 .filter(':first')
532 .addClass('active');
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)
537 var prev_hash;
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));
546 tab_check_fnc();
547 setInterval(tab_check_fnc, 200);
551 // END: Tabbed forms
552 // ------------------------------------------------------------------
554 // ------------------------------------------------------------------
555 // Form reset buttons
558 $(function() {
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) {
584 return;
586 setFieldValue(field, getFieldType(field), defaultValues[field_id]);
589 $(function() {
590 $('.tabs_contents')
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) {
594 e.preventDefault();
595 var href = $(this).attr('href');
596 var field_sel;
597 if ($(this).hasClass('restore-default')) {
598 field_sel = href;
599 restoreField(field_sel.substr(1));
600 } else {
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
620 $(function() {
621 offerPrefsAutoimport();
622 var radios = $('#import_local_storage, #export_local_storage');
623 if (!radios.length) {
624 return;
627 // enable JavaScript dependent fields
628 radios
629 .attr('disabled', false)
630 .add('#export_text_file, #import_text_file')
631 .click(function(){
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();
645 if (ls_exists) {
646 updatePrefsDate();
648 $('form.prefs-form').change(function(){
649 var form = $(this);
650 var disabled = false;
651 if (!ls_supported) {
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) {
655 disabled = true;
657 form.find('input[type=submit]').attr('disabled', disabled);
658 }).submit(function(e) {
659 var form = $(this);
660 if (form.attr('name') == 'prefs_export' && $('#export_local_storage')[0].checked) {
661 e.preventDefault();
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(){
671 var div = $(this);
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)
684 form = $(form);
685 var submit = form.find('input[type=submit]');
686 submit.attr('disabled', true);
687 $.ajax({
688 url: 'prefs_manage.php',
689 cache: false,
690 type: 'POST',
691 data: {
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();
699 updatePrefsDate();
700 $('.localStorage-empty').hide();
701 $('.localStorage-exists').show();
702 var group = form.parent('.group');
703 group.css('height', group.height() + 'px');
704 form.hide('fast');
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
726 * @param {Date} d
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) {
745 return;
747 cnt.find('a').click(function(e) {
748 e.preventDefault();
749 var a = $(this);
750 if (a.attr('href') == '#no') {
751 cnt.remove();
752 $.post('main.php', {
753 token: cnt.find('input[name=token]').val(),
754 prefs_autoload: 'hide'});
755 return;
757 cnt.find('input[name=json]').val(window.localStorage['config']);
758 cnt.find('form').submit();
760 cnt.show();
764 // END: User preferences import/export
765 // ------------------------------------------------------------------