Merge branch 'QA_3_4'
[phpmyadmin-themes.git] / js / config.js
blob7f55503f5f4ece3fd15ddad56974cdc0061d713f
1 /**
2  * Functions used in configuration forms and on user preferences pages
3  */
5 // default values for fields
6 var defaultValues = {};
8 // language strings
9 var PMA_messages = {};
11 /**
12  * Returns field type
13  *
14  * @param {Element} field
15  */
16 function getFieldType(field) {
17         field = $(field);
18     var tagName = field.attr('tagName');
19         if (tagName == 'INPUT') {
20         return field.attr('type');
21     } else if (tagName == 'SELECT') {
22         return 'select';
23     } else if (tagName == 'TEXTAREA') {
24         return 'text';
25     }
26     return '';
29 /**
30  * Sets field value
31  *
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'
37  *
38  * @param {Element} field
39  * @param {String}  field_type  see {@link #getFieldType}
40  * @param {String|Boolean}  [value]
41  */
42 function setFieldValue(field, field_type, value) {
43         field = $(field);
44     switch (field_type) {
45         case 'text':
46             field.attr('value', (value != undefined ? value : field.attr('defaultValue')));
47             break;
48         case 'checkbox':
49             field.attr('checked', (value != undefined ? value : field.attr('defaultChecked')));
50             break;
51         case 'select':
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;
57                 }
58             } else {
59                 for (i = 0; i < imax; i++) {
60                         options[i].selected = (value.indexOf(options[i].value) != -1);
61                 }
62             }
63             break;
64     }
65     markField(field);
68 /**
69  * Gets field value
70  *
71  * Will return one of:
72  * o String - if type is 'text'
73  * o boolean - if type is 'checkbox'
74  * o Array of values - if type is 'select'
75  *
76  * @param {Element} field
77  * @param {String}  field_type returned by {@link #getFieldType}
78  * @type Boolean|String|String[]
79  */
80 function getFieldValue(field, field_type) {
81         field = $(field);
82     switch (field_type) {
83         case 'text':
84             return field.attr('value');
85         case 'checkbox':
86             return field.attr('checked');
87         case 'select':
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);
93                 }
94             }
95             return items;
96     }
97     return null;
101  * Returns values for all fields in fieldsets
102  */
103 function getAllValues() {
104     var elements = $('fieldset input, fieldset select, fieldset textarea');
105     var values = {};
106     var type, value;
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') {
113                 value = value[0];
114             }
115             values[elements[i].name] = value;
116         }
117     }
118     return values;
122  * Checks whether field has its default value
124  * @param {Element} field
125  * @param {String}  type
126  * @return boolean
127  */
128 function checkFieldDefault(field, type) {
129     field = $(field);
130     var field_id = field.attr('id');
131     if (typeof defaultValues[field_id] == 'undefined') {
132         return true;
133     }
134     var isDefault = true;
135     var currentValue = getFieldValue(field, type);
136     if (type != 'select') {
137         isDefault = currentValue == defaultValues[field_id];
138     } else {
139         // compare arrays, will work for our representation of select values
140         if (currentValue.length != defaultValues[field_id].length) {
141             isDefault = false;
142         }
143         else {
144             for (var i = 0; i < currentValue.length; i++) {
145                 if (currentValue[i] != defaultValues[field_id][i]) {
146                     isDefault = false;
147                     break;
148                 }
149             }
150         }
151     }
152     return isDefault;
156  * Returns element's id prefix
157  * @param {Element} element
158  */
159 function getIdPrefix(element) {
160     return $(element).attr('id').replace(/[^-]+$/, '');
163 // ------------------------------------------------------------------
164 // Form validation and field operations
167 // form validator assignments
168 var validate = {};
170 // form validator list
171 var validators = {
172     // regexp: numeric value
173     _regexp_numeric: /^[0-9]+$/,
174     // regexp: extract parts from PCRE expression
175     _regexp_pcre_extract: /(.)(.*)\1(.*)?/,
176     /**
177      * Validates positive number
178      *
179      * @param {boolean} isKeyUp
180      */
181     validate_positive_number: function (isKeyUp) {
182         if (isKeyUp && this.value == '') {
183             return true;
184         }
185         var result = this.value != '0' && validators._regexp_numeric.test(this.value);
186         return result ? true : PMA_messages['error_nan_p'];
187     },
188     /**
189      * Validates non-negative number
190      *
191      * @param {boolean} isKeyUp
192      */
193     validate_non_negative_number: function (isKeyUp) {
194         if (isKeyUp && this.value == '') {
195             return true;
196         }
197         var result = validators._regexp_numeric.test(this.value);
198         return result ? true : PMA_messages['error_nan_nneg'];
199     },
200     /**
201      * Validates port number
202      *
203      * @param {boolean} isKeyUp
204      */
205     validate_port_number: function(isKeyUp) {
206         if (this.value == '') {
207             return true;
208         }
209         var result = validators._regexp_numeric.test(this.value) && this.value != '0';
210         return result && this.value <= 65535 ? true : PMA_messages['error_incorrect_port'];
211     },
212     /**
213      * Validates value according to given regular expression
214      *
215      * @param {boolean} isKeyUp
216      * @param {string}  regexp
217      */
218     validate_by_regex: function(isKeyUp, regexp) {
219         if (isKeyUp && this.value == '') {
220             return true;
221         }
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'];
226     },
227     /**
228      * Validates upper bound for numeric inputs
229      *
230      * @param {boolean} isKeyUp
231      * @param {int} max_value
232      */
233     validate_upper_bound: function(isKeyUp, max_value) {
234         var val = parseInt(this.value);
235         if (isNaN(val)) {
236             return true;
237         }
238         return val <= max_value ? true : PMA_messages['error_value_lte'].replace('%s', max_value);
239     },
240     // field validators
241     _field: {
242     },
243     // fieldset validators
244     _fieldset: {
245     }
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
255  */
256 function validateField(id, type, onKeyUp, params) {
257     if (typeof validators[type] == 'undefined') {
258         return;
259     }
260     if (typeof validate[id] == 'undefined') {
261         validate[id] = [];
262     }
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
271  * @type Array
272  * @return array of [function, paramseters to be passed to function]
273  */
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]];
279     }
281     // look for registered validators
282     var functions = [];
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]) {
287                 continue;
288             }
289             functions.push([validators[validate[field_id][i][0]], validate[field_id][i][1]]);
290         }
291     }
293     return functions;
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}
303  */
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) {
315             return item != '';
316         });
318         // CSS error class
319         if (!isFieldset) {
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');
323         }
325         if (errors.length) {
326             // if error container doesn't exist, create it
327             if (errorCnt.length == 0) {
328                 if (isFieldset) {
329                     errorCnt = $('<dl class="errors" />');
330                     field.find('table').before(errorCnt);
331                 } else {
332                     errorCnt = $('<dl class="inline_errors" />');
333                     field.closest('td').append(errorCnt);
334                 }
335             }
337             var html = '';
338             for (var i = 0, imax = errors.length; i < imax; i++) {
339                 html += '<dd>' + errors[i] + '</dd>';
340             }
341             errorCnt.html(html);
342         } else if (errorCnt !== null) {
343             // remove useless error container
344             errorCnt.remove();
345         }
346     }
350  * Validates fieldset and puts errors in 'errors' object
352  * @param {Element} fieldset
353  * @param {boolean} isKeyUp
354  * @param {Object}  errors
355  */
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] = [];
363             }
364             if (typeof fieldset_errors[field_id] == 'string') {
365                 fieldset_errors[field_id] = [fieldset_errors[field_id]];
366             }
367             $.merge(errors[field_id], fieldset_errors[field_id]);
368         }
369     }
373  * Validates form field and puts errors in 'errors' object
375  * @param {Element} field
376  * @param {boolean} isKeyUp
377  * @param {Object}  errors
378  */
379 function validate_field(field, isKeyUp, errors) {
380         field = $(field);
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)
387             : [];
388         args.unshift(isKeyUp);
389         var result = functions[i][0].apply(field[0], args);
390         if (result !== true) {
391             if (typeof result == 'string') {
392                 result = [result];
393             }
394             $.merge(errors[field_id], result);
395         }
396     }
400  * Validates form field and parent fieldset
402  * @param {Element} field
403  * @param {boolean} isKeyUp
404  */
405 function validate_field_and_fieldset(field, isKeyUp) {
406         field = $(field);
407     var errors = {};
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
417  */
418 function markField(field) {
419         field = $(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
434  */
435 function setRestoreDefaultBtn(field, display) {
436     var el = $(field).closest('td').find('.restore-default img');
437     el[display ? 'show' : 'hide']();
440 $(function() {
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(){
444         markField(this);
445         var el = $(this);
446         el.bind('change', function() {
447             validate_field_and_fieldset(this, false);
448             markField(this);
449         });
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);
455                 markField(el);
456             });
457         }
458         // disable textarea spellcheck
459         if (tagName == 'TEXTAREA') {
460                 el.attr('spellcheck', false);
461         }
462     });
464         // check whether we've refreshed a page and browser remembered modified
465         // form values
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
469                 var errors = {};
470                 for (var i = 0; i < elements.length; i++) {
471                         validate_field(elements[i], false, errors);
472                 }
473                 // run all fieldset validators
474                 $('fieldset').each(function(){
475                         validate_fieldset(this, false, errors);
476                 });
478                 displayErrors(errors);
479         } else if (check_page_refresh) {
480                 check_page_refresh.val('1');
481         }
485 // END: Form validation and field operations
486 // ------------------------------------------------------------------
488 // ------------------------------------------------------------------
489 // Tabbed forms
493  * Sets active tab
495  * @param {String} tab_id
496  */
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);
504 $(function() {
505     var tabs = $('.tabs');
506     if (!tabs.length) {
507         return;
508     }
509     // add tabs events and activate one tab (the first one or indicated by location hash)
510     tabs.find('a')
511         .click(function(e) {
512             e.preventDefault();
513             setTab($(this).attr('href'));
514         })
515         .filter(':first')
516         .addClass('active');
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)
521     var prev_hash;
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));
527             }
528         }
529     };
530     tab_check_fnc();
531     setInterval(tab_check_fnc, 200);
535 // END: Tabbed forms
536 // ------------------------------------------------------------------
538 // ------------------------------------------------------------------
539 // Form reset buttons
542 $(function() {
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]));
547         }
548     });
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
563  */
564 function restoreField(field_id) {
565     var field = $('#'+field_id);
566     if (field.length == 0 || defaultValues[field_id] == undefined) {
567         return;
568     }
569     setFieldValue(field, getFieldType(field), defaultValues[field_id]);
572 $(function() {
573     $('.tabs_contents')
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) {
577             e.preventDefault();
578             var href = $(this).attr('href');
579             var field_sel;
580             if ($(this).hasClass('restore-default')) {
581                 field_sel = href;
582                 restoreField(field_sel.substr(1));
583             } else {
584                 field_sel = href.match(/^[^=]+/)[0];
585                 var value = href.match(/=(.+)$/)[1];
586                 setFieldValue($(field_sel), 'text', value);
587             }
588             $(field_sel).trigger('change');
589         })
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
603 $(function() {
604     offerPrefsAutoimport();
605     var radios = $('#import_local_storage, #export_local_storage');
606     if (!radios.length) {
607         return;
608     }
610     // enable JavaScript dependent fields
611     radios
612         .attr('disabled', false)
613         .add('#export_text_file, #import_text_file')
614         .click(function(){
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);
621         });
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();
628     if (ls_exists) {
629         updatePrefsDate();
630     }
631     $('form.prefs-form').change(function(){
632         var form = $(this);
633         var disabled = false;
634         if (!ls_supported) {
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) {
638             disabled = true;
639         }
640         form.find('input[type=submit]').attr('disabled', disabled);
641     }).submit(function(e) {
642         var form = $(this);
643         if (form.attr('name') == 'prefs_export' && $('#export_local_storage')[0].checked) {
644             e.preventDefault();
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']);
650         }
651     });
653     $('.click-hide-message').live('click', function(){
654         var div = $(this);
655         div.hide().parent('.group').css('height', '');
656         div.next('form').show();
657     });
661  * Saves user preferences to localStorage
663  * @param {Element} form
664  */
665 function savePrefsToLocalStorage(form)
667     form = $(form);
668     var submit = form.find('input[type=submit]');
669     submit.attr('disabled', true);
670     $.ajax({
671         url: 'prefs_manage.php',
672         cache: false,
673         type: 'POST',
674         data: {
675             token: form.find('input[name=token]').val(),
676             submit_get_json: true
677         },
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();
682             updatePrefsDate();
683             $('.localStorage-empty').hide();
684             $('.localStorage-exists').show();
685             var group = form.parent('.group');
686             group.css('height', group.height() + 'px');
687             form.hide('fast');
688             form.prev('.click-hide-message').show('fast');
689         },
690         complete: function() {
691             submit.attr('disabled', false);
692         }
693     });
697  * Updates preferences timestamp in Import form
698  */
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
709  * @param {Date} d
710  */
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
722  */
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) {
728         return;
729     }
730     cnt.find('a').click(function(e) {
731         e.preventDefault();
732         var a = $(this);
733         if (a.attr('href') == '#no') {
734             cnt.remove();
735             $.post('main.php', {
736                 token: cnt.find('input[name=token]').val(),
737                 prefs_autoload: 'hide'});
738             return;
739         }
740         cnt.find('input[name=json]').val(window.localStorage['config']);
741         cnt.find('form').submit();
742     });
743     cnt.show();
747 // END: User preferences import/export
748 // ------------------------------------------------------------------