Translated using Weblate (Hungarian)
[phpmyadmin.git] / js / config.js
blob63147b7e907fffb21deb0569162f6669cece8822
1 /* vim: set expandtab sw=4 ts=4 sts=4: */
2 /**
3  * Functions used in configuration forms and on user preferences pages
4  */
6 /**
7  * Unbind all event handlers before tearing down a page
8  */
9 AJAX.registerTeardown('config.js', function () {
10     $('input[id], select[id], textarea[id]').unbind('change').unbind('keyup');
11     $('input[type=button][name=submit_reset]').unbind('click');
12     $('div.tabs_contents').undelegate();
13     $('#import_local_storage, #export_local_storage').unbind('click');
14     $('form.prefs-form').unbind('change').unbind('submit');
15     $('div.click-hide-message').die('click');
16     $('#prefs_autoload').find('a').unbind('click');
17 });
19 AJAX.registerOnload('config.js', function () {
20     $('#topmenu2').find('li.active a').attr('rel', 'samepage');
21     $('#topmenu2').find('li:not(.active) a').attr('rel', 'newpage');
22 });
24 // default values for fields
25 var defaultValues = {};
27 /**
28  * Returns field type
29  *
30  * @param {Element} field
31  */
32 function getFieldType(field)
34     field = $(field);
35     var tagName = field.prop('tagName');
36     if (tagName == 'INPUT') {
37         return field.attr('type');
38     } else if (tagName == 'SELECT') {
39         return 'select';
40     } else if (tagName == 'TEXTAREA') {
41         return 'text';
42     }
43     return '';
46 /**
47  * Sets field value
48  *
49  * value must be of type:
50  * o undefined (omitted) - restore default value (form default, not PMA default)
51  * o String - if field_type is 'text'
52  * o boolean - if field_type is 'checkbox'
53  * o Array of values - if field_type is 'select'
54  *
55  * @param {Element} field
56  * @param {String}  field_type  see {@link #getFieldType}
57  * @param {String|Boolean}  [value]
58  */
59 function setFieldValue(field, field_type, value)
61     field = $(field);
62     switch (field_type) {
63     case 'text':
64     case 'number':
65         //TODO: replace to .val()
66         field.attr('value', (value !== undefined ? value : field.attr('defaultValue')));
67         break;
68     case 'checkbox':
69         //TODO: replace to .prop()
70         field.attr('checked', (value !== undefined ? value : field.attr('defaultChecked')));
71         break;
72     case 'select':
73         var options = field.prop('options');
74         var i, imax = options.length;
75         if (value === undefined) {
76             for (i = 0; i < imax; i++) {
77                 options[i].selected = options[i].defaultSelected;
78             }
79         } else {
80             for (i = 0; i < imax; i++) {
81                 options[i].selected = (value.indexOf(options[i].value) != -1);
82             }
83         }
84         break;
85     }
86     markField(field);
89 /**
90  * Gets field value
91  *
92  * Will return one of:
93  * o String - if type is 'text'
94  * o boolean - if type is 'checkbox'
95  * o Array of values - if type is 'select'
96  *
97  * @param {Element} field
98  * @param {String}  field_type returned by {@link #getFieldType}
99  * @type Boolean|String|String[]
100  */
101 function getFieldValue(field, field_type)
103     field = $(field);
104     switch (field_type) {
105     case 'text':
106     case 'number':
107         return field.prop('value');
108     case 'checkbox':
109         return field.prop('checked');
110     case 'select':
111         var options = field.prop('options');
112         var i, imax = options.length, items = [];
113         for (i = 0; i < imax; i++) {
114             if (options[i].selected) {
115                 items.push(options[i].value);
116             }
117         }
118         return items;
119     }
120     return null;
124  * Returns values for all fields in fieldsets
125  */
126 function getAllValues()
128     var elements = $('fieldset input, fieldset select, fieldset textarea');
129     var values = {};
130     var type, value;
131     for (var i = 0; i < elements.length; i++) {
132         type = getFieldType(elements[i]);
133         value = getFieldValue(elements[i], type);
134         if (typeof value != 'undefined') {
135             // we only have single selects, fatten array
136             if (type == 'select') {
137                 value = value[0];
138             }
139             values[elements[i].name] = value;
140         }
141     }
142     return values;
146  * Checks whether field has its default value
148  * @param {Element} field
149  * @param {String}  type
150  * @return boolean
151  */
152 function checkFieldDefault(field, type)
154     field = $(field);
155     var field_id = field.attr('id');
156     if (typeof defaultValues[field_id] == 'undefined') {
157         return true;
158     }
159     var isDefault = true;
160     var currentValue = getFieldValue(field, type);
161     if (type != 'select') {
162         isDefault = currentValue == defaultValues[field_id];
163     } else {
164         // compare arrays, will work for our representation of select values
165         if (currentValue.length != defaultValues[field_id].length) {
166             isDefault = false;
167         }
168         else {
169             for (var i = 0; i < currentValue.length; i++) {
170                 if (currentValue[i] != defaultValues[field_id][i]) {
171                     isDefault = false;
172                     break;
173                 }
174             }
175         }
176     }
177     return isDefault;
181  * Returns element's id prefix
182  * @param {Element} element
183  */
184 function getIdPrefix(element)
186     return $(element).attr('id').replace(/[^-]+$/, '');
189 // ------------------------------------------------------------------
190 // Form validation and field operations
193 // form validator assignments
194 var validate = {};
196 // form validator list
197 var validators = {
198     // regexp: numeric value
199     _regexp_numeric: /^[0-9]+$/,
200     // regexp: extract parts from PCRE expression
201     _regexp_pcre_extract: /(.)(.*)\1(.*)?/,
202     /**
203      * Validates positive number
204      *
205      * @param {boolean} isKeyUp
206      */
207     PMA_validatePositiveNumber: function (isKeyUp) {
208         if (isKeyUp && this.value === '') {
209             return true;
210         }
211         var result = this.value != '0' && validators._regexp_numeric.test(this.value);
212         return result ? true : PMA_messages.error_nan_p;
213     },
214     /**
215      * Validates non-negative number
216      *
217      * @param {boolean} isKeyUp
218      */
219     PMA_validateNonNegativeNumber: function (isKeyUp) {
220         if (isKeyUp && this.value === '') {
221             return true;
222         }
223         var result = validators._regexp_numeric.test(this.value);
224         return result ? true : PMA_messages.error_nan_nneg;
225     },
226     /**
227      * Validates port number
228      *
229      * @param {boolean} isKeyUp
230      */
231     PMA_validatePortNumber: function (isKeyUp) {
232         if (this.value === '') {
233             return true;
234         }
235         var result = validators._regexp_numeric.test(this.value) && this.value != '0';
236         return result && this.value <= 65535 ? true : PMA_messages.error_incorrect_port;
237     },
238     /**
239      * Validates value according to given regular expression
240      *
241      * @param {boolean} isKeyUp
242      * @param {string}  regexp
243      */
244     PMA_validateByRegex: function (isKeyUp, regexp) {
245         if (isKeyUp && this.value === '') {
246             return true;
247         }
248         // convert PCRE regexp
249         var parts = regexp.match(validators._regexp_pcre_extract);
250         var valid = this.value.match(new RegExp(parts[2], parts[3])) !== null;
251         return valid ? true : PMA_messages.error_invalid_value;
252     },
253     /**
254      * Validates upper bound for numeric inputs
255      *
256      * @param {boolean} isKeyUp
257      * @param {int} max_value
258      */
259     PMA_validateUpperBound: function (isKeyUp, max_value) {
260         var val = parseInt(this.value, 10);
261         if (isNaN(val)) {
262             return true;
263         }
264         return val <= max_value ? true : PMA_sprintf(PMA_messages.error_value_lte, max_value);
265     },
266     // field validators
267     _field: {
268     },
269     // fieldset validators
270     _fieldset: {
271     }
275  * Registers validator for given field
277  * @param {String}  id       field id
278  * @param {String}  type     validator (key in validators object)
279  * @param {boolean} onKeyUp  whether fire on key up
280  * @param {Array}   params   validation function parameters
281  */
282 function validateField(id, type, onKeyUp, params)
284     if (typeof validators[type] == 'undefined') {
285         return;
286     }
287     if (typeof validate[id] == 'undefined') {
288         validate[id] = [];
289     }
290     validate[id].push([type, params, onKeyUp]);
294  * Returns validation functions associated with form field
296  * @param {String}  field_id     form field id
297  * @param {boolean} onKeyUpOnly  see validateField
298  * @type Array
299  * @return array of [function, parameters to be passed to function]
300  */
301 function getFieldValidators(field_id, onKeyUpOnly)
303     // look for field bound validator
304     var name = field_id.match(/[^-]+$/)[0];
305     if (typeof validators._field[name] != 'undefined') {
306         return [[validators._field[name], null]];
307     }
309     // look for registered validators
310     var functions = [];
311     if (typeof validate[field_id] != 'undefined') {
312         // validate[field_id]: array of [type, params, onKeyUp]
313         for (var i = 0, imax = validate[field_id].length; i < imax; i++) {
314             if (onKeyUpOnly && !validate[field_id][i][2]) {
315                 continue;
316             }
317             functions.push([validators[validate[field_id][i][0]], validate[field_id][i][1]]);
318         }
319     }
321     return functions;
325  * Displays errors for given form fields
327  * WARNING: created DOM elements must be identical with the ones made by
328  * display_input() in FormDisplay.tpl.php!
330  * @param {Object} error_list list of errors in the form {field id: error array}
331  */
332 function displayErrors(error_list)
334     var tempIsEmpty = function (item) {
335         return item !== '';
336     };
338     for (var field_id in error_list) {
339         var errors = error_list[field_id];
340         var field = $('#' + field_id);
341         var isFieldset = field.attr('tagName') == 'FIELDSET';
342         var errorCnt;
343         if (isFieldset) {
344             errorCnt = field.find('dl.errors');
345         } else {
346             errorCnt = field.siblings('.inline_errors');
347         }
349         // remove empty errors (used to clear error list)
350         errors = $.grep(errors, tempIsEmpty);
352         // CSS error class
353         if (!isFieldset) {
354             // checkboxes uses parent <span> for marking
355             var fieldMarker = (field.attr('type') == 'checkbox') ? field.parent() : field;
356             fieldMarker[errors.length ? 'addClass' : 'removeClass']('field-error');
357         }
359         if (errors.length) {
360             // if error container doesn't exist, create it
361             if (errorCnt.length === 0) {
362                 if (isFieldset) {
363                     errorCnt = $('<dl class="errors" />');
364                     field.find('table').before(errorCnt);
365                 } else {
366                     errorCnt = $('<dl class="inline_errors" />');
367                     field.closest('td').append(errorCnt);
368                 }
369             }
371             var html = '';
372             for (var i = 0, imax = errors.length; i < imax; i++) {
373                 html += '<dd>' + errors[i] + '</dd>';
374             }
375             errorCnt.html(html);
376         } else if (errorCnt !== null) {
377             // remove useless error container
378             errorCnt.remove();
379         }
380     }
384  * Validates fieldset and puts errors in 'errors' object
386  * @param {Element} fieldset
387  * @param {boolean} isKeyUp
388  * @param {Object}  errors
389  */
390 function validate_fieldset(fieldset, isKeyUp, errors)
392     fieldset = $(fieldset);
393     if (fieldset.length && typeof validators._fieldset[fieldset.attr('id')] != 'undefined') {
394         var fieldset_errors = validators._fieldset[fieldset.attr('id')].apply(fieldset[0], [isKeyUp]);
395         for (var field_id in fieldset_errors) {
396             if (typeof errors[field_id] == 'undefined') {
397                 errors[field_id] = [];
398             }
399             if (typeof fieldset_errors[field_id] == 'string') {
400                 fieldset_errors[field_id] = [fieldset_errors[field_id]];
401             }
402             $.merge(errors[field_id], fieldset_errors[field_id]);
403         }
404     }
408  * Validates form field and puts errors in 'errors' object
410  * @param {Element} field
411  * @param {boolean} isKeyUp
412  * @param {Object}  errors
413  */
414 function validate_field(field, isKeyUp, errors)
416     var args, result;
417     field = $(field);
418     var field_id = field.attr('id');
419     errors[field_id] = [];
420     var functions = getFieldValidators(field_id, isKeyUp);
421     for (var i = 0; i < functions.length; i++) {
422         if (typeof functions[i][1] !== 'undefined' && functions[i][1] !== null) {
423             args = functions[i][1].slice(0);
424         } else {
425             args = [];
426         }
427         args.unshift(isKeyUp);
428         result = functions[i][0].apply(field[0], args);
429         if (result !== true) {
430             if (typeof result == 'string') {
431                 result = [result];
432             }
433             $.merge(errors[field_id], result);
434         }
435     }
439  * Validates form field and parent fieldset
441  * @param {Element} field
442  * @param {boolean} isKeyUp
443  */
444 function validate_field_and_fieldset(field, isKeyUp)
446     field = $(field);
447     var errors = {};
448     validate_field(field, isKeyUp, errors);
449     validate_fieldset(field.closest('fieldset'), isKeyUp, errors);
450     displayErrors(errors);
454  * Marks field depending on its value (system default or custom)
456  * @param {Element} field
457  */
458 function markField(field)
460     field = $(field);
461     var type = getFieldType(field);
462     var isDefault = checkFieldDefault(field, type);
464     // checkboxes uses parent <span> for marking
465     var fieldMarker = (type == 'checkbox') ? field.parent() : field;
466     setRestoreDefaultBtn(field, !isDefault);
467     fieldMarker[isDefault ? 'removeClass' : 'addClass']('custom');
471  * Enables or disables the "restore default value" button
473  * @param {Element} field
474  * @param {boolean} display
475  */
476 function setRestoreDefaultBtn(field, display)
478     var el = $(field).closest('td').find('.restore-default img');
479     el[display ? 'show' : 'hide']();
482 AJAX.registerOnload('config.js', function () {
483     // register validators and mark custom values
484     var elements = $('input[id], select[id], textarea[id]');
485     $('input[id], select[id], textarea[id]').each(function () {
486         markField(this);
487         var el = $(this);
488         el.bind('change', function () {
489             validate_field_and_fieldset(this, false);
490             markField(this);
491         });
492         var tagName = el.attr('tagName');
493         // text fields can be validated after each change
494         if (tagName == 'INPUT' && el.attr('type') == 'text') {
495             el.keyup(function () {
496                 validate_field_and_fieldset(el, true);
497                 markField(el);
498             });
499         }
500         // disable textarea spellcheck
501         if (tagName == 'TEXTAREA') {
502             el.attr('spellcheck', false);
503         }
504     });
506     // check whether we've refreshed a page and browser remembered modified
507     // form values
508     var check_page_refresh = $('#check_page_refresh');
509     if (check_page_refresh.length === 0 || check_page_refresh.val() == '1') {
510         // run all field validators
511         var errors = {};
512         for (var i = 0; i < elements.length; i++) {
513             validate_field(elements[i], false, errors);
514         }
515         // run all fieldset validators
516         $('fieldset').each(function () {
517             validate_fieldset(this, false, errors);
518         });
520         displayErrors(errors);
521     } else if (check_page_refresh) {
522         check_page_refresh.val('1');
523     }
527 // END: Form validation and field operations
528 // ------------------------------------------------------------------
530 // ------------------------------------------------------------------
531 // Tabbed forms
535  * Sets active tab
537  * @param {String} tab_id
538  */
539 function setTab(tab_id)
541     $('ul.tabs li').removeClass('active').find('a[href=#' + tab_id + ']').parent().addClass('active');
542     $('div.tabs_contents fieldset').hide().filter('#' + tab_id).show();
543     location.hash = 'tab_' + tab_id;
544     $('form.config-form input[name=tab_hash]').val(location.hash);
547 AJAX.registerOnload('config.js', function () {
548     var tabs = $('ul.tabs');
549     if (!tabs.length) {
550         return;
551     }
552     // add tabs events and activate one tab (the first one or indicated by location hash)
553     tabs.find('a')
554         .click(function (e) {
555             e.preventDefault();
556             setTab($(this).attr('href').substr(1));
557         })
558         .filter(':first')
559         .parent()
560         .addClass('active');
561     $('div.tabs_contents fieldset').hide().filter(':first').show();
563     // tab links handling, check each 200ms
564     // (works with history in FF, further browser support here would be an overkill)
565     var prev_hash;
566     var tab_check_fnc = function () {
567         if (location.hash != prev_hash) {
568             prev_hash = location.hash;
569             if (location.hash.match(/^#tab_.+/) && $('#' + location.hash.substr(5)).length) {
570                 setTab(location.hash.substr(5));
571             }
572         }
573     };
574     tab_check_fnc();
575     setInterval(tab_check_fnc, 200);
579 // END: Tabbed forms
580 // ------------------------------------------------------------------
582 // ------------------------------------------------------------------
583 // Form reset buttons
586 AJAX.registerOnload('config.js', function () {
587     $('input[type=button][name=submit_reset]').click(function () {
588         var fields = $(this).closest('fieldset').find('input, select, textarea');
589         for (var i = 0, imax = fields.length; i < imax; i++) {
590             setFieldValue(fields[i], getFieldType(fields[i]));
591         }
592     });
596 // END: Form reset buttons
597 // ------------------------------------------------------------------
599 // ------------------------------------------------------------------
600 // "Restore default" and "set value" buttons
604  * Restores field's default value
606  * @param {String} field_id
607  */
608 function restoreField(field_id)
610     var field = $('#' + field_id);
611     if (field.length === 0 || defaultValues[field_id] === undefined) {
612         return;
613     }
614     setFieldValue(field, getFieldType(field), defaultValues[field_id]);
617 AJAX.registerOnload('config.js', function () {
618     $('div.tabs_contents')
619         .delegate('.restore-default, .set-value', 'mouseenter', function () {
620             $(this).css('opacity', 1);
621         })
622         .delegate('.restore-default, .set-value', 'mouseleave', function () {
623             $(this).css('opacity', 0.25);
624         })
625         .delegate('.restore-default, .set-value', 'click', function (e) {
626             e.preventDefault();
627             var href = $(this).attr('href');
628             var field_sel;
629             if ($(this).hasClass('restore-default')) {
630                 field_sel = href;
631                 restoreField(field_sel.substr(1));
632             } else {
633                 field_sel = href.match(/^[^=]+/)[0];
634                 var value = href.match(/\=(.+)$/)[1];
635                 setFieldValue($(field_sel), 'text', value);
636             }
637             $(field_sel).trigger('change');
638         })
639         .find('.restore-default, .set-value')
640         // inline-block for IE so opacity inheritance works
641         .css({display: 'inline-block', opacity: 0.25});
645 // END: "Restore default" and "set value" buttons
646 // ------------------------------------------------------------------
648 // ------------------------------------------------------------------
649 // User preferences import/export
652 AJAX.registerOnload('config.js', function () {
653     offerPrefsAutoimport();
654     var radios = $('#import_local_storage, #export_local_storage');
655     if (!radios.length) {
656         return;
657     }
659     // enable JavaScript dependent fields
660     radios
661         .prop('disabled', false)
662         .add('#export_text_file, #import_text_file')
663         .click(function () {
664             var enable_id = $(this).attr('id');
665             var disable_id;
666             if (enable_id.match(/local_storage$/)) {
667                 disable_id = enable_id.replace(/local_storage$/, 'text_file');
668             } else {
669                 disable_id = enable_id.replace(/text_file$/, 'local_storage');
670             }
671             $('#opts_' + disable_id).addClass('disabled').find('input').prop('disabled', true);
672             $('#opts_' + enable_id).removeClass('disabled').find('input').prop('disabled', false);
673         });
675     // detect localStorage state
676     var ls_supported = window.localStorage || false;
677     var ls_exists = ls_supported ? (window.localStorage.config || false) : false;
678     $('div.localStorage-' + (ls_supported ? 'un' : '') + 'supported').hide();
679     $('div.localStorage-' + (ls_exists ? 'empty' : 'exists')).hide();
680     if (ls_exists) {
681         updatePrefsDate();
682     }
683     $('form.prefs-form').change(function () {
684         var form = $(this);
685         var disabled = false;
686         if (!ls_supported) {
687             disabled = form.find('input[type=radio][value$=local_storage]').prop('checked');
688         } else if (!ls_exists && form.attr('name') == 'prefs_import' &&
689             $('#import_local_storage')[0].checked
690             ) {
691             disabled = true;
692         }
693         form.find('input[type=submit]').prop('disabled', disabled);
694     }).submit(function (e) {
695         var form = $(this);
696         if (form.attr('name') == 'prefs_export' && $('#export_local_storage')[0].checked) {
697             e.preventDefault();
698             // use AJAX to read JSON settings and save them
699             savePrefsToLocalStorage(form);
700         } else if (form.attr('name') == 'prefs_import' && $('#import_local_storage')[0].checked) {
701             // set 'json' input and submit form
702             form.find('input[name=json]').val(window.localStorage.config);
703         }
704     });
706     $('div.click-hide-message').live('click', function () {
707         $(this)
708         .hide()
709         .parent('.group')
710         .css('height', '')
711         .next('form')
712         .show();
713     });
717  * Saves user preferences to localStorage
719  * @param {Element} form
720  */
721 function savePrefsToLocalStorage(form)
723     form = $(form);
724     var submit = form.find('input[type=submit]');
725     submit.prop('disabled', true);
726     $.ajax({
727         url: 'prefs_manage.php',
728         cache: false,
729         type: 'POST',
730         data: {
731             ajax_request: true,
732             server: form.find('input[name=server]').val(),
733             token: form.find('input[name=token]').val(),
734             submit_get_json: true
735         },
736         success: function (data) {
737             if (typeof data !== 'undefined' && data.success === true) {
738                 window.localStorage.config = data.prefs;
739                 window.localStorage.config_mtime = data.mtime;
740                 window.localStorage.config_mtime_local = (new Date()).toUTCString();
741                 updatePrefsDate();
742                 $('div.localStorage-empty').hide();
743                 $('div.localStorage-exists').show();
744                 var group = form.parent('.group');
745                 group.css('height', group.height() + 'px');
746                 form.hide('fast');
747                 form.prev('.click-hide-message').show('fast');
748             } else {
749                 PMA_ajaxShowMessage(data.error);
750             }
751         },
752         complete: function () {
753             submit.prop('disabled', false);
754         }
755     });
759  * Updates preferences timestamp in Import form
760  */
761 function updatePrefsDate()
763     var d = new Date(window.localStorage.config_mtime_local);
764     var msg = PMA_messages.strSavedOn.replace(
765         '@DATE@',
766         PMA_formatDateTime(d)
767     );
768     $('#opts_import_local_storage div.localStorage-exists').html(msg);
772  * Prepares message which informs that localStorage preferences are available and can be imported
773  */
774 function offerPrefsAutoimport()
776     var has_config = (window.localStorage || false) && (window.localStorage.config || false);
777     var cnt = $('#prefs_autoload');
778     if (!cnt.length || !has_config) {
779         return;
780     }
781     cnt.find('a').click(function (e) {
782         e.preventDefault();
783         var a = $(this);
784         if (a.attr('href') == '#no') {
785             cnt.remove();
786             $.post('index.php', {
787                 token: cnt.find('input[name=token]').val(),
788                 prefs_autoload: 'hide'
789             });
790             return;
791         }
792         cnt.find('input[name=json]').val(window.localStorage.config);
793         cnt.find('form').submit();
794     });
795     cnt.show();
799 // END: User preferences import/export
800 // ------------------------------------------------------------------