Merge pull request #431 from xmujay/0609_monitor
[phpmyadmin/aamir.git] / js / config.js
blobb7115a6e946c4ade85b6e008c2bafefe3bd3ed14
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         //TODO: replace to .val()
65         field.attr('value', (value !== undefined ? value : field.attr('defaultValue')));
66         break;
67     case 'checkbox':
68         //TODO: replace to .prop()
69         field.attr('checked', (value !== undefined ? value : field.attr('defaultChecked')));
70         break;
71     case 'select':
72         var options = field.prop('options');
73         var i, imax = options.length;
74         if (value === undefined) {
75             for (i = 0; i < imax; i++) {
76                 options[i].selected = options[i].defaultSelected;
77             }
78         } else {
79             for (i = 0; i < imax; i++) {
80                 options[i].selected = (value.indexOf(options[i].value) != -1);
81             }
82         }
83         break;
84     }
85     markField(field);
88 /**
89  * Gets field value
90  *
91  * Will return one of:
92  * o String - if type is 'text'
93  * o boolean - if type is 'checkbox'
94  * o Array of values - if type is 'select'
95  *
96  * @param {Element} field
97  * @param {String}  field_type returned by {@link #getFieldType}
98  * @type Boolean|String|String[]
99  */
100 function getFieldValue(field, field_type)
102     field = $(field);
103     switch (field_type) {
104     case 'text':
105         return field.prop('value');
106     case 'checkbox':
107         return field.prop('checked');
108     case 'select':
109         var options = field.prop('options');
110         var i, imax = options.length, items = [];
111         for (i = 0; i < imax; i++) {
112             if (options[i].selected) {
113                 items.push(options[i].value);
114             }
115         }
116         return items;
117     }
118     return null;
122  * Returns values for all fields in fieldsets
123  */
124 function getAllValues()
126     var elements = $('fieldset input, fieldset select, fieldset textarea');
127     var values = {};
128     var type, value;
129     for (var i = 0; i < elements.length; i++) {
130         type = getFieldType(elements[i]);
131         value = getFieldValue(elements[i], type);
132         if (typeof value != 'undefined') {
133             // we only have single selects, fatten array
134             if (type == 'select') {
135                 value = value[0];
136             }
137             values[elements[i].name] = value;
138         }
139     }
140     return values;
144  * Checks whether field has its default value
146  * @param {Element} field
147  * @param {String}  type
148  * @return boolean
149  */
150 function checkFieldDefault(field, type)
152     field = $(field);
153     var field_id = field.attr('id');
154     if (typeof defaultValues[field_id] == 'undefined') {
155         return true;
156     }
157     var isDefault = true;
158     var currentValue = getFieldValue(field, type);
159     if (type != 'select') {
160         isDefault = currentValue == defaultValues[field_id];
161     } else {
162         // compare arrays, will work for our representation of select values
163         if (currentValue.length != defaultValues[field_id].length) {
164             isDefault = false;
165         }
166         else {
167             for (var i = 0; i < currentValue.length; i++) {
168                 if (currentValue[i] != defaultValues[field_id][i]) {
169                     isDefault = false;
170                     break;
171                 }
172             }
173         }
174     }
175     return isDefault;
179  * Returns element's id prefix
180  * @param {Element} element
181  */
182 function getIdPrefix(element)
184     return $(element).attr('id').replace(/[^-]+$/, '');
187 // ------------------------------------------------------------------
188 // Form validation and field operations
191 // form validator assignments
192 var validate = {};
194 // form validator list
195 var validators = {
196     // regexp: numeric value
197     _regexp_numeric: /^[0-9]+$/,
198     // regexp: extract parts from PCRE expression
199     _regexp_pcre_extract: /(.)(.*)\1(.*)?/,
200     /**
201      * Validates positive number
202      *
203      * @param {boolean} isKeyUp
204      */
205     PMA_validatePositiveNumber: function (isKeyUp) {
206         if (isKeyUp && this.value === '') {
207             return true;
208         }
209         var result = this.value != '0' && validators._regexp_numeric.test(this.value);
210         return result ? true : PMA_messages.error_nan_p;
211     },
212     /**
213      * Validates non-negative number
214      *
215      * @param {boolean} isKeyUp
216      */
217     PMA_validateNonNegativeNumber: function (isKeyUp) {
218         if (isKeyUp && this.value === '') {
219             return true;
220         }
221         var result = validators._regexp_numeric.test(this.value);
222         return result ? true : PMA_messages.error_nan_nneg;
223     },
224     /**
225      * Validates port number
226      *
227      * @param {boolean} isKeyUp
228      */
229     PMA_validatePortNumber: function (isKeyUp) {
230         if (this.value === '') {
231             return true;
232         }
233         var result = validators._regexp_numeric.test(this.value) && this.value != '0';
234         return result && this.value <= 65535 ? true : PMA_messages.error_incorrect_port;
235     },
236     /**
237      * Validates value according to given regular expression
238      *
239      * @param {boolean} isKeyUp
240      * @param {string}  regexp
241      */
242     PMA_validateByRegex: function (isKeyUp, regexp) {
243         if (isKeyUp && this.value === '') {
244             return true;
245         }
246         // convert PCRE regexp
247         var parts = regexp.match(validators._regexp_pcre_extract);
248         var valid = this.value.match(new RegExp(parts[2], parts[3])) !== null;
249         return valid ? true : PMA_messages.error_invalid_value;
250     },
251     /**
252      * Validates upper bound for numeric inputs
253      *
254      * @param {boolean} isKeyUp
255      * @param {int} max_value
256      */
257     PMA_validateUpperBound: function (isKeyUp, max_value) {
258         var val = parseInt(this.value, 10);
259         if (isNaN(val)) {
260             return true;
261         }
262         return val <= max_value ? true : $.sprintf(PMA_messages.error_value_lte, max_value);
263     },
264     // field validators
265     _field: {
266     },
267     // fieldset validators
268     _fieldset: {
269     }
273  * Registers validator for given field
275  * @param {String}  id       field id
276  * @param {String}  type     validator (key in validators object)
277  * @param {boolean} onKeyUp  whether fire on key up
278  * @param {Array}   params   validation function parameters
279  */
280 function validateField(id, type, onKeyUp, params)
282     if (typeof validators[type] == 'undefined') {
283         return;
284     }
285     if (typeof validate[id] == 'undefined') {
286         validate[id] = [];
287     }
288     validate[id].push([type, params, onKeyUp]);
292  * Returns valdiation functions associated with form field
294  * @param {String}  field_id     form field id
295  * @param {boolean} onKeyUpOnly  see validateField
296  * @type Array
297  * @return array of [function, paramseters to be passed to function]
298  */
299 function getFieldValidators(field_id, onKeyUpOnly)
301     // look for field bound validator
302     var name = field_id.match(/[^-]+$/)[0];
303     if (typeof validators._field[name] != 'undefined') {
304         return [[validators._field[name], null]];
305     }
307     // look for registered validators
308     var functions = [];
309     if (typeof validate[field_id] != 'undefined') {
310         // validate[field_id]: array of [type, params, onKeyUp]
311         for (var i = 0, imax = validate[field_id].length; i < imax; i++) {
312             if (onKeyUpOnly && !validate[field_id][i][2]) {
313                 continue;
314             }
315             functions.push([validators[validate[field_id][i][0]], validate[field_id][i][1]]);
316         }
317     }
319     return functions;
323  * Displays errors for given form fields
325  * WARNING: created DOM elements must be identical with the ones made by
326  * display_input() in FormDisplay.tpl.php!
328  * @param {Object} error_list list of errors in the form {field id: error array}
329  */
330 function displayErrors(error_list)
332     for (var field_id in error_list) {
333         var errors = error_list[field_id];
334         var field = $('#' + field_id);
335         var isFieldset = field.attr('tagName') == 'FIELDSET';
336         var errorCnt;
337         if (isFieldset) {
338             errorCnt = field.find('dl.errors');
339         } else {
340             errorCnt = field.siblings('.inline_errors');
341         }
343         // remove empty errors (used to clear error list)
344         errors = $.grep(errors, function (item) {
345             return item !== '';
346         });
348         // CSS error class
349         if (!isFieldset) {
350             // checkboxes uses parent <span> for marking
351             var fieldMarker = (field.attr('type') == 'checkbox') ? field.parent() : field;
352             fieldMarker[errors.length ? 'addClass' : 'removeClass']('field-error');
353         }
355         if (errors.length) {
356             // if error container doesn't exist, create it
357             if (errorCnt.length === 0) {
358                 if (isFieldset) {
359                     errorCnt = $('<dl class="errors" />');
360                     field.find('table').before(errorCnt);
361                 } else {
362                     errorCnt = $('<dl class="inline_errors" />');
363                     field.closest('td').append(errorCnt);
364                 }
365             }
367             var html = '';
368             for (var i = 0, imax = errors.length; i < imax; i++) {
369                 html += '<dd>' + errors[i] + '</dd>';
370             }
371             errorCnt.html(html);
372         } else if (errorCnt !== null) {
373             // remove useless error container
374             errorCnt.remove();
375         }
376     }
380  * Validates fieldset and puts errors in 'errors' object
382  * @param {Element} fieldset
383  * @param {boolean} isKeyUp
384  * @param {Object}  errors
385  */
386 function validate_fieldset(fieldset, isKeyUp, errors)
388     fieldset = $(fieldset);
389     if (fieldset.length && typeof validators._fieldset[fieldset.attr('id')] != 'undefined') {
390         var fieldset_errors = validators._fieldset[fieldset.attr('id')].apply(fieldset[0], [isKeyUp]);
391         for (var field_id in fieldset_errors) {
392             if (typeof errors[field_id] == 'undefined') {
393                 errors[field_id] = [];
394             }
395             if (typeof fieldset_errors[field_id] == 'string') {
396                 fieldset_errors[field_id] = [fieldset_errors[field_id]];
397             }
398             $.merge(errors[field_id], fieldset_errors[field_id]);
399         }
400     }
404  * Validates form field and puts errors in 'errors' object
406  * @param {Element} field
407  * @param {boolean} isKeyUp
408  * @param {Object}  errors
409  */
410 function validate_field(field, isKeyUp, errors)
412     var args, result;
413     field = $(field);
414     var field_id = field.attr('id');
415     errors[field_id] = [];
416     var functions = getFieldValidators(field_id, isKeyUp);
417     for (var i = 0; i < functions.length; i++) {
418         if (functions[i][1] !== null) {
419             args = functions[i][1].slice(0);
420         } else {
421             args = [];
422         }
423         args.unshift(isKeyUp);
424         result = functions[i][0].apply(field[0], args);
425         if (result !== true) {
426             if (typeof result == 'string') {
427                 result = [result];
428             }
429             $.merge(errors[field_id], result);
430         }
431     }
435  * Validates form field and parent fieldset
437  * @param {Element} field
438  * @param {boolean} isKeyUp
439  */
440 function validate_field_and_fieldset(field, isKeyUp)
442     field = $(field);
443     var errors = {};
444     validate_field(field, isKeyUp, errors);
445     validate_fieldset(field.closest('fieldset'), isKeyUp, errors);
446     displayErrors(errors);
450  * Marks field depending on its value (system default or custom)
452  * @param {Element} field
453  */
454 function markField(field)
456     field = $(field);
457     var type = getFieldType(field);
458     var isDefault = checkFieldDefault(field, type);
460     // checkboxes uses parent <span> for marking
461     var fieldMarker = (type == 'checkbox') ? field.parent() : field;
462     setRestoreDefaultBtn(field, !isDefault);
463     fieldMarker[isDefault ? 'removeClass' : 'addClass']('custom');
467  * Enables or disables the "restore default value" button
469  * @param {Element} field
470  * @param {boolean} display
471  */
472 function setRestoreDefaultBtn(field, display)
474     var el = $(field).closest('td').find('.restore-default img');
475     el[display ? 'show' : 'hide']();
478 AJAX.registerOnload('config.js', function () {
479     // register validators and mark custom values
480     var elements = $('input[id], select[id], textarea[id]');
481     $('input[id], select[id], textarea[id]').each(function () {
482         markField(this);
483         var el = $(this);
484         el.bind('change', function () {
485             validate_field_and_fieldset(this, false);
486             markField(this);
487         });
488         var tagName = el.attr('tagName');
489         // text fields can be validated after each change
490         if (tagName == 'INPUT' && el.attr('type') == 'text') {
491             el.keyup(function () {
492                 validate_field_and_fieldset(el, true);
493                 markField(el);
494             });
495         }
496         // disable textarea spellcheck
497         if (tagName == 'TEXTAREA') {
498             el.attr('spellcheck', false);
499         }
500     });
502     // check whether we've refreshed a page and browser remembered modified
503     // form values
504     var check_page_refresh = $('#check_page_refresh');
505     if (check_page_refresh.length === 0 || check_page_refresh.val() == '1') {
506         // run all field validators
507         var errors = {};
508         for (var i = 0; i < elements.length; i++) {
509             validate_field(elements[i], false, errors);
510         }
511         // run all fieldset validators
512         $('fieldset').each(function () {
513             validate_fieldset(this, false, errors);
514         });
516         displayErrors(errors);
517     } else if (check_page_refresh) {
518         check_page_refresh.val('1');
519     }
523 // END: Form validation and field operations
524 // ------------------------------------------------------------------
526 // ------------------------------------------------------------------
527 // Tabbed forms
531  * Sets active tab
533  * @param {String} tab_id
534  */
535 function setTab(tab_id)
537     $('ul.tabs li').removeClass('active').find('a[href=#' + tab_id + ']').parent().addClass('active');
538     $('div.tabs_contents fieldset').hide().filter('#' + tab_id).show();
539     location.hash = 'tab_' + tab_id;
540     $('form.config-form input[name=tab_hash]').val(location.hash);
543 AJAX.registerOnload('config.js', function () {
544     var tabs = $('ul.tabs');
545     if (!tabs.length) {
546         return;
547     }
548     // add tabs events and activate one tab (the first one or indicated by location hash)
549     tabs.find('a')
550         .click(function (e) {
551             e.preventDefault();
552             setTab($(this).attr('href').substr(1));
553         })
554         .filter(':first')
555         .parent()
556         .addClass('active');
557     $('div.tabs_contents fieldset').hide().filter(':first').show();
559     // tab links handling, check each 200ms
560     // (works with history in FF, further browser support here would be an overkill)
561     var prev_hash;
562     var tab_check_fnc = function () {
563         if (location.hash != prev_hash) {
564             prev_hash = location.hash;
565             if (location.hash.match(/^#tab_.+/) && $('#' + location.hash.substr(5)).length) {
566                 setTab(location.hash.substr(5));
567             }
568         }
569     };
570     tab_check_fnc();
571     setInterval(tab_check_fnc, 200);
575 // END: Tabbed forms
576 // ------------------------------------------------------------------
578 // ------------------------------------------------------------------
579 // Form reset buttons
582 AJAX.registerOnload('config.js', function () {
583     $('input[type=button][name=submit_reset]').click(function () {
584         var fields = $(this).closest('fieldset').find('input, select, textarea');
585         for (var i = 0, imax = fields.length; i < imax; i++) {
586             setFieldValue(fields[i], getFieldType(fields[i]));
587         }
588     });
592 // END: Form reset buttons
593 // ------------------------------------------------------------------
595 // ------------------------------------------------------------------
596 // "Restore default" and "set value" buttons
600  * Restores field's default value
602  * @param {String} field_id
603  */
604 function restoreField(field_id)
606     var field = $('#' + field_id);
607     if (field.length === 0 || defaultValues[field_id] === undefined) {
608         return;
609     }
610     setFieldValue(field, getFieldType(field), defaultValues[field_id]);
613 AJAX.registerOnload('config.js', function () {
614     $('div.tabs_contents')
615         .delegate('.restore-default, .set-value', 'mouseenter', function () {
616             $(this).css('opacity', 1);
617         })
618         .delegate('.restore-default, .set-value', 'mouseleave', function () {
619             $(this).css('opacity', 0.25);
620         })
621         .delegate('.restore-default, .set-value', 'click', function (e) {
622             e.preventDefault();
623             var href = $(this).attr('href');
624             var field_sel;
625             if ($(this).hasClass('restore-default')) {
626                 field_sel = href;
627                 restoreField(field_sel.substr(1));
628             } else {
629                 field_sel = href.match(/^[^=]+/)[0];
630                 var value = href.match(/\=(.+)$/)[1];
631                 setFieldValue($(field_sel), 'text', value);
632             }
633             $(field_sel).trigger('change');
634         })
635         .find('.restore-default, .set-value')
636         // inline-block for IE so opacity inheritance works
637         .css({display: 'inline-block', opacity: 0.25});
641 // END: "Restore default" and "set value" buttons
642 // ------------------------------------------------------------------
644 // ------------------------------------------------------------------
645 // User preferences import/export
648 AJAX.registerOnload('config.js', function () {
649     offerPrefsAutoimport();
650     var radios = $('#import_local_storage, #export_local_storage');
651     if (!radios.length) {
652         return;
653     }
655     // enable JavaScript dependent fields
656     radios
657         .prop('disabled', false)
658         .add('#export_text_file, #import_text_file')
659         .click(function () {
660             var enable_id = $(this).attr('id');
661             var disable_id;
662             if (enable_id.match(/local_storage$/)) {
663                 disable_id = enable_id.replace(/local_storage$/, 'text_file');
664             } else {
665                 disable_id = enable_id.replace(/text_file$/, 'local_storage');
666             }
667             $('#opts_' + disable_id).addClass('disabled').find('input').prop('disabled', true);
668             $('#opts_' + enable_id).removeClass('disabled').find('input').prop('disabled', false);
669         });
671     // detect localStorage state
672     var ls_supported = window.localStorage || false;
673     var ls_exists = ls_supported ? (window.localStorage.config || false) : false;
674     $('div.localStorage-' + (ls_supported ? 'un' : '') + 'supported').hide();
675     $('div.localStorage-' + (ls_exists ? 'empty' : 'exists')).hide();
676     if (ls_exists) {
677         updatePrefsDate();
678     }
679     $('form.prefs-form').change(function () {
680         var form = $(this);
681         var disabled = false;
682         if (!ls_supported) {
683             disabled = form.find('input[type=radio][value$=local_storage]').prop('checked');
684         } else if (!ls_exists && form.attr('name') == 'prefs_import'
685                 && $('#import_local_storage')[0].checked) {
686             disabled = true;
687         }
688         form.find('input[type=submit]').prop('disabled', disabled);
689     }).submit(function (e) {
690         var form = $(this);
691         if (form.attr('name') == 'prefs_export' && $('#export_local_storage')[0].checked) {
692             e.preventDefault();
693             // use AJAX to read JSON settings and save them
694             savePrefsToLocalStorage(form);
695         } else if (form.attr('name') == 'prefs_import' && $('#import_local_storage')[0].checked) {
696             // set 'json' input and submit form
697             form.find('input[name=json]').val(window.localStorage['config']);
698         }
699     });
701     $('div.click-hide-message').live('click', function () {
702         $(this)
703         .hide()
704         .parent('.group')
705         .css('height', '')
706         .next('form')
707         .show();
708     });
712  * Saves user preferences to localStorage
714  * @param {Element} form
715  */
716 function savePrefsToLocalStorage(form)
718     form = $(form);
719     var submit = form.find('input[type=submit]');
720     submit.prop('disabled', true);
721     $.ajax({
722         url: 'prefs_manage.php',
723         cache: false,
724         type: 'POST',
725         data: {
726             ajax_request: true,
727             token: form.find('input[name=token]').val(),
728             submit_get_json: true
729         },
730         success: function (response) {
731             window.localStorage['config'] = response.prefs;
732             window.localStorage['config_mtime'] = response.mtime;
733             window.localStorage['config_mtime_local'] = (new Date()).toUTCString();
734             updatePrefsDate();
735             $('div.localStorage-empty').hide();
736             $('div.localStorage-exists').show();
737             var group = form.parent('.group');
738             group.css('height', group.height() + 'px');
739             form.hide('fast');
740             form.prev('.click-hide-message').show('fast');
741         },
742         complete: function () {
743             submit.prop('disabled', false);
744         }
745     });
749  * Updates preferences timestamp in Import form
750  */
751 function updatePrefsDate()
753     var d = new Date(window.localStorage['config_mtime_local']);
754     var msg = PMA_messages.strSavedOn.replace('@DATE@', formatDate(d));
755     $('#opts_import_local_storage div.localStorage-exists').html(msg);
759  * Returns date formatted as YYYY-MM-DD HH:II
761  * @param {Date} d
762  */
763 function formatDate(d)
765     return d.getFullYear() + '-' +
766         (d.getMonth() < 10 ? '0' + d.getMonth() : d.getMonth()) +
767         '-' + (d.getDate() < 10 ? '0' + d.getDate() : d.getDate()) +
768         ' ' + (d.getHours() < 10 ? '0' + d.getHours() : d.getHours()) +
769         ':' + (d.getMinutes() < 10 ? '0' + d.getMinutes() : d.getMinutes());
773  * Prepares message which informs that localStorage preferences are available and can be imported
774  */
775 function offerPrefsAutoimport()
777     var has_config = (window.localStorage || false) && (window.localStorage['config'] || false);
778     var cnt = $('#prefs_autoload');
779     if (!cnt.length || !has_config) {
780         return;
781     }
782     cnt.find('a').click(function (e) {
783         e.preventDefault();
784         var a = $(this);
785         if (a.attr('href') == '#no') {
786             cnt.remove();
787             $.post('index.php', {
788                 token: cnt.find('input[name=token]').val(),
789                 prefs_autoload: 'hide'
790             });
791             return;
792         }
793         cnt.find('input[name=json]').val(window.localStorage['config']);
794         cnt.find('form').submit();
795     });
796     cnt.show();
800 // END: User preferences import/export
801 // ------------------------------------------------------------------