Fixed: Not selecting a datalabel used to issue a notice(undefined offset)
[phpmyadmin/ammaryasirr.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
14  *
15  * @param {Element} field
16  */
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';
27     }
28     return '';
31 /**
32  * Sets field value
33  *
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'
39  *
40  * @param {Element} field
41  * @param {String}  field_type  see {@link #getFieldType}
42  * @param {String|Boolean}  [value]
43  */
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;
60                 }
61             } else {
62                 for (i = 0; i < imax; i++) {
63                     options[i].selected = (value.indexOf(options[i].value) != -1);
64                 }
65             }
66             break;
67     }
68     markField(field);
71 /**
72  * Gets field value
73  *
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'
78  *
79  * @param {Element} field
80  * @param {String}  field_type returned by {@link #getFieldType}
81  * @type Boolean|String|String[]
82  */
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);
97                 }
98             }
99             return items;
100     }
101     return null;
105  * Returns values for all fields in fieldsets
106  */
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];
119             }
120             values[elements[i].name] = value;
121         }
122     }
123     return values;
127  * Checks whether field has its default value
129  * @param {Element} field
130  * @param {String}  type
131  * @return boolean
132  */
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;
139     }
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;
148         }
149         else {
150             for (var i = 0; i < currentValue.length; i++) {
151                 if (currentValue[i] != defaultValues[field_id][i]) {
152                     isDefault = false;
153                     break;
154                 }
155             }
156         }
157     }
158     return isDefault;
162  * Returns element's id prefix
163  * @param {Element} element
164  */
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(.*)?/,
183     /**
184      * Validates positive number
185      *
186      * @param {boolean} isKeyUp
187      */
188     validate_positive_number: function (isKeyUp) {
189         if (isKeyUp && this.value == '') {
190             return true;
191         }
192         var result = this.value != '0' && validators._regexp_numeric.test(this.value);
193         return result ? true : PMA_messages['error_nan_p'];
194     },
195     /**
196      * Validates non-negative number
197      *
198      * @param {boolean} isKeyUp
199      */
200     validate_non_negative_number: function (isKeyUp) {
201         if (isKeyUp && this.value == '') {
202             return true;
203         }
204         var result = validators._regexp_numeric.test(this.value);
205         return result ? true : PMA_messages['error_nan_nneg'];
206     },
207     /**
208      * Validates port number
209      *
210      * @param {boolean} isKeyUp
211      */
212     validate_port_number: function(isKeyUp) {
213         if (this.value == '') {
214             return true;
215         }
216         var result = validators._regexp_numeric.test(this.value) && this.value != '0';
217         return result && this.value <= 65535 ? true : PMA_messages['error_incorrect_port'];
218     },
219     /**
220      * Validates value according to given regular expression
221      *
222      * @param {boolean} isKeyUp
223      * @param {string}  regexp
224      */
225     validate_by_regex: function(isKeyUp, regexp) {
226         if (isKeyUp && this.value == '') {
227             return true;
228         }
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'];
233     },
234     /**
235      * Validates upper bound for numeric inputs
236      *
237      * @param {boolean} isKeyUp
238      * @param {int} max_value
239      */
240     validate_upper_bound: function(isKeyUp, max_value) {
241         var val = parseInt(this.value);
242         if (isNaN(val)) {
243             return true;
244         }
245         return val <= max_value ? true : PMA_messages['error_value_lte'].replace('%s', max_value);
246     },
247     // field validators
248     _field: {
249     },
250     // fieldset validators
251     _fieldset: {
252     }
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
262  */
263 function validateField(id, type, onKeyUp, params)
265     if (typeof validators[type] == 'undefined') {
266         return;
267     }
268     if (typeof validate[id] == 'undefined') {
269         validate[id] = [];
270     }
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]
281  */
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]];
288     }
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;
297             }
298             functions.push([validators[validate[field_id][i][0]], validate[field_id][i][1]]);
299         }
300     }
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}
312  */
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 != '';
326         });
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');
333         }
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);
344                 }
345             }
347             var html = '';
348             for (var i = 0, imax = errors.length; i < imax; i++) {
349                 html += '<dd>' + errors[i] + '</dd>';
350             }
351             errorCnt.html(html);
352         } else if (errorCnt !== null) {
353             // remove useless error container
354             errorCnt.remove();
355         }
356     }
360  * Validates fieldset and puts errors in 'errors' object
362  * @param {Element} fieldset
363  * @param {boolean} isKeyUp
364  * @param {Object}  errors
365  */
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] = [];
374             }
375             if (typeof fieldset_errors[field_id] == 'string') {
376                 fieldset_errors[field_id] = [fieldset_errors[field_id]];
377             }
378             $.merge(errors[field_id], fieldset_errors[field_id]);
379         }
380     }
384  * Validates form field and puts errors in 'errors' object
386  * @param {Element} field
387  * @param {boolean} isKeyUp
388  * @param {Object}  errors
389  */
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];
405             }
406             $.merge(errors[field_id], result);
407         }
408     }
412  * Validates form field and parent fieldset
414  * @param {Element} field
415  * @param {boolean} isKeyUp
416  */
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
430  */
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
448  */
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);
464         });
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);
471             });
472         }
473         // disable textarea spellcheck
474         if (tagName == 'TEXTAREA') {
475            el.attr('spellcheck', false);
476         }
477     });
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);
487         }
488         // run all fieldset validators
489         $('fieldset').each(function(){
490             validate_fieldset(this, false, errors);
491         });
493         displayErrors(errors);
494     } else if (check_page_refresh) {
495         check_page_refresh.val('1');
496     }
500 // END: Form validation and field operations
501 // ------------------------------------------------------------------
503 // ------------------------------------------------------------------
504 // Tabbed forms
508  * Sets active tab
510  * @param {String} tab_id
511  */
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;
524     }
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'));
530         })
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));
543             }
544         }
545     };
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]));
563         }
564     });
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
579  */
580 function restoreField(field_id)
582     var field = $('#'+field_id);
583     if (field.length == 0 || defaultValues[field_id] == undefined) {
584         return;
585     }
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);
604             }
605             $(field_sel).trigger('change');
606         })
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;
625     }
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);
638         });
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();
647     }
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;
656         }
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']);
667         }
668     });
670     $('.click-hide-message').live('click', function(){
671         var div = $(this);
672         div.hide().parent('.group').css('height', '');
673         div.next('form').show();
674     });
678  * Saves user preferences to localStorage
680  * @param {Element} form
681  */
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
694         },
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');
706         },
707         complete: function() {
708             submit.attr('disabled', false);
709         }
710     });
714  * Updates preferences timestamp in Import form
715  */
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
727  */
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
739  */
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;
746     }
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;
756         }
757         cnt.find('input[name=json]').val(window.localStorage['config']);
758         cnt.find('form').submit();
759     });
760     cnt.show();
764 // END: User preferences import/export
765 // ------------------------------------------------------------------