1 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 * general function, usually for data manipulation pages
8 * @var sql_box_locked lock for the sqlbox textarea in the querybox
10 var sql_box_locked = false;
13 * @var array holds elements which content should only selected once
15 var only_once_elements = [];
18 * @var int ajax_message_count Number of AJAX messages shown since page load
20 var ajax_message_count = 0;
23 * @var codemirror_editor object containing CodeMirror editor of the query editor in SQL tab
25 var codemirror_editor = false;
28 * @var codemirror_editor object containing CodeMirror editor of the inline query editor
30 var codemirror_inline_editor = false;
33 * @var sql_autocomplete_in_progress bool shows if Table/Column name autocomplete AJAX is in progress
35 var sql_autocomplete_in_progress = false;
38 * @var sql_autocomplete object containing list of columns in each table
40 var sql_autocomplete = false;
43 * @var sql_autocomplete_default_table string containing default table to autocomplete columns
45 var sql_autocomplete_default_table = '';
48 * @var chart_activeTimeouts object active timeouts that refresh the charts. When disabling a realtime chart, this can be used to stop the continuous ajax requests
50 var chart_activeTimeouts = {};
53 * @var central_column_list array to hold the columns in central list per db.
55 var central_column_list = [];
58 * @var primary_indexes array to hold 'Primary' index columns.
60 var primary_indexes = [];
63 * @var unique_indexes array to hold 'Unique' index columns.
65 var unique_indexes = [];
68 * @var indexes array to hold 'Index' columns.
73 * @var fulltext_indexes array to hold 'Fulltext' columns.
75 var fulltext_indexes = [];
78 * @var spatial_indexes array to hold 'Spatial' columns.
80 var spatial_indexes = [];
83 * Make sure that ajax requests will not be cached
84 * by appending a random variable to their parameters
86 $.ajaxPrefilter(function (options, originalOptions, jqXHR) {
87 var nocache = new Date().getTime() + "" + Math.floor(Math.random() * 1000000);
88 if (typeof options.data == "string") {
89 options.data += "&_nocache=" + nocache;
90 } else if (typeof options.data == "object") {
91 options.data = $.extend(originalOptions.data, {'_nocache' : nocache});
96 * Hanle redirect and reload flags send as part of AJAX requests
98 * @param data ajax response data
100 function PMA_handleRedirectAndReload(data) {
101 if (parseInt(data.redirect_flag) == 1) {
102 // add one more GET param to display session expiry msg
103 if (window.location.href.indexOf('?') === -1) {
104 window.location.href += '?session_expired=1';
106 window.location.href += '&session_expired=1';
108 window.location.reload();
109 } else if (parseInt(data.reload_flag) == 1) {
110 // remove the token param and reload
111 window.location.href = window.location.href.replace(/&?token=[^&#]*/g, "");
112 window.location.reload();
117 * Creates an SQL editor which supports auto completing etc.
119 * @param $textarea jQuery object wrapping the textarea to be made the editor
120 * @param options optional options for CodeMirror
121 * @param resize optional resizing ('vertical', 'horizontal', 'both')
123 function PMA_getSQLEditor($textarea, options, resize) {
124 if ($textarea.length > 0 && typeof CodeMirror !== 'undefined') {
126 // merge options for CodeMirror
130 extraKeys: {"Ctrl-Space": "autocomplete"},
131 hintOptions: {"completeSingle": false, "completeOnSingleClick": true},
133 mode: "text/x-mysql",
137 if (CodeMirror.sqlLint) {
139 gutters: ["CodeMirror-lint-markers"],
141 "getAnnotations": CodeMirror.sqlLint,
147 $.extend(true, defaults, options);
149 // create CodeMirror editor
150 var codemirrorEditor = CodeMirror.fromTextArea($textarea[0], defaults);
156 if (resize == 'vertical') {
159 if (resize == 'both') {
162 if (resize == 'horizontal') {
165 $(codemirrorEditor.getWrapperElement())
166 .css('resize', resize)
170 codemirrorEditor.setSize($(this).width(), $(this).height());
173 // enable autocomplete
174 codemirrorEditor.on("inputRead", codemirrorAutocompleteOnInputRead);
176 return codemirrorEditor;
182 * Clear text selection
184 function PMA_clearSelection() {
185 if (document.selection && document.selection.empty) {
186 document.selection.empty();
187 } else if (window.getSelection) {
188 var sel = window.getSelection();
192 if (sel.removeAllRanges) {
193 sel.removeAllRanges();
199 * Create a jQuery UI tooltip
201 * @param $elements jQuery object representing the elements
202 * @param item the item
203 * (see http://api.jqueryui.com/tooltip/#option-items)
204 * @param myContent content of the tooltip
205 * @param additionalOptions to override the default options
208 function PMA_tooltip($elements, item, myContent, additionalOptions)
210 if ($('#no_hint').length > 0) {
214 var defaultOptions = {
217 tooltipClass: "tooltip",
223 $elements.tooltip($.extend(true, defaultOptions, additionalOptions));
230 function escapeHtml(unsafe) {
231 if (typeof(unsafe) != 'undefined') {
234 .replace(/&/g, "&")
235 .replace(/</g, "<")
236 .replace(/>/g, ">")
237 .replace(/"/g, """)
238 .replace(/'/g, "'");
244 function PMA_sprintf() {
245 return sprintf.apply(this, arguments);
249 * Hides/shows the default value input field, depending on the default type
250 * Ticks the NULL checkbox if NULL is chosen as default value.
252 function PMA_hideShowDefaultValue($default_type)
254 if ($default_type.val() == 'USER_DEFINED') {
255 $default_type.siblings('.default_value').show().focus();
257 $default_type.siblings('.default_value').hide();
258 if ($default_type.val() == 'NULL') {
259 var $null_checkbox = $default_type.closest('tr').find('.allow_null');
260 $null_checkbox.prop('checked', true);
266 * Hides/shows the input field for column expression based on whether
267 * VIRTUAL/PERSISTENT is selected
269 * @param $virtuality virtuality dropdown
271 function PMA_hideShowExpression($virtuality)
273 if ($virtuality.val() == '') {
274 $virtuality.siblings('.expression').hide();
276 $virtuality.siblings('.expression').show();
281 * Show notices for ENUM columns; add/hide the default value
284 function PMA_verifyColumnsProperties()
286 $("select.column_type").each(function () {
287 PMA_showNoticeForEnum($(this));
289 $("select.default_type").each(function () {
290 PMA_hideShowDefaultValue($(this));
292 $('select.virtuality').each(function () {
293 PMA_hideShowExpression($(this));
298 * Add a hidden field to the form to indicate that this will be an
299 * Ajax request (only if this hidden field does not exist)
301 * @param $form object the form
303 function PMA_prepareForAjaxRequest($form)
305 if (! $form.find('input:hidden').is('#ajax_request_hidden')) {
306 $form.append('<input type="hidden" id="ajax_request_hidden" name="ajax_request" value="true" />');
311 * Generate a new password and copy it to the password input areas
313 * @param passwd_form object the form that holds the password fields
315 * @return boolean always true
317 function suggestPassword(passwd_form)
319 // restrict the password to just letters and numbers to avoid problems:
320 // "editors and viewers regard the password as multiple words and
321 // things like double click no longer work"
322 var pwchars = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWYXZ";
323 var passwordlength = 16; // do we want that to be dynamic? no, keep it simple :)
324 var passwd = passwd_form.generated_pw;
325 var randomWords = new Int32Array(passwordlength);
329 // First we're going to try to use a built-in CSPRNG
330 if (window.crypto && window.crypto.getRandomValues) {
331 window.crypto.getRandomValues(randomWords);
333 // Because of course IE calls it msCrypto instead of being standard
334 else if (window.msCrypto && window.msCrypto.getRandomValues) {
335 window.msCrypto.getRandomValues(randomWords);
337 // Fallback to Math.random
338 for (var i = 0; i < passwordlength; i++) {
339 randomWords[i] = Math.floor(Math.random() * pwchars.length);
343 for (var i = 0; i < passwordlength; i++) {
344 passwd.value += pwchars.charAt(Math.abs(randomWords[i]) % pwchars.length);
347 passwd_form.text_pma_pw.value = passwd.value;
348 passwd_form.text_pma_pw2.value = passwd.value;
353 * Version string to integer conversion.
355 function parseVersionString(str)
357 if (typeof(str) != 'string') { return false; }
359 // Parse possible alpha/beta/rc/
360 var state = str.split('-');
361 if (state.length >= 2) {
362 if (state[1].substr(0, 2) == 'rc') {
363 add = - 20 - parseInt(state[1].substr(2), 10);
364 } else if (state[1].substr(0, 4) == 'beta') {
365 add = - 40 - parseInt(state[1].substr(4), 10);
366 } else if (state[1].substr(0, 5) == 'alpha') {
367 add = - 60 - parseInt(state[1].substr(5), 10);
368 } else if (state[1].substr(0, 3) == 'dev') {
369 /* We don't handle dev, it's git snapshot */
374 var x = str.split('.');
375 // Use 0 for non existing parts
376 var maj = parseInt(x[0], 10) || 0;
377 var min = parseInt(x[1], 10) || 0;
378 var pat = parseInt(x[2], 10) || 0;
379 var hotfix = parseInt(x[3], 10) || 0;
380 return maj * 100000000 + min * 1000000 + pat * 10000 + hotfix * 100 + add;
384 * Indicates current available version on main page.
386 function PMA_current_version(data)
388 if (data && data.version && data.date) {
389 var current = parseVersionString($('span.version').text());
390 var latest = parseVersionString(data.version);
391 var version_information_message = '<span class="latest">' +
392 PMA_messages.strLatestAvailable +
393 ' ' + escapeHtml(data.version) +
395 if (latest > current) {
396 var message = PMA_sprintf(
397 PMA_messages.strNewerVersion,
398 escapeHtml(data.version),
399 escapeHtml(data.date)
401 var htmlClass = 'notice';
402 if (Math.floor(latest / 10000) === Math.floor(current / 10000)) {
403 /* Security update */
406 $('#newer_version_notice').remove();
407 $('#maincontainer').after('<div id="newer_version_notice" class="' + htmlClass + '">' + message + '</div>');
409 if (latest === current) {
410 version_information_message = ' (' + PMA_messages.strUpToDate + ')';
412 var $liPmaVersion = $('#li_pma_version');
413 $liPmaVersion.find('span.latest').remove();
414 $liPmaVersion.append(version_information_message);
419 * Loads Git revision data from ajax for index.php
421 function PMA_display_git_revision()
423 $('#is_git_revision').remove();
424 $('#li_pma_version_git').remove();
428 "server": PMA_commonParams.get('server'),
429 "token": PMA_commonParams.get('token'),
430 "git_revision": true,
431 "ajax_request": true,
435 if (typeof data !== 'undefined' && data.success === true) {
436 $(data.message).insertAfter('#li_pma_version');
443 * for libraries/display_change_password.lib.php
444 * libraries/user_password.php
448 function displayPasswordGenerateButton()
450 $('#tr_element_before_generate_password').parent().append('<tr class="vmiddle"><td>' + PMA_messages.strGeneratePassword + '</td><td><input type="button" class="button" id="button_generate_password" value="' + PMA_messages.strGenerate + '" onclick="suggestPassword(this.form)" /><input type="text" name="generated_pw" id="generated_pw" /></td></tr>');
451 $('#div_element_before_generate_password').parent().append('<div class="item"><label for="button_generate_password">' + PMA_messages.strGeneratePassword + ':</label><span class="options"><input type="button" class="button" id="button_generate_password" value="' + PMA_messages.strGenerate + '" onclick="suggestPassword(this.form)" /></span><input type="text" name="generated_pw" id="generated_pw" /></div>');
455 * Adds a date/time picker to an element
457 * @param object $this_element a jQuery object pointing to the element
459 function PMA_addDatepicker($this_element, type, options)
461 var showTimepicker = true;
463 showTimepicker = false;
466 var defaultOptions = {
468 buttonImage: themeCalendarImage, // defined in js/messages.php
469 buttonImageOnly: true,
475 showTimepicker: showTimepicker,
476 showButtonPanel: false,
477 dateFormat: 'yy-mm-dd', // yy means year with four digits
478 timeFormat: 'HH:mm:ss.lc',
479 constrainInput: false,
480 altFieldTimeOnly: false,
482 beforeShow: function (input, inst) {
483 // Remember that we came from the datepicker; this is used
484 // in tbl_change.js by verificationsAfterFieldChange()
485 $this_element.data('comes_from', 'datepicker');
486 if ($(input).closest('.cEdit').length > 0) {
487 setTimeout(function () {
495 // Fix wrong timepicker z-index, doesn't work without timeout
496 setTimeout(function () {
497 $('#ui-timepicker-div').css('z-index', $('#ui-datepicker-div').css('z-index'));
500 onSelect: function() {
501 $this_element.data('datepicker').inline = true;
503 onClose: function (dateText, dp_inst) {
504 // The value is no more from the date picker
505 $this_element.data('comes_from', '');
506 if (typeof $this_element.data('datepicker') !== 'undefined') {
507 $this_element.data('datepicker').inline = false;
511 if (type == "datetime" || type == "timestamp") {
512 $this_element.datetimepicker($.extend(defaultOptions, options));
514 else if (type == "date") {
515 $this_element.datetimepicker($.extend(defaultOptions, options));
517 else if (type == "time") {
518 $this_element.timepicker($.extend(defaultOptions, options));
523 * selects the content of a given object, f.e. a textarea
525 * @param element object element of which the content will be selected
526 * @param lock var variable which holds the lock for this element
527 * or true, if no lock exists
528 * @param only_once boolean if true this is only done once
529 * f.e. only on first focus
531 function selectContent(element, lock, only_once)
533 if (only_once && only_once_elements[element.name]) {
537 only_once_elements[element.name] = true;
547 * Displays a confirmation box before submitting a "DROP/DELETE/ALTER" query.
548 * This function is called while clicking links
550 * @param theLink object the link
551 * @param theSqlQuery object the sql query to submit
553 * @return boolean whether to run the query or not
555 function confirmLink(theLink, theSqlQuery)
557 // Confirmation is not required in the configuration file
558 // or browser is Opera (crappy js implementation)
559 if (PMA_messages.strDoYouReally === '' || typeof(window.opera) != 'undefined') {
563 var is_confirmed = confirm(PMA_sprintf(PMA_messages.strDoYouReally, theSqlQuery));
565 if ($(theLink).hasClass('formLinkSubmit')) {
566 var name = 'is_js_confirmed';
567 if ($(theLink).attr('href').indexOf('usesubform') != -1) {
568 name = 'subform[' + $(theLink).attr('href').substr('#').match(/usesubform\[(\d+)\]/i)[1] + '][is_js_confirmed]';
571 $(theLink).parents('form').append('<input type="hidden" name="' + name + '" value="1" />');
572 } else if (typeof(theLink.href) != 'undefined') {
573 theLink.href += '&is_js_confirmed=1';
574 } else if (typeof(theLink.form) != 'undefined') {
575 theLink.form.action += '?is_js_confirmed=1';
580 } // end of the 'confirmLink()' function
583 * Displays an error message if a "DROP DATABASE" statement is submitted
584 * while it isn't allowed, else confirms a "DROP/DELETE/ALTER" query before
585 * submitting it if required.
586 * This function is called by the 'checkSqlQuery()' js function.
588 * @param theForm1 object the form
589 * @param sqlQuery1 object the sql query textarea
591 * @return boolean whether to run the query or not
593 * @see checkSqlQuery()
595 function confirmQuery(theForm1, sqlQuery1)
597 // Confirmation is not required in the configuration file
598 if (PMA_messages.strDoYouReally === '') {
602 // "DROP DATABASE" statement isn't allowed
603 if (PMA_messages.strNoDropDatabases !== '') {
604 var drop_re = new RegExp('(^|;)\\s*DROP\\s+(IF EXISTS\\s+)?DATABASE\\s', 'i');
605 if (drop_re.test(sqlQuery1.value)) {
606 alert(PMA_messages.strNoDropDatabases);
613 // Confirms a "DROP/DELETE/ALTER/TRUNCATE" statement
615 // TODO: find a way (if possible) to use the parser-analyser
616 // for this kind of verification
617 // For now, I just added a ^ to check for the statement at
618 // beginning of expression
620 var do_confirm_re_0 = new RegExp('^\\s*DROP\\s+(IF EXISTS\\s+)?(TABLE|DATABASE|PROCEDURE)\\s', 'i');
621 var do_confirm_re_1 = new RegExp('^\\s*ALTER\\s+TABLE\\s+((`[^`]+`)|([A-Za-z0-9_$]+))\\s+DROP\\s', 'i');
622 var do_confirm_re_2 = new RegExp('^\\s*DELETE\\s+FROM\\s', 'i');
623 var do_confirm_re_3 = new RegExp('^\\s*TRUNCATE\\s', 'i');
625 if (do_confirm_re_0.test(sqlQuery1.value) ||
626 do_confirm_re_1.test(sqlQuery1.value) ||
627 do_confirm_re_2.test(sqlQuery1.value) ||
628 do_confirm_re_3.test(sqlQuery1.value)) {
630 if (sqlQuery1.value.length > 100) {
631 message = sqlQuery1.value.substr(0, 100) + '\n ...';
633 message = sqlQuery1.value;
635 var is_confirmed = confirm(PMA_sprintf(PMA_messages.strDoYouReally, message));
636 // statement is confirmed -> update the
637 // "is_js_confirmed" form field so the confirm test won't be
638 // run on the server side and allows to submit the form
640 theForm1.elements.is_js_confirmed.value = 1;
643 // statement is rejected -> do not submit the form
648 } // end if (handle confirm box result)
649 } // end if (display confirm box)
652 } // end of the 'confirmQuery()' function
655 * Displays an error message if the user submitted the sql query form with no
656 * sql query, else checks for "DROP/DELETE/ALTER" statements
658 * @param theForm object the form
660 * @return boolean always false
662 * @see confirmQuery()
664 function checkSqlQuery(theForm)
666 // get the textarea element containing the query
668 if (codemirror_editor) {
669 codemirror_editor.save();
670 sqlQuery = codemirror_editor.getValue();
672 sqlQuery = theForm.elements.sql_query.value;
675 var space_re = new RegExp('\\s+');
676 if (typeof(theForm.elements.sql_file) != 'undefined' &&
677 theForm.elements.sql_file.value.replace(space_re, '') !== '') {
680 if (isEmpty && typeof(theForm.elements.id_bookmark) != 'undefined' &&
681 (theForm.elements.id_bookmark.value !== null || theForm.elements.id_bookmark.value !== '') &&
682 theForm.elements.id_bookmark.selectedIndex !== 0) {
685 // Checks for "DROP/DELETE/ALTER" statements
686 if (sqlQuery.replace(space_re, '') !== '') {
687 return confirmQuery(theForm, sqlQuery);
693 alert(PMA_messages.strFormEmpty);
694 codemirror_editor.focus();
699 } // end of the 'checkSqlQuery()' function
702 * Check if a form's element is empty.
703 * An element containing only spaces is also considered empty
705 * @param object the form
706 * @param string the name of the form field to put the focus on
708 * @return boolean whether the form field is empty or not
710 function emptyCheckTheField(theForm, theFieldName)
712 var theField = theForm.elements[theFieldName];
713 var space_re = new RegExp('\\s+');
714 return theField.value.replace(space_re, '') === '';
715 } // end of the 'emptyCheckTheField()' function
718 * Ensures a value submitted in a form is numeric and is in a range
720 * @param object the form
721 * @param string the name of the form field to check
722 * @param integer the minimum authorized value
723 * @param integer the maximum authorized value
725 * @return boolean whether a valid number has been submitted or not
727 function checkFormElementInRange(theForm, theFieldName, message, min, max)
729 var theField = theForm.elements[theFieldName];
730 var val = parseInt(theField.value, 10);
732 if (typeof(min) == 'undefined') {
735 if (typeof(max) == 'undefined') {
736 max = Number.MAX_VALUE;
742 alert(PMA_messages.strEnterValidNumber);
746 // It's a number but it is not between min and max
747 else if (val < min || val > max) {
749 alert(PMA_sprintf(message, val));
753 // It's a valid number
755 theField.value = val;
759 } // end of the 'checkFormElementInRange()' function
762 function checkTableEditForm(theForm, fieldsCnt)
764 // TODO: avoid sending a message if user just wants to add a line
765 // on the form but has not completed at least one field name
767 var atLeastOneField = 0;
768 var i, elm, elm2, elm3, val, id;
770 for (i = 0; i < fieldsCnt; i++) {
771 id = "#field_" + i + "_2";
774 if (val == 'VARCHAR' || val == 'CHAR' || val == 'BIT' || val == 'VARBINARY' || val == 'BINARY') {
775 elm2 = $("#field_" + i + "_3");
776 val = parseInt(elm2.val(), 10);
777 elm3 = $("#field_" + i + "_1");
778 if (isNaN(val) && elm3.val() !== "") {
780 alert(PMA_messages.strEnterValidLength);
786 if (atLeastOneField === 0) {
787 id = "field_" + i + "_1";
788 if (!emptyCheckTheField(theForm, id)) {
793 if (atLeastOneField === 0) {
794 var theField = theForm.elements.field_0_1;
795 alert(PMA_messages.strFormEmpty);
800 // at least this section is under jQuery
801 var $input = $("input.textfield[name='table']");
802 if ($input.val() === "") {
803 alert(PMA_messages.strFormEmpty);
809 } // enf of the 'checkTableEditForm()' function
812 * True if last click is to check a row.
814 var last_click_checked = false;
817 * Zero-based index of last clicked row.
818 * Used to handle the shift + click event in the code above.
820 var last_clicked_row = -1;
823 * Zero-based index of last shift clicked row.
825 var last_shift_clicked_row = -1;
827 var _idleSecondsCounter = 0;
830 AJAX.registerTeardown('functions.js', function () {
831 clearTimeout(updateTimeout);
832 clearInterval(IncInterval);
833 $(document).off('mousemove');
836 AJAX.registerOnload('functions.js', function () {
837 document.onclick = function() {
838 _idleSecondsCounter = 0;
840 $(document).on('mousemove',function() {
841 _idleSecondsCounter = 0;
843 document.onkeypress = function() {
844 _idleSecondsCounter = 0;
847 function SetIdleTime() {
848 _idleSecondsCounter++;
850 function UpdateIdleTime() {
851 var href = 'index.php';
853 'ajax_request' : true,
854 'token' : PMA_commonParams.get('token'),
855 'server' : PMA_commonParams.get('server'),
856 'db' : PMA_commonParams.get('db'),
857 'access_time':_idleSecondsCounter
863 success: function (data) {
865 if (PMA_commonParams.get('LoginCookieValidity')-_idleSecondsCounter > 5) {
866 var interval = (PMA_commonParams.get('LoginCookieValidity') - _idleSecondsCounter - 5) * 1000;
867 if (interval > Math.pow(2, 31) - 1) { // max value for setInterval() function
868 interval = Math.pow(2, 31) - 1;
870 updateTimeout = window.setTimeout(UpdateIdleTime, interval);
872 updateTimeout = window.setTimeout(UpdateIdleTime, 2000);
874 } else { //timeout occurred
875 window.location.reload(true);
876 clearInterval(IncInterval);
881 if (PMA_commonParams.get('logged_in') && PMA_commonParams.get('auth_type') == 'cookie') {
882 IncInterval = window.setInterval(SetIdleTime, 1000);
883 var interval = (PMA_commonParams.get('LoginCookieValidity') - 5) * 1000;
884 if (interval > Math.pow(2, 31) - 1) { // max value for setInterval() function
885 interval = Math.pow(2, 31) - 1;
887 updateTimeout = window.setTimeout(UpdateIdleTime, interval);
891 * Unbind all event handlers before tearing down a page
893 AJAX.registerTeardown('functions.js', function () {
894 $(document).off('click', 'input:checkbox.checkall');
896 AJAX.registerOnload('functions.js', function () {
898 * Row marking in horizontal mode (use "on" so that it works also for
899 * next pages reached via AJAX); a tr may have the class noclick to remove
903 $(document).on('click', 'input:checkbox.checkall', function (e) {
905 var $tr = $this.closest('tr');
906 var $table = $this.closest('table');
908 if (!e.shiftKey || last_clicked_row == -1) {
911 var $checkbox = $tr.find(':checkbox.checkall');
912 var checked = $this.prop('checked');
913 $checkbox.prop('checked', checked).trigger('change');
915 $tr.addClass('marked');
917 $tr.removeClass('marked');
919 last_click_checked = checked;
921 // remember the last clicked row
922 last_clicked_row = last_click_checked ? $table.find('tr.odd:not(.noclick), tr.even:not(.noclick)').index($tr) : -1;
923 last_shift_clicked_row = -1;
925 // handle the shift click
926 PMA_clearSelection();
929 // clear last shift click result
930 if (last_shift_clicked_row >= 0) {
931 if (last_shift_clicked_row >= last_clicked_row) {
932 start = last_clicked_row;
933 end = last_shift_clicked_row;
935 start = last_shift_clicked_row;
936 end = last_clicked_row;
938 $tr.parent().find('tr.odd:not(.noclick), tr.even:not(.noclick)')
939 .slice(start, end + 1)
940 .removeClass('marked')
942 .prop('checked', false)
946 // handle new shift click
947 var curr_row = $table.find('tr.odd:not(.noclick), tr.even:not(.noclick)').index($tr);
948 if (curr_row >= last_clicked_row) {
949 start = last_clicked_row;
953 end = last_clicked_row;
955 $tr.parent().find('tr.odd:not(.noclick), tr.even:not(.noclick)')
956 .slice(start, end + 1)
959 .prop('checked', true)
962 // remember the last shift clicked row
963 last_shift_clicked_row = curr_row;
970 * Add attribute to text boxes for iOS devices (based on bugID: 3508912)
972 if (navigator.userAgent.match(/(iphone|ipod|ipad)/i)) {
973 $('input[type=text]').attr('autocapitalize', 'off').attr('autocorrect', 'off');
978 * Row highlighting in horizontal mode (use "on"
979 * so that it works also for pages reached via AJAX)
981 /*AJAX.registerOnload('functions.js', function () {
982 $(document).on('hover', 'tr.odd, tr.even',function (event) {
984 $tr.toggleClass('hover',event.type=='mouseover');
985 $tr.children().toggleClass('hover',event.type=='mouseover');
990 * This array is used to remember mark status of rows in browse mode
995 * marks all rows and selects its first checkbox inside the given element
996 * the given element is usually a table or a div containing the table or tables
998 * @param container DOM element
1000 function markAllRows(container_id)
1003 $("#" + container_id).find("input:checkbox:enabled").prop('checked', true)
1005 .parents("tr").addClass("marked");
1010 * marks all rows and selects its first checkbox inside the given element
1011 * the given element is usually a table or a div containing the table or tables
1013 * @param container DOM element
1015 function unMarkAllRows(container_id)
1018 $("#" + container_id).find("input:checkbox:enabled").prop('checked', false)
1020 .parents("tr").removeClass("marked");
1025 * Checks/unchecks all checkbox in given container (f.e. a form, fieldset or div)
1027 * @param string container_id the container id
1028 * @param boolean state new value for checkbox (true or false)
1029 * @return boolean always true
1031 function setCheckboxes(container_id, state)
1034 $("#" + container_id).find("input:checkbox").prop('checked', state);
1036 } // end of the 'setCheckboxes()' function
1039 * Checks/unchecks all options of a <select> element
1041 * @param string the form name
1042 * @param string the element name
1043 * @param boolean whether to check or to uncheck options
1045 * @return boolean always true
1047 function setSelectOptions(the_form, the_select, do_check)
1049 $("form[name='" + the_form + "'] select[name='" + the_select + "']").find("option").prop('selected', do_check);
1051 } // end of the 'setSelectOptions()' function
1054 * Sets current value for query box.
1056 function setQuery(query)
1058 if (codemirror_editor) {
1059 codemirror_editor.setValue(query);
1060 codemirror_editor.focus();
1062 document.sqlform.sql_query.value = query;
1063 document.sqlform.sql_query.focus();
1068 * Handles 'Simulate query' button on SQL query box.
1072 function PMA_handleSimulateQueryButton()
1074 var update_re = new RegExp('^\\s*UPDATE\\s+((`[^`]+`)|([A-Za-z0-9_$]+))\\s+SET\\s', 'i');
1075 var delete_re = new RegExp('^\\s*DELETE\\s+FROM\\s', 'i');
1078 if (codemirror_editor) {
1079 query = codemirror_editor.getValue();
1081 query = $('#sqlquery').val();
1084 var $simulateDml = $('#simulate_dml');
1085 if (update_re.test(query) || delete_re.test(query)) {
1086 if (! $simulateDml.length) {
1087 $('#button_submit_query')
1088 .before('<input type="button" id="simulate_dml"' +
1089 'tabindex="199" value="' +
1090 PMA_messages.strSimulateDML +
1094 if ($simulateDml.length) {
1095 $simulateDml.remove();
1101 * Create quick sql statements.
1104 function insertQuery(queryType)
1106 if (queryType == "clear") {
1109 } else if (queryType == "format") {
1110 if (codemirror_editor) {
1111 $('#querymessage').html(PMA_messages.strFormatting +
1112 ' <img class="ajaxIcon" src="' +
1113 pmaThemeImage + 'ajax_clock_small.gif" alt="">');
1114 var href = 'db_sql_format.php';
1116 'ajax_request': true,
1117 'token': PMA_commonParams.get('token'),
1118 'sql': codemirror_editor.getValue()
1124 success: function (data) {
1126 codemirror_editor.setValue(data.sql);
1128 $('#querymessage').html('');
1133 } else if (queryType == "saved") {
1134 if ($.cookie('auto_saved_sql')) {
1135 setQuery($.cookie('auto_saved_sql'));
1137 PMA_ajaxShowMessage(PMA_messages.strNoAutoSavedQuery);
1143 var myListBox = document.sqlform.dummy;
1144 var table = document.sqlform.table.value;
1146 if (myListBox.options.length > 0) {
1147 sql_box_locked = true;
1148 var columnsList = "";
1152 for (var i = 0; i < myListBox.options.length; i++) {
1155 columnsList += ", ";
1159 columnsList += myListBox.options[i].value;
1160 valDis += "[value-" + NbSelect + "]";
1161 editDis += myListBox.options[i].value + "=[value-" + NbSelect + "]";
1163 if (queryType == "selectall") {
1164 query = "SELECT * FROM `" + table + "` WHERE 1";
1165 } else if (queryType == "select") {
1166 query = "SELECT " + columnsList + " FROM `" + table + "` WHERE 1";
1167 } else if (queryType == "insert") {
1168 query = "INSERT INTO `" + table + "`(" + columnsList + ") VALUES (" + valDis + ")";
1169 } else if (queryType == "update") {
1170 query = "UPDATE `" + table + "` SET " + editDis + " WHERE 1";
1171 } else if (queryType == "delete") {
1172 query = "DELETE FROM `" + table + "` WHERE 1";
1175 sql_box_locked = false;
1181 * Inserts multiple fields.
1184 function insertValueQuery()
1186 var myQuery = document.sqlform.sql_query;
1187 var myListBox = document.sqlform.dummy;
1189 if (myListBox.options.length > 0) {
1190 sql_box_locked = true;
1191 var columnsList = "";
1193 for (var i = 0; i < myListBox.options.length; i++) {
1194 if (myListBox.options[i].selected) {
1197 columnsList += ", ";
1199 columnsList += myListBox.options[i].value;
1203 /* CodeMirror support */
1204 if (codemirror_editor) {
1205 codemirror_editor.replaceSelection(columnsList);
1207 } else if (document.selection) {
1209 var sel = document.selection.createRange();
1210 sel.text = columnsList;
1211 document.sqlform.insert.focus();
1213 //MOZILLA/NETSCAPE support
1214 else if (document.sqlform.sql_query.selectionStart || document.sqlform.sql_query.selectionStart == "0") {
1215 var startPos = document.sqlform.sql_query.selectionStart;
1216 var endPos = document.sqlform.sql_query.selectionEnd;
1217 var SqlString = document.sqlform.sql_query.value;
1219 myQuery.value = SqlString.substring(0, startPos) + columnsList + SqlString.substring(endPos, SqlString.length);
1221 myQuery.value += columnsList;
1223 sql_box_locked = false;
1228 * Updates the input fields for the parameters based on the query
1230 function updateQueryParameters() {
1232 if ($('#parameterized').is(':checked')) {
1233 var query = codemirror_editor ? codemirror_editor.getValue() : $('#sqlquery').val();
1235 var allParameters = query.match(/:[a-zA-Z0-9_]+/g);
1236 var parameters = [];
1237 // get unique parameters
1238 if (allParameters) {
1239 $.each(allParameters, function(i, parameter){
1240 if ($.inArray(parameter, parameters) === -1) {
1241 parameters.push(parameter);
1246 var $temp = $('<div />');
1247 $temp.append($('#parametersDiv').children());
1248 $('#parametersDiv').empty();
1250 $.each(parameters, function (i, parameter) {
1251 var paramName = parameter.substring(1);
1252 var $param = $temp.find('#paramSpan_' + paramName );
1253 if (! $param.length) {
1254 $param = $('<span class="parameter" id="paramSpan_' + paramName + '" />');
1255 $('<label for="param_' + paramName + '" />').text(parameter).appendTo($param);
1256 $('<input type="text" name="parameters[' + parameter + ']" id="param_' + paramName + '" />').appendTo($param);
1258 $('#parametersDiv').append($param);
1261 $('#parametersDiv').empty();
1266 * Add a date/time picker to each element that needs it
1267 * (only when jquery-ui-timepicker-addon.js is loaded)
1269 function addDateTimePicker() {
1270 if ($.timepicker !== undefined) {
1271 $('input.timefield, input.datefield, input.datetimefield').each(function () {
1273 var decimals = $(this).parent().attr('data-decimals');
1274 var type = $(this).parent().attr('data-type');
1276 var showMillisec = false;
1277 var showMicrosec = false;
1278 var timeFormat = 'HH:mm:ss';
1279 // check for decimal places of seconds
1280 if (decimals > 0 && type.indexOf('time') != -1){
1282 showMillisec = true;
1283 showMicrosec = true;
1284 timeFormat = 'HH:mm:ss.lc';
1286 showMillisec = true;
1287 timeFormat = 'HH:mm:ss.l';
1290 PMA_addDatepicker($(this), type, {
1291 showMillisec: showMillisec,
1292 showMicrosec: showMicrosec,
1293 timeFormat: timeFormat
1300 * Refresh/resize the WYSIWYG scratchboard
1302 function refreshLayout()
1304 var $elm = $('#pdflayout');
1305 var orientation = $('#orientation_opt').val();
1307 var $paperOpt = $('#paper_opt');
1308 if ($paperOpt.length == 1) {
1309 paper = $paperOpt.val();
1313 if (orientation == 'P') {
1317 $elm.css('width', pdfPaperSize(paper, posa) + 'px');
1318 $elm.css('height', pdfPaperSize(paper, posb) + 'px');
1322 * Initializes positions of elements.
1324 function TableDragInit() {
1325 $('.pdflayout_table').each(function () {
1326 var $this = $(this);
1327 var number = $this.data('number');
1328 var x = $('#c_table_' + number + '_x').val();
1329 var y = $('#c_table_' + number + '_y').val();
1330 $this.css('left', x + 'px');
1331 $this.css('top', y + 'px');
1332 /* Make elements draggable */
1334 containment: "parent",
1335 drag: function (evt, ui) {
1336 var number = $this.data('number');
1337 $('#c_table_' + number + '_x').val(parseInt(ui.position.left, 10));
1338 $('#c_table_' + number + '_y').val(parseInt(ui.position.top, 10));
1345 * Resets drag and drop positions.
1347 function resetDrag() {
1348 $('.pdflayout_table').each(function () {
1349 var $this = $(this);
1350 var x = $this.data('x');
1351 var y = $this.data('y');
1352 $this.css('left', x + 'px');
1353 $this.css('top', y + 'px');
1358 * User schema handlers.
1361 /* Move in scratchboard on manual change */
1362 $(document).on('change', '.position-change', function () {
1363 var $this = $(this);
1364 var $elm = $('#table_' + $this.data('number'));
1365 $elm.css($this.data('axis'), $this.val() + 'px');
1367 /* Refresh on paper size/orientation change */
1368 $(document).on('change', '.paper-change', function () {
1369 var $elm = $('#pdflayout');
1370 if ($elm.css('visibility') == 'visible') {
1375 /* Show/hide the WYSIWYG scratchboard */
1376 $(document).on('click', '#toggle-dragdrop', function () {
1377 var $elm = $('#pdflayout');
1378 if ($elm.css('visibility') == 'hidden') {
1381 $elm.css('visibility', 'visible');
1382 $elm.css('display', 'block');
1383 $('#showwysiwyg').val('1');
1385 $elm.css('visibility', 'hidden');
1386 $elm.css('display', 'none');
1387 $('#showwysiwyg').val('0');
1390 /* Reset scratchboard */
1391 $(document).on('click', '#reset-dragdrop', function () {
1397 * Returns paper sizes for a given format
1399 function pdfPaperSize(format, axis)
1401 switch (format.toUpperCase()) {
1751 * Get checkbox for foreign key checks
1755 function getForeignKeyCheckboxLoader() {
1758 html += '<div class="load-default-fk-check-value">';
1759 html += PMA_getImage('ajax_clock_small.gif');
1765 function loadForeignKeyCheckbox() {
1766 // Load default foreign key check value
1768 'ajax_request': true,
1769 'token': PMA_commonParams.get('token'),
1770 'server': PMA_commonParams.get('server'),
1771 'get_default_fk_check_value': true
1773 $.get('sql.php', params, function (data) {
1774 var html = '<input type="hidden" name="fk_checks" value="0" />' +
1775 '<input type="checkbox" name="fk_checks" id="fk_checks"' +
1776 (data.default_fk_check_value ? ' checked="checked"' : '') + ' />' +
1777 '<label for="fk_checks">' + PMA_messages.strForeignKeyCheck + '</label>';
1778 $('.load-default-fk-check-value').replaceWith(html);
1782 function getJSConfirmCommonParam(elem) {
1784 'is_js_confirmed' : 1,
1785 'ajax_request' : true,
1786 'fk_checks': $(elem).find('#fk_checks').is(':checked') ? 1 : 0
1791 * Unbind all event handlers before tearing down a page
1793 AJAX.registerTeardown('functions.js', function () {
1794 $(document).off('click', "a.inline_edit_sql");
1795 $(document).off('click', "input#sql_query_edit_save");
1796 $(document).off('click', "input#sql_query_edit_discard");
1797 $('input.sqlbutton').unbind('click');
1798 if (codemirror_editor) {
1799 codemirror_editor.off('blur');
1801 $(document).off('blur', '#sqlquery');
1803 $(document).off('change', '#parameterized');
1804 $('#sqlquery').unbind('keydown');
1805 $('#sql_query_edit').unbind('keydown');
1807 if (codemirror_inline_editor) {
1808 // Copy the sql query to the text area to preserve it.
1809 $('#sql_query_edit').text(codemirror_inline_editor.getValue());
1810 $(codemirror_inline_editor.getWrapperElement()).unbind('keydown');
1811 codemirror_inline_editor.toTextArea();
1812 codemirror_inline_editor = false;
1814 if (codemirror_editor) {
1815 $(codemirror_editor.getWrapperElement()).unbind('keydown');
1820 * Jquery Coding for inline editing SQL_QUERY
1822 AJAX.registerOnload('functions.js', function () {
1823 // If we are coming back to the page by clicking forward button
1824 // of the browser, bind the code mirror to inline query editor.
1825 bindCodeMirrorToInlineEditor();
1826 $(document).on('click', "a.inline_edit_sql", function () {
1827 if ($('#sql_query_edit').length) {
1828 // An inline query editor is already open,
1829 // we don't want another copy of it
1833 var $form = $(this).prev('form');
1834 var sql_query = $form.find("input[name='sql_query']").val().trim();
1835 var $inner_sql = $(this).parent().prev().find('code.sql');
1836 var old_text = $inner_sql.html();
1838 var new_content = "<textarea name=\"sql_query_edit\" id=\"sql_query_edit\">" + sql_query + "</textarea>\n";
1839 new_content += getForeignKeyCheckboxLoader();
1840 new_content += "<input type=\"submit\" id=\"sql_query_edit_save\" class=\"button btnSave\" value=\"" + PMA_messages.strGo + "\"/>\n";
1841 new_content += "<input type=\"button\" id=\"sql_query_edit_discard\" class=\"button btnDiscard\" value=\"" + PMA_messages.strCancel + "\"/>\n";
1842 var $editor_area = $('div#inline_editor');
1843 if ($editor_area.length === 0) {
1844 $editor_area = $('<div id="inline_editor_outer"></div>');
1845 $editor_area.insertBefore($inner_sql);
1847 $editor_area.html(new_content);
1848 loadForeignKeyCheckbox();
1851 bindCodeMirrorToInlineEditor();
1855 $(document).on('click', "input#sql_query_edit_save", function () {
1856 $(".success").hide();
1857 //hide already existing success message
1859 if (codemirror_inline_editor) {
1860 codemirror_inline_editor.save();
1861 sql_query = codemirror_inline_editor.getValue();
1863 sql_query = $(this).parent().find('#sql_query_edit').val();
1865 var fk_check = $(this).parent().find('#fk_checks').is(':checked');
1867 var $form = $("a.inline_edit_sql").prev('form');
1868 var $fake_form = $('<form>', {action: 'import.php', method: 'post'})
1869 .append($form.find("input[name=server], input[name=db], input[name=table], input[name=token]").clone())
1870 .append($('<input/>', {type: 'hidden', name: 'show_query', value: 1}))
1871 .append($('<input/>', {type: 'hidden', name: 'is_js_confirmed', value: 0}))
1872 .append($('<input/>', {type: 'hidden', name: 'sql_query', value: sql_query}))
1873 .append($('<input/>', {type: 'hidden', name: 'fk_checks', value: fk_check ? 1 : 0}));
1874 if (! checkSqlQuery($fake_form[0])) {
1877 $fake_form.appendTo($('body')).submit();
1880 $(document).on('click', "input#sql_query_edit_discard", function () {
1881 var $divEditor = $('div#inline_editor_outer');
1882 $divEditor.siblings('code.sql').show();
1883 $divEditor.remove();
1886 $('input.sqlbutton').click(function (evt) {
1887 insertQuery(evt.target.id);
1888 PMA_handleSimulateQueryButton();
1892 $(document).on('change', '#parameterized', updateQueryParameters);
1894 var $inputUsername = $('#input_username');
1895 if ($inputUsername) {
1896 if ($inputUsername.val() === '') {
1897 $inputUsername.focus();
1899 $('#input_password').focus();
1905 * "inputRead" event handler for CodeMirror SQL query editors for autocompletion
1907 function codemirrorAutocompleteOnInputRead(instance) {
1908 if (!sql_autocomplete_in_progress
1909 && (!instance.options.hintOptions.tables || !sql_autocomplete)) {
1911 if (!sql_autocomplete) {
1912 // Reset after teardown
1913 instance.options.hintOptions.tables = false;
1914 instance.options.hintOptions.defaultTable = '';
1916 sql_autocomplete_in_progress = true;
1918 var href = 'db_sql_autocomplete.php';
1920 'ajax_request': true,
1921 'token': PMA_commonParams.get('token'),
1922 'server': PMA_commonParams.get('server'),
1923 'db': PMA_commonParams.get('db'),
1927 var columnHintRender = function(elem, self, data) {
1928 $('<div class="autocomplete-column-name">')
1929 .text(data.columnName)
1931 $('<div class="autocomplete-column-hint">')
1932 .text(data.columnHint)
1940 success: function (data) {
1942 var tables = $.parseJSON(data.tables);
1943 sql_autocomplete_default_table = PMA_commonParams.get('table');
1944 sql_autocomplete = [];
1945 for (var table in tables) {
1946 if (tables.hasOwnProperty(table)) {
1947 var columns = tables[table];
1952 for (var column in columns) {
1953 if (columns.hasOwnProperty(column)) {
1954 var displayText = columns[column].Type;
1955 if (columns[column].Key == 'PRI') {
1956 displayText += ' | Primary';
1957 } else if (columns[column].Key == 'UNI') {
1958 displayText += ' | Unique';
1960 table.columns.push({
1962 displayText: column + " | " + displayText,
1964 columnHint: displayText,
1965 render: columnHintRender
1970 sql_autocomplete.push(table);
1972 instance.options.hintOptions.tables = sql_autocomplete;
1973 instance.options.hintOptions.defaultTable = sql_autocomplete_default_table;
1976 complete: function () {
1977 sql_autocomplete_in_progress = false;
1982 instance.options.hintOptions.tables = sql_autocomplete;
1983 instance.options.hintOptions.defaultTable = sql_autocomplete_default_table;
1986 if (instance.state.completionActive) {
1989 var cur = instance.getCursor();
1990 var token = instance.getTokenAt(cur);
1992 if (token.string.match(/^[.`\w@]\w*$/)) {
1993 string = token.string;
1995 if (string.length > 0) {
1996 CodeMirror.commands.autocomplete(instance);
2001 * Remove autocomplete information before tearing down a page
2003 AJAX.registerTeardown('functions.js', function () {
2004 sql_autocomplete = false;
2005 sql_autocomplete_default_table = '';
2009 * Binds the CodeMirror to the text area used to inline edit a query.
2011 function bindCodeMirrorToInlineEditor() {
2012 var $inline_editor = $('#sql_query_edit');
2013 if ($inline_editor.length > 0) {
2014 if (typeof CodeMirror !== 'undefined') {
2015 var height = $inline_editor.css('height');
2016 codemirror_inline_editor = PMA_getSQLEditor($inline_editor);
2017 codemirror_inline_editor.getWrapperElement().style.height = height;
2018 codemirror_inline_editor.refresh();
2019 codemirror_inline_editor.focus();
2020 $(codemirror_inline_editor.getWrapperElement())
2021 .bind('keydown', catchKeypressesFromSqlInlineEdit);
2025 .bind('keydown', catchKeypressesFromSqlInlineEdit);
2030 function catchKeypressesFromSqlInlineEdit(event) {
2031 // ctrl-enter is 10 in chrome and ie, but 13 in ff
2032 if (event.ctrlKey && (event.keyCode == 13 || event.keyCode == 10)) {
2033 $("#sql_query_edit_save").trigger('click');
2038 * Adds doc link to single highlighted SQL element
2040 function PMA_doc_add($elm, params)
2042 if (typeof mysql_doc_template == 'undefined') {
2046 var url = PMA_sprintf(
2047 decodeURIComponent(mysql_doc_template),
2050 if (params.length > 1) {
2051 url += '#' + params[1];
2053 var content = $elm.text();
2055 $elm.append('<a target="mysql_doc" class="cm-sql-doc" href="' + url + '">' + content + '</a>');
2059 * Generates doc links for keywords inside highlighted SQL
2061 function PMA_doc_keyword(idx, elm)
2064 /* Skip already processed ones */
2065 if ($elm.find('a').length > 0) {
2068 var keyword = $elm.text().toUpperCase();
2069 var $next = $elm.next('.cm-keyword');
2071 var next_keyword = $next.text().toUpperCase();
2072 var full = keyword + ' ' + next_keyword;
2074 var $next2 = $next.next('.cm-keyword');
2076 var next2_keyword = $next2.text().toUpperCase();
2077 var full2 = full + ' ' + next2_keyword;
2078 if (full2 in mysql_doc_keyword) {
2079 PMA_doc_add($elm, mysql_doc_keyword[full2]);
2080 PMA_doc_add($next, mysql_doc_keyword[full2]);
2081 PMA_doc_add($next2, mysql_doc_keyword[full2]);
2085 if (full in mysql_doc_keyword) {
2086 PMA_doc_add($elm, mysql_doc_keyword[full]);
2087 PMA_doc_add($next, mysql_doc_keyword[full]);
2091 if (keyword in mysql_doc_keyword) {
2092 PMA_doc_add($elm, mysql_doc_keyword[keyword]);
2097 * Generates doc links for builtins inside highlighted SQL
2099 function PMA_doc_builtin(idx, elm)
2102 var builtin = $elm.text().toUpperCase();
2103 if (builtin in mysql_doc_builtin) {
2104 PMA_doc_add($elm, mysql_doc_builtin[builtin]);
2109 * Higlights SQL using CodeMirror.
2111 function PMA_highlightSQL($base)
2113 var $elm = $base.find('code.sql');
2114 $elm.each(function () {
2116 var $pre = $sql.find('pre');
2117 /* We only care about visible elements to avoid double processing */
2118 if ($pre.is(":visible")) {
2119 var $highlight = $('<div class="sql-highlight cm-s-default"></div>');
2120 $sql.append($highlight);
2121 if (typeof CodeMirror != 'undefined') {
2122 CodeMirror.runMode($sql.text(), 'text/x-mysql', $highlight[0]);
2124 $highlight.find('.cm-keyword').each(PMA_doc_keyword);
2125 $highlight.find('.cm-builtin').each(PMA_doc_builtin);
2132 * Updates an element containing code.
2134 * @param jQuery Object $base base element which contains the raw and the
2137 * @param string htmlValue code in HTML format, displayed if code cannot be
2140 * @param string rawValue raw code, used as a parameter for highlighter
2142 * @return bool whether content was updated or not
2144 function PMA_updateCode($base, htmlValue, rawValue)
2146 var $code = $base.find('code');
2147 if ($code.length == 0) {
2151 // Determines the type of the content and appropriate CodeMirror mode.
2152 var type = '', mode = '';
2153 if ($code.hasClass('json')) {
2155 mode = 'application/json';
2156 } else if ($code.hasClass('sql')) {
2158 mode = 'text/x-mysql';
2159 } else if ($code.hasClass('xml')) {
2161 mode = 'application/xml';
2166 // Element used to display unhighlighted code.
2167 var $notHighlighted = $('<pre>' + htmlValue + '</pre>');
2169 // Tries to highlight code using CodeMirror.
2170 if (typeof CodeMirror != 'undefined') {
2171 var $highlighted = $('<div class="' + type + '-highlight cm-s-default"></div>');
2172 CodeMirror.runMode(rawValue, mode, $highlighted[0]);
2173 $notHighlighted.hide();
2174 $code.html('').append($notHighlighted, $highlighted[0]);
2176 $code.html('').append($notHighlighted);
2183 * Show a message on the top of the page for an Ajax request
2187 * 1) var $msg = PMA_ajaxShowMessage();
2188 * This will show a message that reads "Loading...". Such a message will not
2189 * disappear automatically and cannot be dismissed by the user. To remove this
2190 * message either the PMA_ajaxRemoveMessage($msg) function must be called or
2191 * another message must be show with PMA_ajaxShowMessage() function.
2193 * 2) var $msg = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
2194 * This is a special case. The behaviour is same as above,
2195 * just with a different message
2197 * 3) var $msg = PMA_ajaxShowMessage('The operation was successful');
2198 * This will show a message that will disappear automatically and it can also
2199 * be dismissed by the user.
2201 * 4) var $msg = PMA_ajaxShowMessage('Some error', false);
2202 * This will show a message that will not disappear automatically, but it
2203 * can be dismissed by the user after he has finished reading it.
2205 * @param string message string containing the message to be shown.
2206 * optional, defaults to 'Loading...'
2207 * @param mixed timeout number of milliseconds for the message to be visible
2208 * optional, defaults to 5000. If set to 'false', the
2209 * notification will never disappear
2210 * @return jQuery object jQuery Element that holds the message div
2211 * this object can be passed to PMA_ajaxRemoveMessage()
2212 * to remove the notification
2214 function PMA_ajaxShowMessage(message, timeout)
2217 * @var self_closing Whether the notification will automatically disappear
2219 var self_closing = true;
2221 * @var dismissable Whether the user will be able to remove
2222 * the notification by clicking on it
2224 var dismissable = true;
2225 // Handle the case when a empty data.message is passed.
2226 // We don't want the empty message
2227 if (message === '') {
2229 } else if (! message) {
2230 // If the message is undefined, show the default
2231 message = PMA_messages.strLoading;
2232 dismissable = false;
2233 self_closing = false;
2234 } else if (message == PMA_messages.strProcessingRequest) {
2235 // This is another case where the message should not disappear
2236 dismissable = false;
2237 self_closing = false;
2239 // Figure out whether (or after how long) to remove the notification
2240 if (timeout === undefined) {
2242 } else if (timeout === false) {
2243 self_closing = false;
2245 // Create a parent element for the AJAX messages, if necessary
2246 if ($('#loading_parent').length === 0) {
2247 $('<div id="loading_parent"></div>')
2248 .prependTo("#page_content");
2250 // Update message count to create distinct message elements every time
2251 ajax_message_count++;
2252 // Remove all old messages, if any
2253 $("span.ajax_notification[id^=ajax_message_num]").remove();
2255 * @var $retval a jQuery object containing the reference
2256 * to the created AJAX message
2259 '<span class="ajax_notification" id="ajax_message_num_' +
2260 ajax_message_count +
2264 .appendTo("#loading_parent")
2267 // If the notification is self-closing we should create a callback to remove it
2271 .fadeOut('medium', function () {
2272 if ($(this).is(':data(tooltip)')) {
2273 $(this).tooltip('destroy');
2275 // Remove the notification
2279 // If the notification is dismissable we need to add the relevant class to it
2280 // and add a tooltip so that the users know that it can be removed
2282 $retval.addClass('dismissable').css('cursor', 'pointer');
2284 * Add a tooltip to the notification to let the user know that (s)he
2285 * can dismiss the ajax notification by clicking on it.
2290 PMA_messages.strDismiss
2293 PMA_highlightSQL($retval);
2299 * Removes the message shown for an Ajax operation when it's completed
2301 * @param jQuery object jQuery Element that holds the notification
2305 function PMA_ajaxRemoveMessage($this_msgbox)
2307 if ($this_msgbox !== undefined && $this_msgbox instanceof jQuery) {
2311 if ($this_msgbox.is(':data(tooltip)')) {
2312 $this_msgbox.tooltip('destroy');
2314 $this_msgbox.remove();
2320 * Requests SQL for previewing before executing.
2322 * @param jQuery Object $form Form containing query data
2326 function PMA_previewSQL($form)
2328 var form_url = $form.attr('action');
2329 var form_data = $form.serialize() +
2333 var $msgbox = PMA_ajaxShowMessage();
2338 success: function (response) {
2339 PMA_ajaxRemoveMessage($msgbox);
2340 if (response.success) {
2341 var $dialog_content = $('<div/>')
2342 .append(response.sql_data);
2343 var button_options = {};
2344 button_options[PMA_messages.strClose] = function () {
2345 $(this).dialog('close');
2347 var $response_dialog = $dialog_content.dialog({
2351 buttons: button_options,
2352 title: PMA_messages.strPreviewSQL,
2353 close: function () {
2357 // Pretty SQL printing.
2358 PMA_highlightSQL($(this));
2362 PMA_ajaxShowMessage(response.message);
2365 error: function () {
2366 PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest);
2372 * check for reserved keyword column name
2374 * @param jQuery Object $form Form
2376 * @returns true|false
2379 function PMA_checkReservedWordColumns($form) {
2380 var is_confirmed = true;
2383 url: "tbl_structure.php",
2384 data: $form.serialize() + '&reserved_word_check=1',
2385 success: function (data) {
2386 if (typeof data.success != 'undefined' && data.success === true) {
2387 is_confirmed = confirm(data.message);
2392 return is_confirmed;
2395 // This event only need to be fired once after the initial page load
2398 * Allows the user to dismiss a notification
2399 * created with PMA_ajaxShowMessage()
2401 $(document).on('click', 'span.ajax_notification.dismissable', function () {
2402 PMA_ajaxRemoveMessage($(this));
2405 * The below two functions hide the "Dismiss notification" tooltip when a user
2406 * is hovering a link or button that is inside an ajax message
2408 $(document).on('mouseover', 'span.ajax_notification a, span.ajax_notification button, span.ajax_notification input', function () {
2409 if ($(this).parents('span.ajax_notification').is(':data(tooltip)')) {
2410 $(this).parents('span.ajax_notification').tooltip('disable');
2413 $(document).on('mouseout', 'span.ajax_notification a, span.ajax_notification button, span.ajax_notification input', function () {
2414 if ($(this).parents('span.ajax_notification').is(':data(tooltip)')) {
2415 $(this).parents('span.ajax_notification').tooltip('enable');
2421 * Hides/shows the "Open in ENUM/SET editor" message, depending on the data type of the column currently selected
2423 function PMA_showNoticeForEnum(selectElement)
2425 var enum_notice_id = selectElement.attr("id").split("_")[1];
2426 enum_notice_id += "_" + (parseInt(selectElement.attr("id").split("_")[2], 10) + 1);
2427 var selectedType = selectElement.val();
2428 if (selectedType == "ENUM" || selectedType == "SET") {
2429 $("p#enum_notice_" + enum_notice_id).show();
2431 $("p#enum_notice_" + enum_notice_id).hide();
2436 * Creates a Profiling Chart with jqplot. Used in sql.js
2437 * and in server_status_monitor.js
2439 function PMA_createProfilingChartJqplot(target, data)
2441 return $.jqplot(target, [data],
2444 renderer: $.jqplot.PieRenderer,
2446 showDataLabels: true
2451 tooltipLocation: 'se',
2453 tooltipAxes: 'pieref',
2454 useAxesFormatters: false,
2455 formatString: '%s, %.9Ps'
2460 rendererOptions: {numberColumns: 2}
2462 // from http://tango.freedesktop.org/Tango_Icon_Theme_Guidelines#Color_Palette
2488 * Formats a profiling duration nicely (in us and ms time).
2489 * Used in server_status_monitor.js
2491 * @param integer Number to be formatted, should be in the range of microsecond to second
2492 * @param integer Accuracy, how many numbers right to the comma should be
2493 * @return string The formatted number
2495 function PMA_prettyProfilingNum(num, acc)
2500 acc = Math.pow(10, acc);
2501 if (num * 1000 < 0.1) {
2502 num = Math.round(acc * (num * 1000 * 1000)) / acc + 'µ';
2503 } else if (num < 0.1) {
2504 num = Math.round(acc * (num * 1000)) / acc + 'm';
2506 num = Math.round(acc * num) / acc;
2514 * Formats a SQL Query nicely with newlines and indentation. Depends on Codemirror and MySQL Mode!
2516 * @param string Query to be formatted
2517 * @return string The formatted query
2519 function PMA_SQLPrettyPrint(string)
2521 if (typeof CodeMirror == 'undefined') {
2525 var mode = CodeMirror.getMode({}, "text/x-mysql");
2526 var stream = new CodeMirror.StringStream(string);
2527 var state = mode.startState();
2528 var token, tokens = [];
2530 var tabs = function (cnt) {
2532 for (var i = 0; i < 4 * cnt; i++) {
2538 // "root-level" statements
2540 'select': ['select', 'from', 'on', 'where', 'having', 'limit', 'order by', 'group by'],
2541 'update': ['update', 'set', 'where'],
2542 'insert into': ['insert into', 'values']
2544 // don't put spaces before these tokens
2545 var spaceExceptionsBefore = {';': true, ',': true, '.': true, '(': true};
2546 // don't put spaces after these tokens
2547 var spaceExceptionsAfter = {'.': true};
2549 // Populate tokens array
2551 while (! stream.eol()) {
2552 stream.start = stream.pos;
2553 token = mode.token(stream, state);
2554 if (token !== null) {
2555 tokens.push([token, stream.current().toLowerCase()]);
2559 var currentStatement = tokens[0][1];
2561 if (! statements[currentStatement]) {
2564 // Holds all currently opened code blocks (statement, function or generic)
2565 var blockStack = [];
2566 // Holds the type of block from last iteration (the current is in blockStack[0])
2568 // If a new code block is found, newBlock contains its type for one iteration and vice versa for endBlock
2569 var newBlock, endBlock;
2570 // How much to indent in the current line
2571 var indentLevel = 0;
2572 // Holds the "root-level" statements
2573 var statementPart, lastStatementPart = statements[currentStatement][0];
2575 blockStack.unshift('statement');
2577 // Iterate through every token and format accordingly
2578 for (var i = 0; i < tokens.length; i++) {
2579 previousBlock = blockStack[0];
2581 // New block => push to stack
2582 if (tokens[i][1] == '(') {
2583 if (i < tokens.length - 1 && tokens[i + 1][0] == 'statement-verb') {
2584 blockStack.unshift(newBlock = 'statement');
2585 } else if (i > 0 && tokens[i - 1][0] == 'builtin') {
2586 blockStack.unshift(newBlock = 'function');
2588 blockStack.unshift(newBlock = 'generic');
2594 // Block end => pop from stack
2595 if (tokens[i][1] == ')') {
2596 endBlock = blockStack[0];
2602 // A subquery is starting
2603 if (i > 0 && newBlock == 'statement') {
2605 output += "\n" + tabs(indentLevel) + tokens[i][1] + ' ' + tokens[i + 1][1].toUpperCase() + "\n" + tabs(indentLevel + 1);
2606 currentStatement = tokens[i + 1][1];
2611 // A subquery is ending
2612 if (endBlock == 'statement' && indentLevel > 0) {
2613 output += "\n" + tabs(indentLevel);
2617 // One less indentation for statement parts (from, where, order by, etc.) and a newline
2618 statementPart = statements[currentStatement].indexOf(tokens[i][1]);
2619 if (statementPart != -1) {
2623 output += tabs(indentLevel) + tokens[i][1].toUpperCase();
2624 output += "\n" + tabs(indentLevel + 1);
2625 lastStatementPart = tokens[i][1];
2627 // Normal indentation and spaces for everything else
2629 if (! spaceExceptionsBefore[tokens[i][1]] &&
2630 ! (i > 0 && spaceExceptionsAfter[tokens[i - 1][1]]) &&
2631 output.charAt(output.length - 1) != ' ') {
2634 if (tokens[i][0] == 'keyword') {
2635 output += tokens[i][1].toUpperCase();
2637 output += tokens[i][1];
2641 // split columns in select and 'update set' clauses, but only inside statements blocks
2642 if ((lastStatementPart == 'select' || lastStatementPart == 'where' || lastStatementPart == 'set') &&
2643 tokens[i][1] == ',' && blockStack[0] == 'statement') {
2645 output += "\n" + tabs(indentLevel + 1);
2648 // split conditions in where clauses, but only inside statements blocks
2649 if (lastStatementPart == 'where' &&
2650 (tokens[i][1] == 'and' || tokens[i][1] == 'or' || tokens[i][1] == 'xor')) {
2652 if (blockStack[0] == 'statement') {
2653 output += "\n" + tabs(indentLevel + 1);
2655 // Todo: Also split and or blocks in newlines & indentation++
2656 //if (blockStack[0] == 'generic')
2664 * jQuery function that uses jQueryUI's dialogs to confirm with user. Does not
2665 * return a jQuery object yet and hence cannot be chained
2667 * @param string question
2668 * @param string url URL to be passed to the callbackFn to make
2670 * @param function callbackFn callback to execute after user clicks on OK
2671 * @param function openCallback optional callback to run when dialog is shown
2674 jQuery.fn.PMA_confirm = function (question, url, callbackFn, openCallback) {
2675 var confirmState = PMA_commonParams.get('confirm');
2676 if (! confirmState) {
2677 // user does not want to confirm
2678 if ($.isFunction(callbackFn)) {
2679 callbackFn.call(this, url);
2683 if (PMA_messages.strDoYouReally === '') {
2688 * @var button_options Object that stores the options passed to jQueryUI
2691 var button_options = [
2693 text: PMA_messages.strOK,
2694 'class': 'submitOK',
2695 click: function () {
2696 $(this).dialog("close");
2697 if ($.isFunction(callbackFn)) {
2698 callbackFn.call(this, url);
2703 text: PMA_messages.strCancel,
2704 'class': 'submitCancel',
2705 click: function () {
2706 $(this).dialog("close");
2711 $('<div/>', {'id': 'confirm_dialog', 'title': PMA_messages.strConfirm})
2714 buttons: button_options,
2715 close: function () {
2724 * jQuery function to sort a table's body after a new row has been appended to it.
2725 * Also fixes the even/odd classes of the table rows at the end.
2727 * @param string text_selector string to select the sortKey's text
2729 * @return jQuery Object for chaining purposes
2731 jQuery.fn.PMA_sort_table = function (text_selector) {
2732 return this.each(function () {
2735 * @var table_body Object referring to the table's <tbody> element
2737 var table_body = $(this);
2739 * @var rows Object referring to the collection of rows in {@link table_body}
2741 var rows = $(this).find('tr').get();
2743 //get the text of the field that we will sort by
2744 $.each(rows, function (index, row) {
2745 row.sortKey = $.trim($(row).find(text_selector).text().toLowerCase());
2748 //get the sorted order
2749 rows.sort(function (a, b) {
2750 if (a.sortKey < b.sortKey) {
2753 if (a.sortKey > b.sortKey) {
2759 //pull out each row from the table and then append it according to it's order
2760 $.each(rows, function (index, row) {
2761 $(table_body).append(row);
2765 //Re-check the classes of each row
2766 $(this).find('tr:odd')
2767 .removeClass('even').addClass('odd')
2770 .removeClass('odd').addClass('even');
2775 * Unbind all event handlers before tearing down a page
2777 AJAX.registerTeardown('functions.js', function () {
2778 $(document).off('submit', "#create_table_form_minimal.ajax");
2779 $(document).off('submit', "form.create_table_form.ajax");
2780 $(document).off('click', "form.create_table_form.ajax input[name=submit_num_fields]");
2781 $(document).off('keyup', "form.create_table_form.ajax input");
2785 * jQuery coding for 'Create Table'. Used on db_operations.php,
2786 * db_structure.php and db_tracking.php (i.e., wherever
2787 * libraries/display_create_table.lib.php is used)
2789 * Attach Ajax Event handlers for Create Table
2791 AJAX.registerOnload('functions.js', function () {
2793 * Attach event handler for submission of create table form (save)
2795 $(document).on('submit', "form.create_table_form.ajax", function (event) {
2796 event.preventDefault();
2799 * @var the_form object referring to the create table form
2801 var $form = $(this);
2804 * First validate the form; if there is a problem, avoid submitting it
2806 * checkTableEditForm() needs a pure element and not a jQuery object,
2807 * this is why we pass $form[0] as a parameter (the jQuery object
2808 * is actually an array of DOM elements)
2811 if (checkTableEditForm($form[0], $form.find('input[name=orig_num_fields]').val())) {
2812 PMA_prepareForAjaxRequest($form);
2813 if (PMA_checkReservedWordColumns($form)) {
2814 PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
2815 //User wants to submit the form
2816 $.post($form.attr('action'), $form.serialize() + "&do_save_data=1", function (data) {
2817 if (typeof data !== 'undefined' && data.success === true) {
2818 $('#properties_message')
2819 .removeClass('error')
2821 PMA_ajaxShowMessage(data.message);
2822 // Only if the create table dialog (distinct panel) exists
2823 var $createTableDialog = $("#create_table_dialog");
2824 if ($createTableDialog.length > 0) {
2825 $createTableDialog.dialog("close").remove();
2827 $('#tableslistcontainer').before(data.formatted_sql);
2830 * @var tables_table Object referring to the <tbody> element that holds the list of tables
2832 var tables_table = $("#tablesForm").find("tbody").not("#tbl_summary_row");
2833 // this is the first table created in this db
2834 if (tables_table.length === 0) {
2835 PMA_commonActions.refreshMain(
2836 PMA_commonParams.get('opendb_url')
2840 * @var curr_last_row Object referring to the last <tr> element in {@link tables_table}
2842 var curr_last_row = $(tables_table).find('tr:last');
2844 * @var curr_last_row_index_string String containing the index of {@link curr_last_row}
2846 var curr_last_row_index_string = $(curr_last_row).find('input:checkbox').attr('id').match(/\d+/)[0];
2848 * @var curr_last_row_index Index of {@link curr_last_row}
2850 var curr_last_row_index = parseFloat(curr_last_row_index_string);
2852 * @var new_last_row_index Index of the new row to be appended to {@link tables_table}
2854 var new_last_row_index = curr_last_row_index + 1;
2856 * @var new_last_row_id String containing the id of the row to be appended to {@link tables_table}
2858 var new_last_row_id = 'checkbox_tbl_' + new_last_row_index;
2860 data.new_table_string = data.new_table_string.replace(/checkbox_tbl_/, new_last_row_id);
2862 $(data.new_table_string)
2863 .appendTo(tables_table);
2866 $(tables_table).PMA_sort_table('th');
2868 // Adjust summary row
2872 //Refresh navigation as a new table has been added
2873 PMA_reloadNavigation();
2874 // Redirect to table structure page on creation of new table
2875 var params_12 = 'ajax_request=true&ajax_page_request=true';
2876 if (! (history && history.pushState)) {
2877 params_12 += PMA_MicroHistory.menus.getRequestParam();
2879 tblStruct_url = 'tbl_structure.php?server=' + data._params.server +
2880 '&db='+ data._params.db + '&token=' + data._params.token +
2881 '&goto=db_structure.php&table=' + data._params.table + '';
2882 $.get(tblStruct_url, params_12, AJAX.responseHandler);
2884 PMA_ajaxShowMessage(
2885 '<div class="error">' + data.error + '</div>',
2891 } // end if (checkTableEditForm() )
2892 }); // end create table form (save)
2895 * Attach event handler for create table form (add fields)
2897 $(document).on('click', "form.create_table_form.ajax input[name=submit_num_fields]", function (event) {
2898 event.preventDefault();
2900 * @var the_form object referring to the create table form
2902 var $form = $(this).closest('form');
2904 if (!checkFormElementInRange(this.form, 'added_fields', PMA_messages.strLeastColumnError, 1)) {
2908 var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
2909 PMA_prepareForAjaxRequest($form);
2911 //User wants to add more fields to the table
2912 $.post($form.attr('action'), $form.serialize() + "&submit_num_fields=1", function (data) {
2913 if (typeof data !== 'undefined' && data.success) {
2914 var $pageContent = $("#page_content");
2915 $pageContent.html(data.message);
2916 PMA_highlightSQL($pageContent);
2917 PMA_verifyColumnsProperties();
2918 PMA_hideShowConnection($('.create_table_form select[name=tbl_storage_engine]'));
2919 PMA_ajaxRemoveMessage($msgbox);
2921 PMA_ajaxShowMessage(data.error);
2924 }); // end create table form (add fields)
2926 $(document).on('keydown', "form.create_table_form.ajax input[name=added_fields]", function (event) {
2927 if (event.keyCode == 13) {
2928 event.preventDefault();
2929 event.stopImmediatePropagation();
2932 .find('input[name=submit_num_fields]')
2936 $("input[value=AUTO_INCREMENT]").change(function(){
2938 var col = /\d/.exec($(this).attr('name'));
2940 var $selectFieldKey = $('select[name="field_key[' + col + ']"]');
2941 if ($selectFieldKey.val() === 'none_'+col) {
2942 $selectFieldKey.val('primary_'+col).change();
2947 .off('click', 'input.preview_sql')
2948 .on('click', 'input.preview_sql', function () {
2949 var $form = $(this).closest('form');
2950 PMA_previewSQL($form);
2956 * Validates the password field in a form
2958 * @see PMA_messages.strPasswordEmpty
2959 * @see PMA_messages.strPasswordNotSame
2960 * @param object $the_form The form to be validated
2963 function PMA_checkPassword($the_form)
2965 // Did the user select 'no password'?
2966 if ($the_form.find('#nopass_1').is(':checked')) {
2969 var $pred = $the_form.find('#select_pred_password');
2970 if ($pred.length && ($pred.val() == 'none' || $pred.val() == 'keep')) {
2975 var $password = $the_form.find('input[name=pma_pw]');
2976 var $password_repeat = $the_form.find('input[name=pma_pw2]');
2977 var alert_msg = false;
2979 if ($password.val() === '') {
2980 alert_msg = PMA_messages.strPasswordEmpty;
2981 } else if ($password.val() != $password_repeat.val()) {
2982 alert_msg = PMA_messages.strPasswordNotSame;
2988 $password_repeat.val('');
2996 * Unbind all event handlers before tearing down a page
2998 AJAX.registerTeardown('functions.js', function () {
2999 $(document).off('click', '#change_password_anchor.ajax');
3002 * Attach Ajax event handlers for 'Change Password' on index.php
3004 AJAX.registerOnload('functions.js', function () {
3007 * Attach Ajax event handler on the change password anchor
3009 $(document).on('click', '#change_password_anchor.ajax', function (event) {
3010 event.preventDefault();
3012 var $msgbox = PMA_ajaxShowMessage();
3015 * @var button_options Object containing options to be passed to jQueryUI's dialog
3017 var button_options = {};
3018 button_options[PMA_messages.strGo] = function () {
3020 event.preventDefault();
3023 * @var $the_form Object referring to the change password form
3025 var $the_form = $("#change_password_form");
3027 if (! PMA_checkPassword($the_form)) {
3032 * @var this_value String containing the value of the submit button.
3033 * Need to append this for the change password form on Server Privileges
3036 var this_value = $(this).val();
3038 var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
3039 $the_form.append('<input type="hidden" name="ajax_request" value="true" />');
3041 $.post($the_form.attr('action'), $the_form.serialize() + '&change_pw=' + this_value, function (data) {
3042 if (typeof data === 'undefined' || data.success !== true) {
3043 PMA_ajaxShowMessage(data.error, false);
3047 var $pageContent = $("#page_content");
3048 $pageContent.prepend(data.message);
3049 PMA_highlightSQL($pageContent);
3050 $("#change_password_dialog").hide().remove();
3051 $("#edit_user_dialog").dialog("close").remove();
3052 PMA_ajaxRemoveMessage($msgbox);
3056 button_options[PMA_messages.strCancel] = function () {
3057 $(this).dialog('close');
3059 $.get($(this).attr('href'), {'ajax_request': true}, function (data) {
3060 if (typeof data === 'undefined' || !data.success) {
3061 PMA_ajaxShowMessage(data.error, false);
3065 $('<div id="change_password_dialog"></div>')
3067 title: PMA_messages.strChangePassword,
3069 close: function (ev, ui) {
3072 buttons: button_options,
3075 .append(data.message);
3076 // for this dialog, we remove the fieldset wrapping due to double headings
3077 $("fieldset#fieldset_change_password")
3078 .find("legend").remove().end()
3079 .find("table.noclick").unwrap().addClass("some-margin")
3080 .find("input#text_pma_pw").focus();
3081 displayPasswordGenerateButton();
3082 $('#fieldset_change_password_footer').hide();
3083 PMA_ajaxRemoveMessage($msgbox);
3084 $('#change_password_form').bind('submit', function (e) {
3087 .closest('.ui-dialog')
3088 .find('.ui-dialog-buttonpane .ui-button')
3093 }); // end handler for change password anchor
3094 }); // end $() for Change Password
3097 * Unbind all event handlers before tearing down a page
3099 AJAX.registerTeardown('functions.js', function () {
3100 $(document).off('change', "select.column_type");
3101 $(document).off('change', "select.default_type");
3102 $(document).off('change', "select.virtuality");
3103 $(document).off('change', 'input.allow_null');
3104 $(document).off('change', '.create_table_form select[name=tbl_storage_engine]');
3107 * Toggle the hiding/showing of the "Open in ENUM/SET editor" message when
3108 * the page loads and when the selected data type changes
3110 AJAX.registerOnload('functions.js', function () {
3111 // is called here for normal page loads and also when opening
3112 // the Create table dialog
3113 PMA_verifyColumnsProperties();
3115 // needs on() to work also in the Create Table dialog
3116 $(document).on('change', "select.column_type", function () {
3117 PMA_showNoticeForEnum($(this));
3119 $(document).on('change', "select.default_type", function () {
3120 PMA_hideShowDefaultValue($(this));
3122 $(document).on('change', "select.virtuality", function () {
3123 PMA_hideShowExpression($(this));
3125 $(document).on('change', 'input.allow_null', function () {
3126 PMA_validateDefaultValue($(this));
3128 $(document).on('change', '.create_table_form select[name=tbl_storage_engine]', function () {
3129 PMA_hideShowConnection($(this));
3134 * If the chosen storage engine is FEDERATED show connection field. Hide otherwise
3136 * @param $engine_selector storage engine selector
3138 function PMA_hideShowConnection($engine_selector)
3140 var $connection = $('.create_table_form input[name=connection]');
3141 var index = $connection.parent('td').index() + 1;
3142 var $labelTh = $connection.parents('tr').prev('tr').children('th:nth-child(' + index + ')');
3143 if ($engine_selector.val() != 'FEDERATED') {
3145 .prop('disabled', true)
3146 .parent('td').hide();
3150 .prop('disabled', false)
3151 .parent('td').show();
3157 * If the column does not allow NULL values, makes sure that default is not NULL
3159 function PMA_validateDefaultValue($null_checkbox)
3161 if (! $null_checkbox.prop('checked')) {
3162 var $default = $null_checkbox.closest('tr').find('.default_type');
3163 if ($default.val() == 'NULL') {
3164 $default.val('NONE');
3170 * function to populate the input fields on picking a column from central list
3172 * @param string input_id input id of the name field for the column to be populated
3173 * @param integer offset of the selected column in central list of columns
3175 function autoPopulate(input_id, offset)
3177 var db = PMA_commonParams.get('db');
3178 var table = PMA_commonParams.get('table');
3179 input_id = input_id.substring(0, input_id.length - 1);
3180 $('#' + input_id + '1').val(central_column_list[db + '_' + table][offset].col_name);
3181 var col_type = central_column_list[db + '_' + table][offset].col_type.toUpperCase();
3182 $('#' + input_id + '2').val(col_type);
3183 var $input3 = $('#' + input_id + '3');
3184 $input3.val(central_column_list[db + '_' + table][offset].col_length);
3185 if(col_type === 'ENUM' || col_type === 'SET') {
3186 $input3.next().show();
3188 $input3.next().hide();
3190 var col_default = central_column_list[db + '_' + table][offset].col_default.toUpperCase();
3191 var $input4 = $('#' + input_id + '4');
3192 if (col_default !== '' && col_default !== 'NULL' && col_default !== 'CURRENT_TIMESTAMP') {
3193 $input4.val("USER_DEFINED");
3194 $input4.next().next().show();
3195 $input4.next().next().val(central_column_list[db + '_' + table][offset].col_default);
3197 $input4.val(central_column_list[db + '_' + table][offset].col_default);
3198 $input4.next().next().hide();
3200 $('#' + input_id + '5').val(central_column_list[db + '_' + table][offset].col_collation);
3201 var $input6 = $('#' + input_id + '6');
3202 $input6.val(central_column_list[db + '_' + table][offset].col_attribute);
3203 if(central_column_list[db + '_' + table][offset].col_extra === 'on update CURRENT_TIMESTAMP') {
3204 $input6.val(central_column_list[db + '_' + table][offset].col_extra);
3206 if(central_column_list[db + '_' + table][offset].col_extra.toUpperCase() === 'AUTO_INCREMENT') {
3207 $('#' + input_id + '9').prop("checked",true).change();
3209 $('#' + input_id + '9').prop("checked",false);
3211 if(central_column_list[db + '_' + table][offset].col_isNull !== '0') {
3212 $('#' + input_id + '7').prop("checked",true);
3214 $('#' + input_id + '7').prop("checked",false);
3219 * Unbind all event handlers before tearing down a page
3221 AJAX.registerTeardown('functions.js', function () {
3222 $(document).off('click', "a.open_enum_editor");
3223 $(document).off('click', "input.add_value");
3224 $(document).off('click', "#enum_editor td.drop");
3225 $(document).off('click', 'a.central_columns_dialog');
3228 * @var $enum_editor_dialog An object that points to the jQuery
3229 * dialog of the ENUM/SET editor
3231 var $enum_editor_dialog = null;
3233 * Opens the ENUM/SET editor and controls its functions
3235 AJAX.registerOnload('functions.js', function () {
3236 $(document).on('click', "a.open_enum_editor", function () {
3237 // Get the name of the column that is being edited
3238 var colname = $(this).closest('tr').find('input:first').val();
3241 // And use it to make up a title for the page
3242 if (colname.length < 1) {
3243 title = PMA_messages.enum_newColumnVals;
3245 title = PMA_messages.enum_columnVals.replace(
3247 '"' + escapeHtml(decodeURIComponent(colname)) + '"'
3250 // Get the values as a string
3251 var inputstring = $(this)
3255 // Escape html entities
3256 inputstring = $('<div/>')
3259 // Parse the values, escaping quotes and
3260 // slashes on the fly, into an array
3262 var in_string = false;
3263 var curr, next, buffer = '';
3264 for (i = 0; i < inputstring.length; i++) {
3265 curr = inputstring.charAt(i);
3266 next = i == inputstring.length ? '' : inputstring.charAt(i + 1);
3267 if (! in_string && curr == "'") {
3269 } else if (in_string && curr == "\\" && next == "\\") {
3272 } else if (in_string && next == "'" && (curr == "'" || curr == "\\")) {
3275 } else if (in_string && curr == "'") {
3277 values.push(buffer);
3279 } else if (in_string) {
3283 if (buffer.length > 0) {
3284 // The leftovers in the buffer are the last value (if any)
3285 values.push(buffer);
3288 // If there are no values, maybe the user is about to make a
3289 // new list so we add a few for him/her to get started with.
3290 if (values.length === 0) {
3291 values.push('', '', '', '');
3293 // Add the parsed values to the editor
3294 var drop_icon = PMA_getImage('b_drop.png');
3295 for (i = 0; i < values.length; i++) {
3296 fields += "<tr><td>" +
3297 "<input type='text' value='" + values[i] + "'/>" +
3298 "</td><td class='drop'>" +
3303 * @var dialog HTML code for the ENUM/SET dialog
3305 var dialog = "<div id='enum_editor'>" +
3307 "<legend>" + title + "</legend>" +
3308 "<p>" + PMA_getImage('s_notice.png') +
3309 PMA_messages.enum_hint + "</p>" +
3310 "<table class='values'>" + fields + "</table>" +
3311 "</fieldset><fieldset class='tblFooters'>" +
3312 "<table class='add'><tr><td>" +
3313 "<div class='slider'></div>" +
3315 "<form><div><input type='submit' class='add_value' value='" +
3316 PMA_sprintf(PMA_messages.enum_addValue, 1) +
3317 "'/></div></form>" +
3318 "</td></tr></table>" +
3319 "<input type='hidden' value='" + // So we know which column's data is being edited
3320 $(this).closest('td').find("input").attr("id") +
3325 * @var Defines functions to be called when the buttons in
3326 * the buttonOptions jQuery dialog bar are pressed
3328 var buttonOptions = {};
3329 buttonOptions[PMA_messages.strGo] = function () {
3330 // When the submit button is clicked,
3331 // put the data back into the original form
3332 var value_array = [];
3333 $(this).find(".values input").each(function (index, elm) {
3334 var val = elm.value.replace(/\\/g, '\\\\').replace(/'/g, "''");
3335 value_array.push("'" + val + "'");
3337 // get the Length/Values text field where this value belongs
3338 var values_id = $(this).find("input[type='hidden']").val();
3339 $("input#" + values_id).val(value_array.join(","));
3340 $(this).dialog("close");
3342 buttonOptions[PMA_messages.strClose] = function () {
3343 $(this).dialog("close");
3346 var width = parseInt(
3347 (parseInt($('html').css('font-size'), 10) / 13) * 340,
3353 $enum_editor_dialog = $(dialog).dialog({
3357 title: PMA_messages.enum_editor,
3358 buttons: buttonOptions,
3360 // Focus the "Go" button after opening the dialog
3361 $(this).closest('.ui-dialog').find('.ui-dialog-buttonpane button:first').focus();
3363 close: function () {
3367 // slider for choosing how many fields to add
3368 $enum_editor_dialog.find(".slider").slider({
3374 slide: function (event, ui) {
3375 $(this).closest('table').find('input[type=submit]').val(
3376 PMA_sprintf(PMA_messages.enum_addValue, ui.value)
3380 // Focus the slider, otherwise it looks nearly transparent
3381 $('a.ui-slider-handle').addClass('ui-state-focus');
3385 $(document).on('click', 'a.central_columns_dialog', function (e) {
3386 var href = "db_central_columns.php";
3387 var db = PMA_commonParams.get('db');
3388 var table = PMA_commonParams.get('table');
3389 var maxRows = $(this).data('maxrows');
3390 var pick = $(this).data('pick');
3391 if (pick !== false) {
3395 'ajax_request' : true,
3396 'token' : PMA_commonParams.get('token'),
3397 'server' : PMA_commonParams.get('server'),
3398 'db' : PMA_commonParams.get('db'),
3399 'cur_table' : PMA_commonParams.get('table'),
3400 'getColumnList':true
3402 var colid = $(this).closest('td').find("input").attr("id");
3404 if (! (db + '_' + table in central_column_list)) {
3405 central_column_list.push(db + '_' + table);
3410 success: function (data) {
3411 central_column_list[db + '_' + table] = $.parseJSON(data.message);
3417 var list_size = central_column_list[db + '_' + table].length;
3418 var min = (list_size <= maxRows) ? list_size : maxRows;
3419 for (i = 0; i < min; i++) {
3421 fields += '<tr><td><div><span style="font-weight:bold">' +
3422 escapeHtml(central_column_list[db + '_' + table][i].col_name) +
3423 '</span><br><span style="color:gray">' + central_column_list[db + '_' + table][i].col_type;
3425 if (central_column_list[db + '_' + table][i].col_attribute !== '') {
3426 fields += '(' + escapeHtml(central_column_list[db + '_' + table][i].col_attribute) + ') ';
3428 if (central_column_list[db + '_' + table][i].col_length !== '') {
3429 fields += '(' + escapeHtml(central_column_list[db + '_' + table][i].col_length) +') ';
3431 fields += escapeHtml(central_column_list[db + '_' + table][i].col_extra) + '</span>' +
3434 fields += '<td><input class="pick" style="width:100%" type="submit" value="' +
3435 PMA_messages.pickColumn + '" onclick="autoPopulate(\'' + colid + '\',' + i + ')"/></td>';
3439 var result_pointer = i;
3440 var search_in = '<input type="text" class="filter_rows" placeholder="' + PMA_messages.searchList + '">';
3441 if (fields === '') {
3442 fields = PMA_sprintf(PMA_messages.strEmptyCentralList, "'" + db + "'");
3446 if (list_size > maxRows) {
3447 seeMore = "<fieldset class='tblFooters' style='text-align:center;font-weight:bold'>" +
3448 "<a href='#' id='seeMore'>" + PMA_messages.seeMore + "</a></fieldset>";
3450 var central_columns_dialog = "<div style='max-height:400px'>" +
3453 "<table id='col_list' style='width:100%' class='values'>" + fields + "</table>" +
3458 var width = parseInt(
3459 (parseInt($('html').css('font-size'), 10) / 13) * 500,
3465 var buttonOptions = {};
3466 var $central_columns_dialog = $(central_columns_dialog).dialog({
3470 title: PMA_messages.pickColumnTitle,
3471 buttons: buttonOptions,
3473 $('#col_list').on("click", ".pick", function (){
3474 $central_columns_dialog.remove();
3476 $(".filter_rows").on("keyup", function () {
3477 $.uiTableFilter($("#col_list"), $(this).val());
3479 $("#seeMore").click(function() {
3481 min = (list_size <= maxRows + result_pointer) ? list_size : maxRows + result_pointer;
3482 for (i = result_pointer; i < min; i++) {
3484 fields += '<tr><td><div><span style="font-weight:bold">' +
3485 central_column_list[db + '_' + table][i].col_name +
3486 '</span><br><span style="color:gray">' +
3487 central_column_list[db + '_' + table][i].col_type;
3489 if (central_column_list[db + '_' + table][i].col_attribute !== '') {
3490 fields += '(' + central_column_list[db + '_' + table][i].col_attribute + ') ';
3492 if (central_column_list[db + '_' + table][i].col_length !== '') {
3493 fields += '(' + central_column_list[db + '_' + table][i].col_length + ') ';
3495 fields += central_column_list[db + '_' + table][i].col_extra + '</span>' +
3498 fields += '<td><input class="pick" style="width:100%" type="submit" value="' +
3499 PMA_messages.pickColumn + '" onclick="autoPopulate(\'' + colid + '\',' + i + ')"/></td>';
3503 $("#col_list").append(fields);
3505 if (result_pointer === list_size) {
3506 $('.tblFooters').hide();
3510 $(this).closest('.ui-dialog').find('.ui-dialog-buttonpane button:first').focus();
3512 close: function () {
3513 $('#col_list').off("click", ".pick");
3514 $(".filter_rows").off("keyup");
3521 // $(document).on('click', 'a.show_central_list',function(e) {
3524 // When "add a new value" is clicked, append an empty text field
3525 $(document).on('click', "input.add_value", function (e) {
3527 var num_new_rows = $enum_editor_dialog.find("div.slider").slider('value');
3528 while (num_new_rows--) {
3529 $enum_editor_dialog.find('.values')
3531 "<tr style='display: none;'><td>" +
3532 "<input type='text' />" +
3533 "</td><td class='drop'>" +
3534 PMA_getImage('b_drop.png') +
3542 // Removes the specified row from the enum editor
3543 $(document).on('click', "#enum_editor td.drop", function () {
3544 $(this).closest('tr').hide('fast', function () {
3551 * Ensures indexes names are valid according to their type and, for a primary
3552 * key, lock index name to 'PRIMARY'
3553 * @param string form_id Variable which parses the form name as
3555 * @return boolean false if there is no index form, true else
3557 function checkIndexName(form_id)
3559 if ($("#" + form_id).length === 0) {
3563 // Gets the elements pointers
3564 var $the_idx_name = $("#input_index_name");
3565 var $the_idx_choice = $("#select_index_choice");
3567 // Index is a primary key
3568 if ($the_idx_choice.find("option:selected").val() == 'PRIMARY') {
3569 $the_idx_name.val('PRIMARY');
3570 $the_idx_name.prop("disabled", true);
3575 if ($the_idx_name.val() == 'PRIMARY') {
3576 $the_idx_name.val("");
3578 $the_idx_name.prop("disabled", false);
3582 } // end of the 'checkIndexName()' function
3584 AJAX.registerTeardown('functions.js', function () {
3585 $(document).off('click', '#index_frm input[type=submit]');
3587 AJAX.registerOnload('functions.js', function () {
3589 * Handler for adding more columns to an index in the editor
3591 $(document).on('click', '#index_frm input[type=submit]', function (event) {
3592 event.preventDefault();
3593 var rows_to_add = $(this)
3594 .closest('fieldset')
3598 var tempEmptyVal = function () {
3602 var tempSetFocus = function () {
3603 if ($(this).find("option:selected").val() === '') {
3606 $(this).closest("tr").find("input").focus();
3609 while (rows_to_add--) {
3610 var $indexColumns = $('#index_columns');
3611 var $newrow = $indexColumns
3612 .find('tbody > tr:first')
3615 $indexColumns.find('tbody')
3617 $newrow.find(':input').each(tempEmptyVal);
3618 // focus index size input on column picked
3619 $newrow.find('select').change(tempSetFocus);
3624 function indexEditorDialog(url, title, callback_success, callback_failure)
3626 /*Remove the hidden dialogs if there are*/
3627 var $editIndexDialog = $('#edit_index_dialog');
3628 if ($editIndexDialog.length !== 0) {
3629 $editIndexDialog.remove();
3631 var $div = $('<div id="edit_index_dialog"></div>');
3634 * @var button_options Object that stores the options
3635 * passed to jQueryUI dialog
3637 var button_options = {};
3638 button_options[PMA_messages.strGo] = function () {
3640 * @var the_form object referring to the export form
3642 var $form = $("#index_frm");
3643 var $msgbox = PMA_ajaxShowMessage(PMA_messages.strProcessingRequest);
3644 PMA_prepareForAjaxRequest($form);
3645 //User wants to submit the form
3646 $.post($form.attr('action'), $form.serialize() + "&do_save_data=1", function (data) {
3647 var $sqlqueryresults = $(".sqlqueryresults");
3648 if ($sqlqueryresults.length !== 0) {
3649 $sqlqueryresults.remove();
3651 if (typeof data !== 'undefined' && data.success === true) {
3652 PMA_ajaxShowMessage(data.message);
3653 var $resultQuery = $('.result_query');
3654 if ($resultQuery.length) {
3655 $resultQuery.remove();
3657 if (data.sql_query) {
3658 $('<div class="result_query"></div>')
3659 .html(data.sql_query)
3660 .prependTo('#page_content');
3661 PMA_highlightSQL($('#page_content'));
3663 $(".result_query .notice").remove();
3664 $resultQuery.prepend(data.message);
3665 /*Reload the field form*/
3666 $("#table_index").remove();
3667 $("<div id='temp_div'><div>")
3668 .append(data.index_table)
3669 .find("#table_index")
3670 .insertAfter("#index_header");
3671 var $editIndexDialog = $("#edit_index_dialog");
3672 if ($editIndexDialog.length > 0) {
3673 $editIndexDialog.dialog("close");
3675 $('div.no_indexes_defined').hide();
3676 if (callback_success) {
3679 PMA_reloadNavigation();
3681 var $temp_div = $("<div id='temp_div'><div>").append(data.error);
3683 if ($temp_div.find(".error code").length !== 0) {
3684 $error = $temp_div.find(".error code").addClass("error");
3688 if (callback_failure) {
3691 PMA_ajaxShowMessage($error, false);
3695 button_options[PMA_messages.strPreviewSQL] = function () {
3696 // Function for Previewing SQL
3697 var $form = $('#index_frm');
3698 PMA_previewSQL($form);
3700 button_options[PMA_messages.strCancel] = function () {
3701 $(this).dialog('close');
3703 var $msgbox = PMA_ajaxShowMessage();
3704 $.get("tbl_indexes.php", url, function (data) {
3705 if (typeof data !== 'undefined' && data.success === false) {
3706 //in the case of an error, show the error message returned.
3707 PMA_ajaxShowMessage(data.error, false);
3709 PMA_ajaxRemoveMessage($msgbox);
3710 // Show dialog if the request was successful
3712 .append(data.message)
3717 open: PMA_verifyColumnsProperties,
3719 buttons: button_options,
3720 close: function () {
3724 $div.find('.tblFooters').remove();
3725 showIndexEditDialog($div);
3730 function showIndexEditDialog($outer)
3733 checkIndexName("index_frm");
3734 var $indexColumns = $('#index_columns');
3735 $indexColumns.find('td').each(function () {
3736 $(this).css("width", $(this).width() + 'px');
3738 $indexColumns.find('tbody').sortable({
3740 containment: $indexColumns.find("tbody"),
3741 tolerance: 'pointer'
3743 PMA_showHints($outer);
3745 // Add a slider for selecting how many columns to add to the index
3746 $outer.find('.slider').slider({
3751 slide: function (event, ui) {
3752 $(this).closest('fieldset').find('input[type=submit]').val(
3753 PMA_sprintf(PMA_messages.strAddToIndex, ui.value)
3757 $('div.add_fields').removeClass('hide');
3758 // focus index size input on column picked
3759 $outer.find('table#index_columns select').change(function () {
3760 if ($(this).find("option:selected").val() === '') {
3763 $(this).closest("tr").find("input").focus();
3765 // Focus the slider, otherwise it looks nearly transparent
3766 $('a.ui-slider-handle').addClass('ui-state-focus');
3767 // set focus on index name input, if empty
3768 var input = $outer.find('input#input_index_name');
3769 input.val() || input.focus();
3773 * Function to display tooltips that were
3774 * generated on the PHP side by PMA_Util::showHint()
3776 * @param object $div a div jquery object which specifies the
3777 * domain for searching for tooltips. If we
3778 * omit this parameter the function searches
3781 function PMA_showHints($div)
3783 if ($div === undefined || ! $div instanceof jQuery || $div.length === 0) {
3786 $div.find('.pma_hint').each(function () {
3788 $(this).children('img'),
3790 $(this).children('span').html()
3795 AJAX.registerOnload('functions.js', function () {
3799 function PMA_mainMenuResizerCallback() {
3800 // 5 px margin for jumping menu in Chrome
3801 return $(document.body).width() - 5;
3803 // This must be fired only once after the initial page load
3805 // Initialise the menu resize plugin
3806 $('#topmenu').menuResizer(PMA_mainMenuResizerCallback);
3807 // register resize event
3808 $(window).resize(function () {
3809 $('#topmenu').menuResizer('resize');
3814 * Get the row number from the classlist (for example, row_1)
3816 function PMA_getRowNumber(classlist)
3818 return parseInt(classlist.split(/\s+row_/)[1], 10);
3822 * Changes status of slider
3824 function PMA_set_status_label($element)
3827 if ($element.css('display') == 'none') {
3832 $element.closest('.slide-wrapper').prev().find('span').text(text);
3836 * var toggleButton This is a function that creates a toggle
3837 * sliding button given a jQuery reference
3838 * to the correct DOM element
3840 var toggleButton = function ($obj) {
3841 // In rtl mode the toggle switch is flipped horizontally
3842 // so we need to take that into account
3844 if ($('span.text_direction', $obj).text() == 'ltr') {
3850 * var h Height of the button, used to scale the
3851 * background image and position the layers
3853 var h = $obj.height();
3854 $('img', $obj).height(h);
3855 $('table', $obj).css('bottom', h - 1);
3857 * var on Width of the "ON" part of the toggle switch
3858 * var off Width of the "OFF" part of the toggle switch
3860 var on = $('td.toggleOn', $obj).width();
3861 var off = $('td.toggleOff', $obj).width();
3862 // Make the "ON" and "OFF" parts of the switch the same size
3863 // + 2 pixels to avoid overflowed
3864 $('td.toggleOn > div', $obj).width(Math.max(on, off) + 2);
3865 $('td.toggleOff > div', $obj).width(Math.max(on, off) + 2);
3867 * var w Width of the central part of the switch
3869 var w = parseInt(($('img', $obj).height() / 16) * 22, 10);
3870 // Resize the central part of the switch on the top
3871 // layer to match the background
3872 $('table td:nth-child(2) > div', $obj).width(w);
3874 * var imgw Width of the background image
3875 * var tblw Width of the foreground layer
3876 * var offset By how many pixels to move the background
3877 * image, so that it matches the top layer
3879 var imgw = $('img', $obj).width();
3880 var tblw = $('table', $obj).width();
3881 var offset = parseInt(((imgw - tblw) / 2), 10);
3882 // Move the background to match the layout of the top layer
3883 $obj.find('img').css(right, offset);
3885 * var offw Outer width of the "ON" part of the toggle switch
3886 * var btnw Outer width of the central part of the switch
3888 var offw = $('td.toggleOff', $obj).outerWidth();
3889 var btnw = $('table td:nth-child(2)', $obj).outerWidth();
3890 // Resize the main div so that exactly one side of
3891 // the switch plus the central part fit into it.
3892 $obj.width(offw + btnw + 2);
3894 * var move How many pixels to move the
3895 * switch by when toggling
3897 var move = $('td.toggleOff', $obj).outerWidth();
3898 // If the switch is initialized to the
3899 // OFF state we need to move it now.
3900 if ($('div.container', $obj).hasClass('off')) {
3901 if (right == 'right') {
3902 $('div.container', $obj).animate({'left': '-=' + move + 'px'}, 0);
3904 $('div.container', $obj).animate({'left': '+=' + move + 'px'}, 0);
3907 // Attach an 'onclick' event to the switch
3908 $('div.container', $obj).click(function () {
3909 if ($(this).hasClass('isActive')) {
3912 $(this).addClass('isActive');
3914 var $msg = PMA_ajaxShowMessage();
3915 var $container = $(this);
3916 var callback = $('span.callback', this).text();
3917 var operator, url, removeClass, addClass;
3918 // Perform the actual toggle
3919 if ($(this).hasClass('on')) {
3920 if (right == 'right') {
3925 url = $(this).find('td.toggleOff > span').text();
3929 if (right == 'right') {
3934 url = $(this).find('td.toggleOn > span').text();
3935 removeClass = 'off';
3938 $.post(url, {'ajax_request': true}, function (data) {
3939 if (typeof data !== 'undefined' && data.success === true) {
3940 PMA_ajaxRemoveMessage($msg);
3942 .removeClass(removeClass)
3944 .animate({'left': operator + move + 'px'}, function () {
3945 $container.removeClass('isActive');
3949 PMA_ajaxShowMessage(data.error, false);
3950 $container.removeClass('isActive');
3957 * Unbind all event handlers before tearing down a page
3959 AJAX.registerTeardown('functions.js', function () {
3960 $('div.container').unbind('click');
3963 * Initialise all toggle buttons
3965 AJAX.registerOnload('functions.js', function () {
3966 $('div.toggleAjax').each(function () {
3967 var $button = $(this).show();
3968 $button.find('img').each(function () {
3969 if (this.complete) {
3970 toggleButton($button);
3972 $(this).load(function () {
3973 toggleButton($button);
3981 * Unbind all event handlers before tearing down a page
3983 AJAX.registerTeardown('functions.js', function () {
3984 $(document).off('change', 'select.pageselector');
3985 $(document).off('click', 'a.formLinkSubmit');
3986 $('#update_recent_tables').unbind('ready');
3987 $('#sync_favorite_tables').unbind('ready');
3990 AJAX.registerOnload('functions.js', function () {
3993 * Autosubmit page selector
3995 $(document).on('change', 'select.pageselector', function (event) {
3996 event.stopPropagation();
3997 // Check where to load the new content
3998 if ($(this).closest("#pma_navigation").length === 0) {
3999 // For the main page we don't need to do anything,
4000 $(this).closest("form").submit();
4002 // but for the navigation we need to manually replace the content
4003 PMA_navigationTreePagination($(this));
4008 * Load version information asynchronously.
4010 if ($('li.jsversioncheck').length > 0) {
4011 $.getJSON('version_check.php', {'server' : PMA_commonParams.get('server')}, PMA_current_version);
4014 if ($('#is_git_revision').length > 0) {
4015 setTimeout(PMA_display_git_revision, 10);
4024 * Enables the text generated by PMA_Util::linkOrButton() to be clickable
4026 $(document).on('click', 'a.formLinkSubmit', function (e) {
4027 if (! $(this).hasClass('requireConfirm')) {
4028 submitFormLink($(this));
4033 var $updateRecentTables = $('#update_recent_tables');
4034 if ($updateRecentTables.length) {
4036 $updateRecentTables.attr('href'),
4039 if (typeof data !== 'undefined' && data.success === true) {
4040 $('#pma_recent_list').html(data.list);
4046 // Sync favorite tables from localStorage to pmadb.
4047 if ($('#sync_favorite_tables').length) {
4049 url: $('#sync_favorite_tables').attr("href"),
4053 favorite_tables: (isStorageSupported('localStorage') && typeof window.localStorage.favorite_tables !== 'undefined')
4054 ? window.localStorage.favorite_tables
4058 success: function (data) {
4059 // Update localStorage.
4060 if (isStorageSupported('localStorage')) {
4061 window.localStorage.favorite_tables = data.favorite_tables;
4063 $('#pma_favorite_list').html(data.list);
4070 * Submits the form placed in place of a link due to the excessive url length
4072 * @param $link anchor
4073 * @returns {Boolean}
4075 function submitFormLink($link)
4077 if ($link.attr('href').indexOf('=') != -1) {
4078 var data = $link.attr('href').substr($link.attr('href').indexOf('#') + 1).split('=', 2);
4079 $link.parents('form').append('<input type="hidden" name="' + data[0] + '" value="' + data[1] + '"/>');
4081 $link.parents('form').submit();
4085 * Initializes slider effect.
4087 function PMA_init_slider()
4089 $('div.pma_auto_slider').each(function () {
4090 var $this = $(this);
4091 if ($this.data('slider_init_done')) {
4094 var $wrapper = $('<div>', {'class': 'slide-wrapper'});
4095 $wrapper.toggle($this.is(':visible'));
4096 $('<a>', {href: '#' + this.id, "class": 'ajax'})
4097 .text($this.attr('title'))
4098 .prepend($('<span>'))
4099 .insertBefore($this)
4100 .click(function () {
4101 var $wrapper = $this.closest('.slide-wrapper');
4102 var visible = $this.is(':visible');
4106 $this[visible ? 'hide' : 'show']('blind', function () {
4107 $wrapper.toggle(!visible);
4108 $wrapper.parent().toggleClass("print_ignore", visible);
4109 PMA_set_status_label($this);
4113 $this.wrap($wrapper);
4114 $this.removeAttr('title');
4115 PMA_set_status_label($this);
4116 $this.data('slider_init_done', 1);
4121 * Initializes slider effect.
4123 AJAX.registerOnload('functions.js', function () {
4128 * Restores sliders to the state they were in before initialisation.
4130 AJAX.registerTeardown('functions.js', function () {
4131 $('div.pma_auto_slider').each(function () {
4132 var $this = $(this);
4134 $this.parent().replaceWith($this);
4135 $this.parent().children('a').remove();
4140 * Creates a message inside an object with a sliding effect
4142 * @param msg A string containing the text to display
4143 * @param $obj a jQuery object containing the reference
4144 * to the element where to put the message
4145 * This is optional, if no element is
4146 * provided, one will be created below the
4147 * navigation links at the top of the page
4149 * @return bool True on success, false on failure
4151 function PMA_slidingMessage(msg, $obj)
4153 if (msg === undefined || msg.length === 0) {
4154 // Don't show an empty message
4157 if ($obj === undefined || ! $obj instanceof jQuery || $obj.length === 0) {
4158 // If the second argument was not supplied,
4159 // we might have to create a new DOM node.
4160 if ($('#PMA_slidingMessage').length === 0) {
4161 $('#page_content').prepend(
4162 '<span id="PMA_slidingMessage" ' +
4163 'style="display: inline-block;"></span>'
4166 $obj = $('#PMA_slidingMessage');
4168 if ($obj.has('div').length > 0) {
4169 // If there already is a message inside the
4170 // target object, we must get rid of it
4174 .fadeOut(function () {
4179 .append('<div>' + msg + '</div>');
4180 // highlight any sql before taking height;
4181 PMA_highlightSQL($obj);
4187 height: $obj.find('div').first().height()
4194 // Object does not already have a message
4195 // inside it, so we simply slide it down
4197 .html('<div>' + msg + '</div>');
4198 // highlight any sql before taking height;
4199 PMA_highlightSQL($obj);
4213 // Set the height of the parent
4214 // to the height of the child
4225 } // end PMA_slidingMessage()
4228 * Attach CodeMirror2 editor to SQL edit area.
4230 AJAX.registerOnload('functions.js', function () {
4231 var $elm = $('#sqlquery');
4232 if ($elm.length > 0) {
4233 if (typeof CodeMirror != 'undefined') {
4234 codemirror_editor = PMA_getSQLEditor($elm);
4235 codemirror_editor.focus();
4236 codemirror_editor.on("blur", updateQueryParameters);
4238 // without codemirror
4240 .bind('blur', updateQueryParameters);
4243 PMA_highlightSQL($('body'));
4245 AJAX.registerTeardown('functions.js', function () {
4246 if (codemirror_editor) {
4247 $('#sqlquery').text(codemirror_editor.getValue());
4248 codemirror_editor.toTextArea();
4249 codemirror_editor = false;
4252 AJAX.registerOnload('functions.js', function () {
4253 // initializes all lock-page elements lock-id and
4254 // val-hash data property
4255 $('#page_content form.lock-page textarea, ' +
4256 '#page_content form.lock-page input[type="text"], '+
4257 '#page_content form.lock-page input[type="number"], '+
4258 '#page_content form.lock-page select').each(function (i) {
4259 $(this).data('lock-id', i);
4260 // val-hash is the hash of default value of the field
4261 // so that it can be compared with new value hash
4262 // to check whether field was modified or not.
4263 $(this).data('val-hash', AJAX.hash($(this).val()));
4266 // initializes lock-page elements (input types checkbox and radio buttons)
4267 // lock-id and val-hash data property
4268 $('#page_content form.lock-page input[type="checkbox"], ' +
4269 '#page_content form.lock-page input[type="radio"]').each(function (i) {
4270 $(this).data('lock-id', i);
4271 $(this).data('val-hash', AJAX.hash($(this).is(":checked")));
4275 * jQuery plugin to cancel selection in HTML code.
4278 $.fn.noSelect = function (p) { //no select plugin by Paulo P.Marinas
4279 var prevent = (p === null) ? true : p;
4280 var is_msie = navigator.userAgent.indexOf('MSIE') > -1 || !!window.navigator.userAgent.match(/Trident.*rv\:11\./);
4281 var is_firefox = navigator.userAgent.indexOf('Firefox') > -1;
4282 var is_safari = navigator.userAgent.indexOf("Safari") > -1;
4283 var is_opera = navigator.userAgent.indexOf("Presto") > -1;
4285 return this.each(function () {
4286 if (is_msie || is_safari) {
4287 $(this).bind('selectstart', function () {
4290 } else if (is_firefox) {
4291 $(this).css('MozUserSelect', 'none');
4292 $('body').trigger('focus');
4293 } else if (is_opera) {
4294 $(this).bind('mousedown', function () {
4298 $(this).attr('unselectable', 'on');
4302 return this.each(function () {
4303 if (is_msie || is_safari) {
4304 $(this).unbind('selectstart');
4305 } else if (is_firefox) {
4306 $(this).css('MozUserSelect', 'inherit');
4307 } else if (is_opera) {
4308 $(this).unbind('mousedown');
4310 $(this).removeAttr('unselectable');
4318 * jQuery plugin to correctly filter input fields by value, needed
4319 * because some nasty values may break selector syntax
4322 $.fn.filterByValue = function (value) {
4323 return this.filter(function () {
4324 return $(this).val() === value;
4330 * Return value of a cell in a table.
4332 function PMA_getCellValue(td) {
4334 if ($td.is('.null')) {
4336 } else if ((! $td.is('.to_be_saved')
4338 && $td.data('original_data')
4340 return $td.data('original_data');
4347 * Unbind all event handlers before tearing down a page
4349 AJAX.registerTeardown('functions.js', function () {
4350 $(document).off('click', 'a.themeselect');
4351 $(document).off('change', '.autosubmit');
4352 $('a.take_theme').unbind('click');
4355 AJAX.registerOnload('functions.js', function () {
4359 $(document).on('click', 'a.themeselect', function (e) {
4363 'left=10,top=20,width=510,height=350,scrollbars=yes,status=yes,resizable=yes'
4369 * Automatic form submission on change.
4371 $(document).on('change', '.autosubmit', function (e) {
4372 $(this).closest('form').submit();
4378 $('a.take_theme').click(function (e) {
4379 var what = this.name;
4380 if (window.opener && window.opener.document.forms.setTheme.elements.set_theme) {
4381 window.opener.document.forms.setTheme.elements.set_theme.value = what;
4382 window.opener.document.forms.setTheme.submit();
4393 function printPage()
4395 // Do print the page
4396 if (typeof(window.print) != 'undefined') {
4402 * Unbind all event handlers before tearing down a page
4404 AJAX.registerTeardown('functions.js', function () {
4405 $('input#print').unbind('click');
4406 $(document).off('click', 'a.create_view.ajax');
4407 $(document).off('keydown', '#createViewDialog input, #createViewDialog select');
4408 $(document).off('change', '#fkc_checkbox');
4411 AJAX.registerOnload('functions.js', function () {
4412 $('input#print').click(printPage);
4414 * Ajaxification for the "Create View" action
4416 $(document).on('click', 'a.create_view.ajax', function (e) {
4418 PMA_createViewDialog($(this));
4421 * Attach Ajax event handlers for input fields in the editor
4422 * and used to submit the Ajax request when the ENTER key is pressed.
4424 if ($('#createViewDialog').length !== 0) {
4425 $(document).on('keydown', '#createViewDialog input, #createViewDialog select', function (e) {
4426 if (e.which === 13) { // 13 is the ENTER key
4429 // with preventing default, selection by <select> tag
4430 // was also prevented in IE
4433 $(this).closest('.ui-dialog').find('.ui-button:first').click();
4435 }); // end $(document).on()
4438 syntaxHighlighter = PMA_getSQLEditor($('textarea[name="view[as]"]'));
4442 function PMA_createViewDialog($this)
4444 var $msg = PMA_ajaxShowMessage();
4445 var syntaxHighlighter = null;
4446 $.get($this.attr('href') + '&ajax_request=1&ajax_dialog=1', function (data) {
4447 if (typeof data !== 'undefined' && data.success === true) {
4448 PMA_ajaxRemoveMessage($msg);
4449 var buttonOptions = {};
4450 buttonOptions[PMA_messages.strGo] = function () {
4451 if (typeof CodeMirror !== 'undefined') {
4452 syntaxHighlighter.save();
4454 $msg = PMA_ajaxShowMessage();
4455 $.get('view_create.php', $('#createViewDialog').find('form').serialize(), function (data) {
4456 PMA_ajaxRemoveMessage($msg);
4457 if (typeof data !== 'undefined' && data.success === true) {
4458 $('#createViewDialog').dialog("close");
4459 $('.result_query').html(data.message);
4460 PMA_reloadNavigation();
4462 PMA_ajaxShowMessage(data.error, false);
4466 buttonOptions[PMA_messages.strClose] = function () {
4467 $(this).dialog("close");
4469 var $dialog = $('<div/>').attr('id', 'createViewDialog').append(data.message).dialog({
4473 buttons: buttonOptions,
4474 title: PMA_messages.strCreateView,
4475 close: function () {
4479 // Attach syntax highlighted editor
4480 syntaxHighlighter = PMA_getSQLEditor($dialog.find('textarea'));
4481 $('input:visible[type=text]', $dialog).first().focus();
4483 PMA_ajaxShowMessage(data.error);
4489 * Makes the breadcrumbs and the menu bar float at the top of the viewport
4492 if ($("#floating_menubar").length && $('#PMA_disable_floating_menubar').length === 0) {
4493 var left = $('html').attr('dir') == 'ltr' ? 'left' : 'right';
4494 $("#floating_menubar")
4495 .css('margin-' + left, $('#pma_navigation').width() + $('#pma_navigation_resizer').width())
4498 'position': 'fixed',
4503 .append($('#serverinfo'))
4504 .append($('#topmenucontainer'));
4505 // Allow the DOM to render, then adjust the padding on the body
4506 setTimeout(function () {
4509 $('#floating_menubar').outerHeight(true)
4511 $('#topmenu').menuResizer('resize');
4517 * Scrolls the page to the top if clicking the serverinfo bar
4520 $(document).delegate("#serverinfo, #goto_pagetop", "click", function (event) {
4521 event.preventDefault();
4522 $('html, body').animate({scrollTop: 0}, 'fast');
4526 var checkboxes_sel = "input.checkall:checkbox:enabled";
4528 * Watches checkboxes in a form to set the checkall box accordingly
4530 var checkboxes_changed = function () {
4531 var $form = $(this.form);
4532 // total number of checkboxes in current form
4533 var total_boxes = $form.find(checkboxes_sel).length;
4534 // number of checkboxes checked in current form
4535 var checked_boxes = $form.find(checkboxes_sel + ":checked").length;
4536 var $checkall = $form.find("input.checkall_box");
4537 if (total_boxes == checked_boxes) {
4538 $checkall.prop({checked: true, indeterminate: false});
4540 else if (checked_boxes > 0) {
4541 $checkall.prop({checked: true, indeterminate: true});
4544 $checkall.prop({checked: false, indeterminate: false});
4547 $(document).on("change", checkboxes_sel, checkboxes_changed);
4549 $(document).on("change", "input.checkall_box", function () {
4550 var is_checked = $(this).is(":checked");
4551 $(this.form).find(checkboxes_sel).prop("checked", is_checked)
4552 .parents("tr").toggleClass("marked", is_checked);
4556 * Watches checkboxes in a sub form to set the sub checkall box accordingly
4558 var sub_checkboxes_changed = function () {
4559 var $form = $(this).parent().parent();
4560 // total number of checkboxes in current sub form
4561 var total_boxes = $form.find(checkboxes_sel).length;
4562 // number of checkboxes checked in current sub form
4563 var checked_boxes = $form.find(checkboxes_sel + ":checked").length;
4564 var $checkall = $form.find("input.sub_checkall_box");
4565 if (total_boxes == checked_boxes) {
4566 $checkall.prop({checked: true, indeterminate: false});
4568 else if (checked_boxes > 0) {
4569 $checkall.prop({checked: true, indeterminate: true});
4572 $checkall.prop({checked: false, indeterminate: false});
4575 $(document).on("change", checkboxes_sel + ", input.checkall_box:checkbox:enabled", sub_checkboxes_changed);
4577 $(document).on("change", "input.sub_checkall_box", function () {
4578 var is_checked = $(this).is(":checked");
4579 var $form = $(this).parent().parent();
4580 $form.find(checkboxes_sel).prop("checked", is_checked)
4581 .parents("tr").toggleClass("marked", is_checked);
4585 * Toggles row colors of a set of 'tr' elements starting from a given element
4587 * @param $start Starting element
4589 function toggleRowColors($start)
4591 for (var $curr_row = $start; $curr_row.length > 0; $curr_row = $curr_row.next()) {
4592 if ($curr_row.hasClass('odd')) {
4593 $curr_row.removeClass('odd').addClass('even');
4594 } else if ($curr_row.hasClass('even')) {
4595 $curr_row.removeClass('even').addClass('odd');
4601 * Formats a byte number to human-readable form
4603 * @param bytes the bytes to format
4604 * @param optional subdecimals the number of digits after the point
4605 * @param optional pointchar the char to use as decimal point
4607 function formatBytes(bytes, subdecimals, pointchar) {
4614 var units = ['B', 'KiB', 'MiB', 'GiB'];
4615 for (var i = 0; bytes > 1024 && i < units.length; i++) {
4618 var factor = Math.pow(10, subdecimals);
4619 bytes = Math.round(bytes * factor) / factor;
4620 bytes = bytes.toString().split('.').join(pointchar);
4621 return bytes + ' ' + units[i];
4624 AJAX.registerOnload('functions.js', function () {
4626 * Opens pma more themes link in themes browser, in new window instead of popup
4627 * This way, we don't break HTML validity
4629 $("a._blank").prop("target", "_blank");
4631 * Reveal the login form to users with JS enabled
4632 * and focus the appropriate input field
4634 var $loginform = $('#loginform');
4635 if ($loginform.length) {
4636 $loginform.find('.js-show').show();
4637 if ($('#input_username').val()) {
4638 $('#input_password').focus();
4640 $('#input_username').focus();
4646 * Dynamically adjust the width of the boxes
4647 * on the table and db operations pages
4650 function DynamicBoxes() {
4651 var $boxContainer = $('#boxContainer');
4652 if ($boxContainer.length) {
4653 var minWidth = $boxContainer.data('box-width');
4654 var viewport = $(window).width() - $('#pma_navigation').width();
4655 var slots = Math.floor(viewport / minWidth);
4656 $boxContainer.children()
4658 if (viewport < minWidth) {
4659 $(this).width(minWidth);
4661 $(this).css('width', ((1 / slots) * 100) + "%");
4664 .removeClass('clearfloat')
4665 .filter(':nth-child(' + slots + 'n+1)')
4666 .addClass('clearfloat');
4669 AJAX.registerOnload('functions.js', function () {
4673 $(window).resize(DynamicBoxes);
4678 * Formats timestamp for display
4680 function PMA_formatDateTime(date, seconds) {
4681 var result = $.datepicker.formatDate('yy-mm-dd', date);
4682 var timefmt = 'HH:mm';
4684 timefmt = 'HH:mm:ss';
4686 return result + ' ' + $.datepicker.formatTime(
4688 hour: date.getHours(),
4689 minute: date.getMinutes(),
4690 second: date.getSeconds()
4696 * Check than forms have less fields than max allowed by PHP.
4698 function checkNumberOfFields() {
4699 if (typeof maxInputVars === 'undefined') {
4702 if (false === maxInputVars) {
4705 $('form').each(function() {
4706 var nbInputs = $(this).find(':input').length;
4707 if (nbInputs > maxInputVars) {
4708 var warning = PMA_sprintf(PMA_messages.strTooManyInputs, maxInputVars);
4709 PMA_ajaxShowMessage(warning);
4719 * Ignore the displayed php errors.
4720 * Simply removes the displayed errors.
4722 * @param clearPrevErrors whether to clear errors stored
4723 * in $_SESSION['prev_errors'] at server
4726 function PMA_ignorePhpErrors(clearPrevErrors){
4727 if (typeof(clearPrevErrors) === "undefined" ||
4728 clearPrevErrors === null
4732 // send AJAX request to error_report.php with send_error_report=0, exception_type=php & token.
4733 // It clears the prev_errors stored in session.
4734 if(clearPrevErrors){
4735 var $pmaReportErrorsForm = $('#pma_report_errors_form');
4736 $pmaReportErrorsForm.find('input[name="send_error_report"]').val(0); // change send_error_report to '0'
4737 $pmaReportErrorsForm.submit();
4740 // remove displayed errors
4741 var $pmaErrors = $('#pma_errors');
4742 $pmaErrors.fadeOut( "slow");
4743 $pmaErrors.remove();
4747 * checks whether browser supports web storage
4749 * @param type the type of storage i.e. localStorage or sessionStorage
4753 function isStorageSupported(type)
4756 window[type].setItem('PMATest', 'test');
4757 // Check whether key-value pair was set successfully
4758 if (window[type].getItem('PMATest') === 'test') {
4759 // Supported, remove test variable from storage
4760 window[type].removeItem('PMATest');
4765 PMA_ajaxShowMessage(PMA_messages.strNoLocalStorage, false);
4771 * Unbind all event handlers before tearing down a page
4773 AJAX.registerTeardown('functions.js', function(){
4774 $(document).off('keydown', 'form input, form textarea, form select');
4777 AJAX.registerOnload('functions.js', function () {
4779 * Handle 'Ctrl/Alt + Enter' form submits
4781 $('form input, form textarea, form select').on('keydown', function(e){
4782 if((e.ctrlKey && e.which == 13) || (e.altKey && e.which == 13)) {
4783 $form = $(this).closest('form');
4784 if (! $form.find('input[type="submit"]') ||
4785 ! $form.find('input[type="submit"]').click()
4794 * Unbind all event handlers before tearing down a page
4796 AJAX.registerTeardown('functions.js', function(){
4797 $(document).off('change', 'input[type=radio][name="pw_hash"]');
4800 AJAX.registerOnload('functions.js', function(){
4802 * Display warning regarding SSL when sha256_password
4803 * method is selected
4804 * Used in user_password.php (Change Password link on index.php)
4806 $(document).on("change", 'select#select_authentication_plugin_cp', function() {
4807 if (this.value === 'sha256_password') {
4808 $('#ssl_reqd_warning_cp').show();
4810 $('#ssl_reqd_warning_cp').hide();