1 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 * Functions used in configuration forms and on user preferences pages
7 * Unbind all event handlers before tearing down a page
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');
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');
24 // default values for fields
25 var defaultValues = {};
30 * @param {Element} field
32 function getFieldType(field)
35 var tagName = field.prop('tagName');
36 if (tagName == 'INPUT') {
37 return field.attr('type');
38 } else if (tagName == 'SELECT') {
40 } else if (tagName == 'TEXTAREA') {
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'
55 * @param {Element} field
56 * @param {String} field_type see {@link #getFieldType}
57 * @param {String|Boolean} [value]
59 function setFieldValue(field, field_type, value)
64 //TODO: replace to .val()
65 field.attr('value', (value !== undefined ? value : field.attr('defaultValue')));
68 //TODO: replace to .prop()
69 field.attr('checked', (value !== undefined ? value : field.attr('defaultChecked')));
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;
79 for (i = 0; i < imax; i++) {
80 options[i].selected = (value.indexOf(options[i].value) != -1);
92 * o String - if type is 'text'
93 * o boolean - if type is 'checkbox'
94 * o Array of values - if type is 'select'
96 * @param {Element} field
97 * @param {String} field_type returned by {@link #getFieldType}
98 * @type Boolean|String|String[]
100 function getFieldValue(field, field_type)
103 switch (field_type) {
105 return field.prop('value');
107 return field.prop('checked');
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);
122 * Returns values for all fields in fieldsets
124 function getAllValues()
126 var elements = $('fieldset input, fieldset select, fieldset textarea');
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') {
137 values[elements[i].name] = value;
144 * Checks whether field has its default value
146 * @param {Element} field
147 * @param {String} type
150 function checkFieldDefault(field, type)
153 var field_id = field.attr('id');
154 if (typeof defaultValues[field_id] == 'undefined') {
157 var isDefault = true;
158 var currentValue = getFieldValue(field, type);
159 if (type != 'select') {
160 isDefault = currentValue == defaultValues[field_id];
162 // compare arrays, will work for our representation of select values
163 if (currentValue.length != defaultValues[field_id].length) {
167 for (var i = 0; i < currentValue.length; i++) {
168 if (currentValue[i] != defaultValues[field_id][i]) {
179 * Returns element's id prefix
180 * @param {Element} element
182 function getIdPrefix(element)
184 return $(element).attr('id').replace(/[^-]+$/, '');
187 // ------------------------------------------------------------------
188 // Form validation and field operations
191 // form validator assignments
194 // form validator list
196 // regexp: numeric value
197 _regexp_numeric: /^[0-9]+$/,
198 // regexp: extract parts from PCRE expression
199 _regexp_pcre_extract: /(.)(.*)\1(.*)?/,
201 * Validates positive number
203 * @param {boolean} isKeyUp
205 PMA_validatePositiveNumber: function (isKeyUp) {
206 if (isKeyUp && this.value === '') {
209 var result = this.value != '0' && validators._regexp_numeric.test(this.value);
210 return result ? true : PMA_messages.error_nan_p;
213 * Validates non-negative number
215 * @param {boolean} isKeyUp
217 PMA_validateNonNegativeNumber: function (isKeyUp) {
218 if (isKeyUp && this.value === '') {
221 var result = validators._regexp_numeric.test(this.value);
222 return result ? true : PMA_messages.error_nan_nneg;
225 * Validates port number
227 * @param {boolean} isKeyUp
229 PMA_validatePortNumber: function (isKeyUp) {
230 if (this.value === '') {
233 var result = validators._regexp_numeric.test(this.value) && this.value != '0';
234 return result && this.value <= 65535 ? true : PMA_messages.error_incorrect_port;
237 * Validates value according to given regular expression
239 * @param {boolean} isKeyUp
240 * @param {string} regexp
242 PMA_validateByRegex: function (isKeyUp, regexp) {
243 if (isKeyUp && this.value === '') {
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;
252 * Validates upper bound for numeric inputs
254 * @param {boolean} isKeyUp
255 * @param {int} max_value
257 PMA_validateUpperBound: function (isKeyUp, max_value) {
258 var val = parseInt(this.value, 10);
262 return val <= max_value ? true : $.sprintf(PMA_messages.error_value_lte, max_value);
267 // fieldset validators
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
280 function validateField(id, type, onKeyUp, params)
282 if (typeof validators[type] == 'undefined') {
285 if (typeof validate[id] == 'undefined') {
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
297 * @return array of [function, paramseters to be passed to function]
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]];
307 // look for registered validators
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]) {
315 functions.push([validators[validate[field_id][i][0]], validate[field_id][i][1]]);
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}
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';
338 errorCnt = field.find('dl.errors');
340 errorCnt = field.siblings('.inline_errors');
343 // remove empty errors (used to clear error list)
344 errors = $.grep(errors, function (item) {
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');
356 // if error container doesn't exist, create it
357 if (errorCnt.length === 0) {
359 errorCnt = $('<dl class="errors" />');
360 field.find('table').before(errorCnt);
362 errorCnt = $('<dl class="inline_errors" />');
363 field.closest('td').append(errorCnt);
368 for (var i = 0, imax = errors.length; i < imax; i++) {
369 html += '<dd>' + errors[i] + '</dd>';
372 } else if (errorCnt !== null) {
373 // remove useless error container
380 * Validates fieldset and puts errors in 'errors' object
382 * @param {Element} fieldset
383 * @param {boolean} isKeyUp
384 * @param {Object} errors
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] = [];
395 if (typeof fieldset_errors[field_id] == 'string') {
396 fieldset_errors[field_id] = [fieldset_errors[field_id]];
398 $.merge(errors[field_id], fieldset_errors[field_id]);
404 * Validates form field and puts errors in 'errors' object
406 * @param {Element} field
407 * @param {boolean} isKeyUp
408 * @param {Object} errors
410 function validate_field(field, isKeyUp, errors)
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);
423 args.unshift(isKeyUp);
424 result = functions[i][0].apply(field[0], args);
425 if (result !== true) {
426 if (typeof result == 'string') {
429 $.merge(errors[field_id], result);
435 * Validates form field and parent fieldset
437 * @param {Element} field
438 * @param {boolean} isKeyUp
440 function validate_field_and_fieldset(field, isKeyUp)
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
454 function markField(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
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 () {
484 el.bind('change', function () {
485 validate_field_and_fieldset(this, false);
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);
496 // disable textarea spellcheck
497 if (tagName == 'TEXTAREA') {
498 el.attr('spellcheck', false);
502 // check whether we've refreshed a page and browser remembered modified
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
508 for (var i = 0; i < elements.length; i++) {
509 validate_field(elements[i], false, errors);
511 // run all fieldset validators
512 $('fieldset').each(function () {
513 validate_fieldset(this, false, errors);
516 displayErrors(errors);
517 } else if (check_page_refresh) {
518 check_page_refresh.val('1');
523 // END: Form validation and field operations
524 // ------------------------------------------------------------------
526 // ------------------------------------------------------------------
533 * @param {String} tab_id
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');
548 // add tabs events and activate one tab (the first one or indicated by location hash)
550 .click(function (e) {
552 setTab($(this).attr('href').substr(1));
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)
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));
571 setInterval(tab_check_fnc, 200);
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]));
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
604 function restoreField(field_id)
606 var field = $('#' + field_id);
607 if (field.length === 0 || defaultValues[field_id] === undefined) {
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);
618 .delegate('.restore-default, .set-value', 'mouseleave', function () {
619 $(this).css('opacity', 0.25);
621 .delegate('.restore-default, .set-value', 'click', function (e) {
623 var href = $(this).attr('href');
625 if ($(this).hasClass('restore-default')) {
627 restoreField(field_sel.substr(1));
629 field_sel = href.match(/^[^=]+/)[0];
630 var value = href.match(/\=(.+)$/)[1];
631 setFieldValue($(field_sel), 'text', value);
633 $(field_sel).trigger('change');
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) {
655 // enable JavaScript dependent fields
657 .prop('disabled', false)
658 .add('#export_text_file, #import_text_file')
660 var enable_id = $(this).attr('id');
662 if (enable_id.match(/local_storage$/)) {
663 disable_id = enable_id.replace(/local_storage$/, 'text_file');
665 disable_id = enable_id.replace(/text_file$/, 'local_storage');
667 $('#opts_' + disable_id).addClass('disabled').find('input').prop('disabled', true);
668 $('#opts_' + enable_id).removeClass('disabled').find('input').prop('disabled', false);
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();
679 $('form.prefs-form').change(function () {
681 var disabled = false;
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) {
688 form.find('input[type=submit]').prop('disabled', disabled);
689 }).submit(function (e) {
691 if (form.attr('name') == 'prefs_export' && $('#export_local_storage')[0].checked) {
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']);
701 $('div.click-hide-message').live('click', function () {
712 * Saves user preferences to localStorage
714 * @param {Element} form
716 function savePrefsToLocalStorage(form)
719 var submit = form.find('input[type=submit]');
720 submit.prop('disabled', true);
722 url: 'prefs_manage.php',
727 token: form.find('input[name=token]').val(),
728 submit_get_json: true
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();
735 $('div.localStorage-empty').hide();
736 $('div.localStorage-exists').show();
737 var group = form.parent('.group');
738 group.css('height', group.height() + 'px');
740 form.prev('.click-hide-message').show('fast');
742 complete: function () {
743 submit.prop('disabled', false);
749 * Updates preferences timestamp in Import form
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
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
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) {
782 cnt.find('a').click(function (e) {
785 if (a.attr('href') == '#no') {
787 $.post('index.php', {
788 token: cnt.find('input[name=token]').val(),
789 prefs_autoload: 'hide'
793 cnt.find('input[name=json]').val(window.localStorage['config']);
794 cnt.find('form').submit();
800 // END: User preferences import/export
801 // ------------------------------------------------------------------