Translated using Weblate (Dutch)
[phpmyadmin.git] / js / config.js
blobd064b62b5711df1cb2ce05459d06568d5ef875bf
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 : $.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 valdiation 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, paramseters 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     for (var field_id in error_list) {
335         var errors = error_list[field_id];
336         var field = $('#' + field_id);
337         var isFieldset = field.attr('tagName') == 'FIELDSET';
338         var errorCnt;
339         if (isFieldset) {
340             errorCnt = field.find('dl.errors');
341         } else {
342             errorCnt = field.siblings('.inline_errors');
343         }
345         // remove empty errors (used to clear error list)
346         errors = $.grep(errors, function (item) {
347             return item !== '';
348         });
350         // CSS error class
351         if (!isFieldset) {
352             // checkboxes uses parent <span> for marking
353             var fieldMarker = (field.attr('type') == 'checkbox') ? field.parent() : field;
354             fieldMarker[errors.length ? 'addClass' : 'removeClass']('field-error');
355         }
357         if (errors.length) {
358             // if error container doesn't exist, create it
359             if (errorCnt.length === 0) {
360                 if (isFieldset) {
361                     errorCnt = $('<dl class="errors" />');
362                     field.find('table').before(errorCnt);
363                 } else {
364                     errorCnt = $('<dl class="inline_errors" />');
365                     field.closest('td').append(errorCnt);
366                 }
367             }
369             var html = '';
370             for (var i = 0, imax = errors.length; i < imax; i++) {
371                 html += '<dd>' + errors[i] + '</dd>';
372             }
373             errorCnt.html(html);
374         } else if (errorCnt !== null) {
375             // remove useless error container
376             errorCnt.remove();
377         }
378     }
382  * Validates fieldset and puts errors in 'errors' object
384  * @param {Element} fieldset
385  * @param {boolean} isKeyUp
386  * @param {Object}  errors
387  */
388 function validate_fieldset(fieldset, isKeyUp, errors)
390     fieldset = $(fieldset);
391     if (fieldset.length && typeof validators._fieldset[fieldset.attr('id')] != 'undefined') {
392         var fieldset_errors = validators._fieldset[fieldset.attr('id')].apply(fieldset[0], [isKeyUp]);
393         for (var field_id in fieldset_errors) {
394             if (typeof errors[field_id] == 'undefined') {
395                 errors[field_id] = [];
396             }
397             if (typeof fieldset_errors[field_id] == 'string') {
398                 fieldset_errors[field_id] = [fieldset_errors[field_id]];
399             }
400             $.merge(errors[field_id], fieldset_errors[field_id]);
401         }
402     }
406  * Validates form field and puts errors in 'errors' object
408  * @param {Element} field
409  * @param {boolean} isKeyUp
410  * @param {Object}  errors
411  */
412 function validate_field(field, isKeyUp, errors)
414     var args, result;
415     field = $(field);
416     var field_id = field.attr('id');
417     errors[field_id] = [];
418     var functions = getFieldValidators(field_id, isKeyUp);
419     for (var i = 0; i < functions.length; i++) {
420         if (typeof functions[i][1] !== 'undefined' && functions[i][1] !== null) {
421             args = functions[i][1].slice(0);
422         } else {
423             args = [];
424         }
425         args.unshift(isKeyUp);
426         result = functions[i][0].apply(field[0], args);
427         if (result !== true) {
428             if (typeof result == 'string') {
429                 result = [result];
430             }
431             $.merge(errors[field_id], result);
432         }
433     }
437  * Validates form field and parent fieldset
439  * @param {Element} field
440  * @param {boolean} isKeyUp
441  */
442 function validate_field_and_fieldset(field, isKeyUp)
444     field = $(field);
445     var errors = {};
446     validate_field(field, isKeyUp, errors);
447     validate_fieldset(field.closest('fieldset'), isKeyUp, errors);
448     displayErrors(errors);
452  * Marks field depending on its value (system default or custom)
454  * @param {Element} field
455  */
456 function markField(field)
458     field = $(field);
459     var type = getFieldType(field);
460     var isDefault = checkFieldDefault(field, type);
462     // checkboxes uses parent <span> for marking
463     var fieldMarker = (type == 'checkbox') ? field.parent() : field;
464     setRestoreDefaultBtn(field, !isDefault);
465     fieldMarker[isDefault ? 'removeClass' : 'addClass']('custom');
469  * Enables or disables the "restore default value" button
471  * @param {Element} field
472  * @param {boolean} display
473  */
474 function setRestoreDefaultBtn(field, display)
476     var el = $(field).closest('td').find('.restore-default img');
477     el[display ? 'show' : 'hide']();
480 AJAX.registerOnload('config.js', function () {
481     // register validators and mark custom values
482     var elements = $('input[id], select[id], textarea[id]');
483     $('input[id], select[id], textarea[id]').each(function () {
484         markField(this);
485         var el = $(this);
486         el.bind('change', function () {
487             validate_field_and_fieldset(this, false);
488             markField(this);
489         });
490         var tagName = el.attr('tagName');
491         // text fields can be validated after each change
492         if (tagName == 'INPUT' && el.attr('type') == 'text') {
493             el.keyup(function () {
494                 validate_field_and_fieldset(el, true);
495                 markField(el);
496             });
497         }
498         // disable textarea spellcheck
499         if (tagName == 'TEXTAREA') {
500             el.attr('spellcheck', false);
501         }
502     });
504     // check whether we've refreshed a page and browser remembered modified
505     // form values
506     var check_page_refresh = $('#check_page_refresh');
507     if (check_page_refresh.length === 0 || check_page_refresh.val() == '1') {
508         // run all field validators
509         var errors = {};
510         for (var i = 0; i < elements.length; i++) {
511             validate_field(elements[i], false, errors);
512         }
513         // run all fieldset validators
514         $('fieldset').each(function () {
515             validate_fieldset(this, false, errors);
516         });
518         displayErrors(errors);
519     } else if (check_page_refresh) {
520         check_page_refresh.val('1');
521     }
525 // END: Form validation and field operations
526 // ------------------------------------------------------------------
528 // ------------------------------------------------------------------
529 // Tabbed forms
533  * Sets active tab
535  * @param {String} tab_id
536  */
537 function setTab(tab_id)
539     $('ul.tabs li').removeClass('active').find('a[href=#' + tab_id + ']').parent().addClass('active');
540     $('div.tabs_contents fieldset').hide().filter('#' + tab_id).show();
541     location.hash = 'tab_' + tab_id;
542     $('form.config-form input[name=tab_hash]').val(location.hash);
545 AJAX.registerOnload('config.js', function () {
546     var tabs = $('ul.tabs');
547     if (!tabs.length) {
548         return;
549     }
550     // add tabs events and activate one tab (the first one or indicated by location hash)
551     tabs.find('a')
552         .click(function (e) {
553             e.preventDefault();
554             setTab($(this).attr('href').substr(1));
555         })
556         .filter(':first')
557         .parent()
558         .addClass('active');
559     $('div.tabs_contents fieldset').hide().filter(':first').show();
561     // tab links handling, check each 200ms
562     // (works with history in FF, further browser support here would be an overkill)
563     var prev_hash;
564     var tab_check_fnc = function () {
565         if (location.hash != prev_hash) {
566             prev_hash = location.hash;
567             if (location.hash.match(/^#tab_.+/) && $('#' + location.hash.substr(5)).length) {
568                 setTab(location.hash.substr(5));
569             }
570         }
571     };
572     tab_check_fnc();
573     setInterval(tab_check_fnc, 200);
577 // END: Tabbed forms
578 // ------------------------------------------------------------------
580 // ------------------------------------------------------------------
581 // Form reset buttons
584 AJAX.registerOnload('config.js', function () {
585     $('input[type=button][name=submit_reset]').click(function () {
586         var fields = $(this).closest('fieldset').find('input, select, textarea');
587         for (var i = 0, imax = fields.length; i < imax; i++) {
588             setFieldValue(fields[i], getFieldType(fields[i]));
589         }
590     });
594 // END: Form reset buttons
595 // ------------------------------------------------------------------
597 // ------------------------------------------------------------------
598 // "Restore default" and "set value" buttons
602  * Restores field's default value
604  * @param {String} field_id
605  */
606 function restoreField(field_id)
608     var field = $('#' + field_id);
609     if (field.length === 0 || defaultValues[field_id] === undefined) {
610         return;
611     }
612     setFieldValue(field, getFieldType(field), defaultValues[field_id]);
615 AJAX.registerOnload('config.js', function () {
616     $('div.tabs_contents')
617         .delegate('.restore-default, .set-value', 'mouseenter', function () {
618             $(this).css('opacity', 1);
619         })
620         .delegate('.restore-default, .set-value', 'mouseleave', function () {
621             $(this).css('opacity', 0.25);
622         })
623         .delegate('.restore-default, .set-value', 'click', function (e) {
624             e.preventDefault();
625             var href = $(this).attr('href');
626             var field_sel;
627             if ($(this).hasClass('restore-default')) {
628                 field_sel = href;
629                 restoreField(field_sel.substr(1));
630             } else {
631                 field_sel = href.match(/^[^=]+/)[0];
632                 var value = href.match(/\=(.+)$/)[1];
633                 setFieldValue($(field_sel), 'text', value);
634             }
635             $(field_sel).trigger('change');
636         })
637         .find('.restore-default, .set-value')
638         // inline-block for IE so opacity inheritance works
639         .css({display: 'inline-block', opacity: 0.25});
643 // END: "Restore default" and "set value" buttons
644 // ------------------------------------------------------------------
646 // ------------------------------------------------------------------
647 // User preferences import/export
650 AJAX.registerOnload('config.js', function () {
651     offerPrefsAutoimport();
652     var radios = $('#import_local_storage, #export_local_storage');
653     if (!radios.length) {
654         return;
655     }
657     // enable JavaScript dependent fields
658     radios
659         .prop('disabled', false)
660         .add('#export_text_file, #import_text_file')
661         .click(function () {
662             var enable_id = $(this).attr('id');
663             var disable_id;
664             if (enable_id.match(/local_storage$/)) {
665                 disable_id = enable_id.replace(/local_storage$/, 'text_file');
666             } else {
667                 disable_id = enable_id.replace(/text_file$/, 'local_storage');
668             }
669             $('#opts_' + disable_id).addClass('disabled').find('input').prop('disabled', true);
670             $('#opts_' + enable_id).removeClass('disabled').find('input').prop('disabled', false);
671         });
673     // detect localStorage state
674     var ls_supported = window.localStorage || false;
675     var ls_exists = ls_supported ? (window.localStorage.config || false) : false;
676     $('div.localStorage-' + (ls_supported ? 'un' : '') + 'supported').hide();
677     $('div.localStorage-' + (ls_exists ? 'empty' : 'exists')).hide();
678     if (ls_exists) {
679         updatePrefsDate();
680     }
681     $('form.prefs-form').change(function () {
682         var form = $(this);
683         var disabled = false;
684         if (!ls_supported) {
685             disabled = form.find('input[type=radio][value$=local_storage]').prop('checked');
686         } else if (!ls_exists && form.attr('name') == 'prefs_import' &&
687             $('#import_local_storage')[0].checked
688             ) {
689             disabled = true;
690         }
691         form.find('input[type=submit]').prop('disabled', disabled);
692     }).submit(function (e) {
693         var form = $(this);
694         if (form.attr('name') == 'prefs_export' && $('#export_local_storage')[0].checked) {
695             e.preventDefault();
696             // use AJAX to read JSON settings and save them
697             savePrefsToLocalStorage(form);
698         } else if (form.attr('name') == 'prefs_import' && $('#import_local_storage')[0].checked) {
699             // set 'json' input and submit form
700             form.find('input[name=json]').val(window.localStorage['config']);
701         }
702     });
704     $('div.click-hide-message').live('click', function () {
705         $(this)
706         .hide()
707         .parent('.group')
708         .css('height', '')
709         .next('form')
710         .show();
711     });
715  * Saves user preferences to localStorage
717  * @param {Element} form
718  */
719 function savePrefsToLocalStorage(form)
721     form = $(form);
722     var submit = form.find('input[type=submit]');
723     submit.prop('disabled', true);
724     $.ajax({
725         url: 'prefs_manage.php',
726         cache: false,
727         type: 'POST',
728         data: {
729             ajax_request: true,
730             token: form.find('input[name=token]').val(),
731             submit_get_json: true
732         },
733         success: function (response) {
734             window.localStorage['config'] = response.prefs;
735             window.localStorage['config_mtime'] = response.mtime;
736             window.localStorage['config_mtime_local'] = (new Date()).toUTCString();
737             updatePrefsDate();
738             $('div.localStorage-empty').hide();
739             $('div.localStorage-exists').show();
740             var group = form.parent('.group');
741             group.css('height', group.height() + 'px');
742             form.hide('fast');
743             form.prev('.click-hide-message').show('fast');
744         },
745         complete: function () {
746             submit.prop('disabled', false);
747         }
748     });
752  * Updates preferences timestamp in Import form
753  */
754 function updatePrefsDate()
756     var d = new Date(window.localStorage['config_mtime_local']);
757     var msg = PMA_messages.strSavedOn.replace(
758         '@DATE@',
759         PMA_formatDateTime(d)
760     );
761     $('#opts_import_local_storage div.localStorage-exists').html(msg);
765  * Prepares message which informs that localStorage preferences are available and can be imported
766  */
767 function offerPrefsAutoimport()
769     var has_config = (window.localStorage || false) && (window.localStorage['config'] || false);
770     var cnt = $('#prefs_autoload');
771     if (!cnt.length || !has_config) {
772         return;
773     }
774     cnt.find('a').click(function (e) {
775         e.preventDefault();
776         var a = $(this);
777         if (a.attr('href') == '#no') {
778             cnt.remove();
779             $.post('index.php', {
780                 token: cnt.find('input[name=token]').val(),
781                 prefs_autoload: 'hide'
782             });
783             return;
784         }
785         cnt.find('input[name=json]').val(window.localStorage['config']);
786         cnt.find('form').submit();
787     });
788     cnt.show();
792 // END: User preferences import/export
793 // ------------------------------------------------------------------