Merge remote-tracking branch 'origin/QA_4_7' into QA_4_7
[phpmyadmin.git] / js / sql.js
blob376f8a090e5dbfc74c511a74931e5e7858497687
1 /* vim: set expandtab sw=4 ts=4 sts=4: */
2 /**
3  * @fileoverview    functions used wherever an sql query form is used
4  *
5  * @requires    jQuery
6  * @requires    js/functions.js
7  *
8  */
10 var $data_a;
11 var prevScrollX = 0;
13 /**
14  * decode a string URL_encoded
15  *
16  * @param string str
17  * @return string the URL-decoded string
18  */
19 function PMA_urldecode(str)
21     if (typeof str !== 'undefined') {
22         return decodeURIComponent(str.replace(/\+/g, '%20'));
23     }
26 /**
27  * endecode a string URL_decoded
28  *
29  * @param string str
30  * @return string the URL-encoded string
31  */
32 function PMA_urlencode(str)
34     if (typeof str !== 'undefined') {
35         return encodeURIComponent(str).replace(/\%20/g, '+');
36     }
39 /**
40  * Saves SQL query in local storage or cooie
41  *
42  * @param string SQL query
43  * @return void
44  */
45 function PMA_autosaveSQL(query)
47     if (query) {
48         if (isStorageSupported('localStorage')) {
49             window.localStorage.auto_saved_sql = query;
50         } else {
51             $.cookie('auto_saved_sql', query);
52         }
53     }
56 /**
57  * Get the field name for the current field.  Required to construct the query
58  * for grid editing
59  *
60  * @param $table_results enclosing results table
61  * @param $this_field    jQuery object that points to the current field's tr
62  */
63 function getFieldName($table_results, $this_field)
66     var this_field_index = $this_field.index();
67     // ltr or rtl direction does not impact how the DOM was generated
68     // check if the action column in the left exist
69     var left_action_exist = !$table_results.find('th:first').hasClass('draggable');
70     // number of column span for checkbox and Actions
71     var left_action_skip = left_action_exist ? $table_results.find('th:first').attr('colspan') - 1 : 0;
73     // If this column was sorted, the text of the a element contains something
74     // like <small>1</small> that is useful to indicate the order in case
75     // of a sort on multiple columns; however, we dont want this as part
76     // of the column name so we strip it ( .clone() to .end() )
77     var field_name = $table_results
78         .find('thead')
79         .find('th:eq(' + (this_field_index - left_action_skip) + ') a')
80         .clone()    // clone the element
81         .children() // select all the children
82         .remove()   // remove all of them
83         .end()      // go back to the selected element
84         .text();    // grab the text
85     // happens when just one row (headings contain no a)
86     if (field_name === '') {
87         var $heading = $table_results.find('thead').find('th:eq(' + (this_field_index - left_action_skip) + ')').children('span');
88         // may contain column comment enclosed in a span - detach it temporarily to read the column name
89         var $tempColComment = $heading.children().detach();
90         field_name = $heading.text();
91         // re-attach the column comment
92         $heading.append($tempColComment);
93     }
95     field_name = $.trim(field_name);
97     return field_name;
101  * Unbind all event handlers before tearing down a page
102  */
103 AJAX.registerTeardown('sql.js', function () {
104     $(document).off('click', 'a.delete_row.ajax');
105     $(document).off('submit', '.bookmarkQueryForm');
106     $('input#bkm_label').unbind('keyup');
107     $(document).off('makegrid', ".sqlqueryresults");
108     $(document).off('stickycolumns', ".sqlqueryresults");
109     $("#togglequerybox").unbind('click');
110     $(document).off('click', "#button_submit_query");
111     $(document).off('change', '#id_bookmark');
112     $("input[name=bookmark_variable]").unbind("keypress");
113     $(document).off('submit', "#sqlqueryform.ajax");
114     $(document).off('click', "input[name=navig].ajax");
115     $(document).off('submit', "form[name='displayOptionsForm'].ajax");
116     $(document).off('mouseenter', 'th.column_heading.pointer');
117     $(document).off('mouseleave', 'th.column_heading.pointer');
118     $(document).off('click', 'th.column_heading.marker');
119     $(window).unbind('scroll');
120     $(document).off("keyup", ".filter_rows");
121     $(document).off('click', "#printView");
122     if (codemirror_editor) {
123         codemirror_editor.off('change');
124     } else {
125         $('#sqlquery').off('input propertychange');
126     }
127     $('body').off('click', '.navigation .showAllRows');
128     $('body').off('click','a.browse_foreign');
129     $('body').off('click', '#simulate_dml');
130     $('body').off('keyup', '#sqlqueryform');
131     $('body').off('click', 'form[name="resultsForm"].ajax button[name="submit_mult"], form[name="resultsForm"].ajax input[name="submit_mult"]');
135  * @description <p>Ajax scripts for sql and browse pages</p>
137  * Actions ajaxified here:
138  * <ul>
139  * <li>Retrieve results of an SQL query</li>
140  * <li>Paginate the results table</li>
141  * <li>Sort the results table</li>
142  * <li>Change table according to display options</li>
143  * <li>Grid editing of data</li>
144  * <li>Saving a bookmark</li>
145  * </ul>
147  * @name        document.ready
148  * @memberOf    jQuery
149  */
150 AJAX.registerOnload('sql.js', function () {
152     $(function () {
153         if (codemirror_editor) {
154             codemirror_editor.on('change', function () {
155                 PMA_autosaveSQL(codemirror_editor.getValue());
156             });
157         } else {
158             $('#sqlquery').on('input propertychange', function () {
159                 PMA_autosaveSQL($('#sqlquery').val());
160             });
161         }
162     });
164     // Delete row from SQL results
165     $(document).on('click', 'a.delete_row.ajax', function (e) {
166         e.preventDefault();
167         var question =  PMA_sprintf(PMA_messages.strDoYouReally, escapeHtml($(this).closest('td').find('div').text()));
168         var $link = $(this);
169         $link.PMA_confirm(question, $link.attr('href'), function (url) {
170             $msgbox = PMA_ajaxShowMessage();
171             if ($link.hasClass('formLinkSubmit')) {
172                 submitFormLink($link);
173             } else {
174                 var params = {
175                     'ajax_request': true,
176                     'is_js_confirmed': true,
177                     'token': PMA_commonParams.get('token')
178                 };
179                 $.post(url, params, function (data) {
180                     if (data.success) {
181                         PMA_ajaxShowMessage(data.message);
182                         $link.closest('tr').remove();
183                     } else {
184                         PMA_ajaxShowMessage(data.error, false);
185                     }
186                 });
187             }
188         });
189     });
191     // Ajaxification for 'Bookmark this SQL query'
192     $(document).on('submit', '.bookmarkQueryForm', function (e) {
193         e.preventDefault();
194         PMA_ajaxShowMessage();
195         $.post($(this).attr('action'), 'ajax_request=1&' + $(this).serialize(), function (data) {
196             if (data.success) {
197                 PMA_ajaxShowMessage(data.message);
198             } else {
199                 PMA_ajaxShowMessage(data.error, false);
200             }
201         });
202     });
204     /* Hides the bookmarkoptions checkboxes when the bookmark label is empty */
205     $('input#bkm_label').keyup(function () {
206         $('input#id_bkm_all_users, input#id_bkm_replace')
207             .parent()
208             .toggle($(this).val().length > 0);
209     }).trigger('keyup');
211     /**
212      * Attach Event Handler for 'Copy to clipbpard
213      */
214     $(document).on('click', "#copyToClipBoard", function (event) {
215         event.preventDefault();
217         var textArea = document.createElement("textarea");
219         //
220         // *** This styling is an extra step which is likely not required. ***
221         //
222         // Why is it here? To ensure:
223         // 1. the element is able to have focus and selection.
224         // 2. if element was to flash render it has minimal visual impact.
225         // 3. less flakyness with selection and copying which **might** occur if
226         //    the textarea element is not visible.
227         //
228         // The likelihood is the element won't even render, not even a flash,
229         // so some of these are just precautions. However in IE the element
230         // is visible whilst the popup box asking the user for permission for
231         // the web page to copy to the clipboard.
232         //
234         // Place in top-left corner of screen regardless of scroll position.
235         textArea.style.position = 'fixed';
236         textArea.style.top = 0;
237         textArea.style.left = 0;
239         // Ensure it has a small width and height. Setting to 1px / 1em
240         // doesn't work as this gives a negative w/h on some browsers.
241         textArea.style.width = '2em';
242         textArea.style.height = '2em';
244         // We don't need padding, reducing the size if it does flash render.
245         textArea.style.padding = 0;
247         // Clean up any borders.
248         textArea.style.border = 'none';
249         textArea.style.outline = 'none';
250         textArea.style.boxShadow = 'none';
252         // Avoid flash of white box if rendered for any reason.
253         textArea.style.background = 'transparent';
255         textArea.value = '';
257         $('#serverinfo a').each(function(){
258             textArea.value += $(this).text().split(':')[1].trim() + '/';
259         });
260         textArea.value += '\t\t' + window.location.href;
261         textArea.value += '\n';
263         $('.notice,.success').each(function(){
264             textArea.value += $(this).text() + '\n\n';
265         });
267         $('.sql pre').each(function() {
268             textArea.value += $(this).text() + '\n\n';
269         });
271         $('.table_results .column_heading a').each(function() {
272             textArea.value += $(this).text() + '\t';
273         });
275         textArea.value += '\n';
276         $('.table_results tbody tr').each(function() {
277             $(this).find('.data span').each(function(){
278                 textArea.value += $(this).text() + '\t';
279             });
280             textArea.value += '\n';
281         });
283         document.body.appendChild(textArea);
285         textArea.select();
287         try {
288             document.execCommand('copy');
289         } catch (err) {
290             alert('Sorry! Unable to copy');
291         }
293         document.body.removeChild(textArea);
295     }); //end of Copy to Clipboard action
297     /**
298      * Attach Event Handler for 'Print' link
299      */
300     $(document).on('click', "#printView", function (event) {
301         event.preventDefault();
303         // Take to preview mode
304         printPreview();
305     }); //end of 'Print' action
307     /**
308      * Attach the {@link makegrid} function to a custom event, which will be
309      * triggered manually everytime the table of results is reloaded
310      * @memberOf    jQuery
311      */
312     $(document).on('makegrid', ".sqlqueryresults", function () {
313         $('.table_results').each(function () {
314             PMA_makegrid(this);
315         });
316     });
318     /*
319      * Attach a custom event for sticky column headings which will be
320      * triggered manually everytime the table of results is reloaded
321      * @memberOf    jQuery
322      */
323     $(document).on('stickycolumns', ".sqlqueryresults", function () {
324         $(".sticky_columns").remove();
325         $(".table_results").each(function () {
326             var $table_results = $(this);
327             //add sticky columns div
328             var $stick_columns = initStickyColumns($table_results);
329             rearrangeStickyColumns($stick_columns, $table_results);
330             //adjust sticky columns on scroll
331             $(window).bind('scroll', function() {
332                 handleStickyColumns($stick_columns, $table_results);
333             });
334         });
335     });
337     /**
338      * Append the "Show/Hide query box" message to the query input form
339      *
340      * @memberOf jQuery
341      * @name    appendToggleSpan
342      */
343     // do not add this link more than once
344     if (! $('#sqlqueryform').find('a').is('#togglequerybox')) {
345         $('<a id="togglequerybox"></a>')
346         .html(PMA_messages.strHideQueryBox)
347         .appendTo("#sqlqueryform")
348         // initially hidden because at this point, nothing else
349         // appears under the link
350         .hide();
352         // Attach the toggling of the query box visibility to a click
353         $("#togglequerybox").bind('click', function () {
354             var $link = $(this);
355             $link.siblings().slideToggle("fast");
356             if ($link.text() == PMA_messages.strHideQueryBox) {
357                 $link.text(PMA_messages.strShowQueryBox);
358                 // cheap trick to add a spacer between the menu tabs
359                 // and "Show query box"; feel free to improve!
360                 $('#togglequerybox_spacer').remove();
361                 $link.before('<br id="togglequerybox_spacer" />');
362             } else {
363                 $link.text(PMA_messages.strHideQueryBox);
364             }
365             // avoid default click action
366             return false;
367         });
368     }
371     /**
372      * Event handler for sqlqueryform.ajax button_submit_query
373      *
374      * @memberOf    jQuery
375      */
376     $(document).on('click', "#button_submit_query", function (event) {
377         $(".success,.error").hide();
378         //hide already existing error or success message
379         var $form = $(this).closest("form");
380         // the Go button related to query submission was clicked,
381         // instead of the one related to Bookmarks, so empty the
382         // id_bookmark selector to avoid misinterpretation in
383         // import.php about what needs to be done
384         $form.find("select[name=id_bookmark]").val("");
385         // let normal event propagation happen
386     });
388     /**
389      * Event handler to show appropiate number of variable boxes
390      * based on the bookmarked query
391      */
392     $(document).on('change', '#id_bookmark', function (event) {
394         var varCount = $(this).find('option:selected').data('varcount');
395         if (typeof varCount == 'undefined') {
396             varCount = 0;
397         }
399         var $varDiv = $('#bookmark_variables');
400         $varDiv.empty();
401         for (var i = 1; i <= varCount; i++) {
402             $varDiv.append($('<label for="bookmark_variable_' + i + '">' + PMA_sprintf(PMA_messages.strBookmarkVariable, i) + '</label>'));
403             $varDiv.append($('<input type="text" size="10" name="bookmark_variable[' + i + ']" id="bookmark_variable_' + i + '"></input>'));
404         }
406         if (varCount == 0) {
407             $varDiv.parent('.formelement').hide();
408         } else {
409             $varDiv.parent('.formelement').show();
410         }
411     });
413     /**
414      * Event handler for hitting enter on sqlqueryform bookmark_variable
415      * (the Variable textfield in Bookmarked SQL query section)
416      *
417      * @memberOf    jQuery
418      */
419     $("input[name=bookmark_variable]").bind("keypress", function (event) {
420         // force the 'Enter Key' to implicitly click the #button_submit_bookmark
421         var keycode = (event.keyCode ? event.keyCode : (event.which ? event.which : event.charCode));
422         if (keycode == 13) { // keycode for enter key
423             // When you press enter in the sqlqueryform, which
424             // has 2 submit buttons, the default is to run the
425             // #button_submit_query, because of the tabindex
426             // attribute.
427             // This submits #button_submit_bookmark instead,
428             // because when you are in the Bookmarked SQL query
429             // section and hit enter, you expect it to do the
430             // same action as the Go button in that section.
431             $("#button_submit_bookmark").click();
432             return false;
433         } else  {
434             return true;
435         }
436     });
438     /**
439      * Ajax Event handler for 'SQL Query Submit'
440      *
441      * @see         PMA_ajaxShowMessage()
442      * @memberOf    jQuery
443      * @name        sqlqueryform_submit
444      */
445     $(document).on('submit', "#sqlqueryform.ajax", function (event) {
446         event.preventDefault();
448         var $form = $(this);
449         if (codemirror_editor) {
450             $form[0].elements.sql_query.value = codemirror_editor.getValue();
451         }
452         if (! checkSqlQuery($form[0])) {
453             return false;
454         }
456         // remove any div containing a previous error message
457         $('div.error').remove();
459         var $msgbox = PMA_ajaxShowMessage();
460         var $sqlqueryresultsouter = $('#sqlqueryresultsouter');
462         PMA_prepareForAjaxRequest($form);
464         $.post($form.attr('action'), $form.serialize() + '&ajax_page_request=true', function (data) {
465             if (typeof data !== 'undefined' && data.success === true) {
466                 // success happens if the query returns rows or not
468                 // show a message that stays on screen
469                 if (typeof data.action_bookmark != 'undefined') {
470                     // view only
471                     if ('1' == data.action_bookmark) {
472                         $('#sqlquery').text(data.sql_query);
473                         // send to codemirror if possible
474                         setQuery(data.sql_query);
475                     }
476                     // delete
477                     if ('2' == data.action_bookmark) {
478                         $("#id_bookmark option[value='" + data.id_bookmark + "']").remove();
479                         // if there are no bookmarked queries now (only the empty option),
480                         // remove the bookmark section
481                         if ($('#id_bookmark option').length == 1) {
482                             $('#fieldsetBookmarkOptions').hide();
483                             $('#fieldsetBookmarkOptionsFooter').hide();
484                         }
485                     }
486                 }
487                 $sqlqueryresultsouter
488                     .show()
489                     .html(data.message);
490                 PMA_highlightSQL($sqlqueryresultsouter);
492                 if (data._menu) {
493                     if (history && history.pushState) {
494                         history.replaceState({
495                                 menu : data._menu
496                             },
497                             null
498                         );
499                         AJAX.handleMenu.replace(data._menu);
500                     } else {
501                         PMA_MicroHistory.menus.replace(data._menu);
502                         PMA_MicroHistory.menus.add(data._menuHash, data._menu);
503                     }
504                 } else if (data._menuHash) {
505                     if (! (history && history.pushState)) {
506                         PMA_MicroHistory.menus.replace(PMA_MicroHistory.menus.get(data._menuHash));
507                     }
508                 }
510                 if (data._params) {
511                     PMA_commonParams.setAll(data._params);
512                 }
514                 if (typeof data.ajax_reload != 'undefined') {
515                     if (data.ajax_reload.reload) {
516                         if (data.ajax_reload.table_name) {
517                             PMA_commonParams.set('table', data.ajax_reload.table_name);
518                             PMA_commonActions.refreshMain();
519                         } else {
520                             PMA_reloadNavigation();
521                         }
522                     }
523                 } else if (typeof data.reload != 'undefined') {
524                     // this happens if a USE or DROP command was typed
525                     PMA_commonActions.setDb(data.db);
526                     var url;
527                     if (data.db) {
528                         if (data.table) {
529                             url = 'table_sql.php';
530                         } else {
531                             url = 'db_sql.php';
532                         }
533                     } else {
534                         url = 'server_sql.php';
535                     }
536                     PMA_commonActions.refreshMain(url, function () {
537                         $('#sqlqueryresultsouter')
538                             .show()
539                             .html(data.message);
540                         PMA_highlightSQL($('#sqlqueryresultsouter'));
541                     });
542                 }
544                 $('.sqlqueryresults').trigger('makegrid').trigger('stickycolumns');
545                 $('#togglequerybox').show();
546                 PMA_init_slider();
548                 if (typeof data.action_bookmark == 'undefined') {
549                     if ($('#sqlqueryform input[name="retain_query_box"]').is(':checked') !== true) {
550                         if ($("#togglequerybox").siblings(":visible").length > 0) {
551                             $("#togglequerybox").trigger('click');
552                         }
553                     }
554                 }
555             } else if (typeof data !== 'undefined' && data.success === false) {
556                 // show an error message that stays on screen
557                 $sqlqueryresultsouter
558                     .show()
559                     .html(data.error);
560             }
561             PMA_ajaxRemoveMessage($msgbox);
562         }); // end $.post()
563     }); // end SQL Query submit
565     /**
566      * Ajax Event handler for the display options
567      * @memberOf    jQuery
568      * @name        displayOptionsForm_submit
569      */
570     $(document).on('submit', "form[name='displayOptionsForm'].ajax", function (event) {
571         event.preventDefault();
573         $form = $(this);
575         var $msgbox = PMA_ajaxShowMessage();
576         $.post($form.attr('action'), $form.serialize() + '&ajax_request=true', function (data) {
577             PMA_ajaxRemoveMessage($msgbox);
578             var $sqlqueryresults = $form.parents(".sqlqueryresults");
579             $sqlqueryresults
580              .html(data.message)
581              .trigger('makegrid')
582              .trigger('stickycolumns');
583             PMA_init_slider();
584             PMA_highlightSQL($sqlqueryresults);
585         }); // end $.post()
586     }); //end displayOptionsForm handler
588     // Filter row handling. --STARTS--
589     $(document).on("keyup", ".filter_rows", function () {
590         var unique_id = $(this).data("for");
591         var $target_table = $(".table_results[data-uniqueId='" + unique_id + "']");
592         var $header_cells = $target_table.find("th[data-column]");
593         var target_columns = Array();
594         // To handle colspan=4, in case of edit,copy etc options.
595         var dummy_th = ($(".edit_row_anchor").length !== 0 ?
596             '<th class="hide dummy_th"></th><th class="hide dummy_th"></th><th class="hide dummy_th"></th>'
597             : '');
598         // Selecting columns that will be considered for filtering and searching.
599         $header_cells.each(function () {
600             target_columns.push($.trim($(this).text()));
601         });
603         var phrase = $(this).val();
604         // Set same value to both Filter rows fields.
605         $(".filter_rows[data-for='" + unique_id + "']").not(this).val(phrase);
606         // Handle colspan.
607         $target_table.find("thead > tr").prepend(dummy_th);
608         $.uiTableFilter($target_table, phrase, target_columns);
609         $target_table.find("th.dummy_th").remove();
610     });
611     // Filter row handling. --ENDS--
613     // Prompt to confirm on Show All
614     $('body').on('click', '.navigation .showAllRows', function (e) {
615         e.preventDefault();
616         var $form = $(this).parents('form');
618         if (! $(this).is(':checked')) { // already showing all rows
619             submitShowAllForm();
620         } else {
621             $form.PMA_confirm(PMA_messages.strShowAllRowsWarning, $form.attr('action'), function (url) {
622                 submitShowAllForm();
623             });
624         }
626         function submitShowAllForm() {
627             var submitData = $form.serialize() + '&ajax_request=true&ajax_page_request=true';
628             PMA_ajaxShowMessage();
629             AJAX.source = $form;
630             $.post($form.attr('action'), submitData, AJAX.responseHandler);
631         }
632     });
634     $('body').on('keyup', '#sqlqueryform', function () {
635         PMA_handleSimulateQueryButton();
636     });
638     /**
639      * Ajax event handler for 'Simulate DML'.
640      */
641     $('body').on('click', '#simulate_dml', function () {
642         var $form = $('#sqlqueryform');
643         var query = '';
644         var delimiter = $('#id_sql_delimiter').val();
645         var db_name = $form.find('input[name="db"]').val();
647         if (codemirror_editor) {
648             query = codemirror_editor.getValue();
649         } else {
650             query = $('#sqlquery').val();
651         }
653         if (query.length === 0) {
654             alert(PMA_messages.strFormEmpty);
655             $('#sqlquery').focus();
656             return false;
657         }
659         var $msgbox = PMA_ajaxShowMessage();
660         $.ajax({
661             type: 'POST',
662             url: $form.attr('action'),
663             data: {
664                 token: PMA_commonParams.get('token'),
665                 server: PMA_commonParams.get('server'),
666                 db: db_name,
667                 ajax_request: '1',
668                 simulate_dml: '1',
669                 sql_query: query,
670                 sql_delimiter: delimiter
671             },
672             success: function (response) {
673                 PMA_ajaxRemoveMessage($msgbox);
674                 if (response.success) {
675                     var dialog_content = '<div class="preview_sql">';
676                     if (response.sql_data) {
677                         var len = response.sql_data.length;
678                         for (var i=0; i<len; i++) {
679                             dialog_content += '<strong>' + PMA_messages.strSQLQuery +
680                                 '</strong>' + response.sql_data[i].sql_query +
681                                 PMA_messages.strMatchedRows +
682                                 ' <a href="' + response.sql_data[i].matched_rows_url +
683                                 '">' + response.sql_data[i].matched_rows + '</a><br>';
684                             if (i<len-1) {
685                                 dialog_content += '<hr>';
686                             }
687                         }
688                     } else {
689                         dialog_content += response.message;
690                     }
691                     dialog_content += '</div>';
692                     var $dialog_content = $(dialog_content);
693                     var button_options = {};
694                     button_options[PMA_messages.strClose] = function () {
695                         $(this).dialog('close');
696                     };
697                     var $response_dialog = $('<div />').append($dialog_content).dialog({
698                         minWidth: 540,
699                         maxHeight: 400,
700                         modal: true,
701                         buttons: button_options,
702                         title: PMA_messages.strSimulateDML,
703                         open: function () {
704                             PMA_highlightSQL($(this));
705                         },
706                         close: function () {
707                             $(this).remove();
708                         }
709                     });
710                 } else {
711                     PMA_ajaxShowMessage(response.error);
712                 }
713             },
714             error: function (response) {
715                 PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest);
716             }
717         });
718     });
720     /**
721      * Handles multi submits of results browsing page such as edit, delete and export
722      */
723     $('body').on('click', 'form[name="resultsForm"].ajax button[name="submit_mult"], form[name="resultsForm"].ajax input[name="submit_mult"]', function (e) {
724         e.preventDefault();
725         var $button = $(this);
726         var $form = $button.closest('form');
727         var submitData = $form.serialize() + '&ajax_request=true&ajax_page_request=true&submit_mult=' + $button.val();
728         PMA_ajaxShowMessage();
729         AJAX.source = $form;
730         $.post($form.attr('action'), submitData, AJAX.responseHandler);
731     });
732 }); // end $()
735  * Starting from some th, change the class of all td under it.
736  * If isAddClass is specified, it will be used to determine whether to add or remove the class.
737  */
738 function PMA_changeClassForColumn($this_th, newclass, isAddClass)
740     // index 0 is the th containing the big T
741     var th_index = $this_th.index();
742     var has_big_t = $this_th.closest('tr').children(':first').hasClass('column_action');
743     // .eq() is zero-based
744     if (has_big_t) {
745         th_index--;
746     }
747     var $table = $this_th.parents('.table_results');
748     if (! $table.length) {
749         $table = $this_th.parents('table').siblings('.table_results');
750     }
751     var $tds = $table.find('tbody tr').find('td.data:eq(' + th_index + ')');
752     if (isAddClass === undefined) {
753         $tds.toggleClass(newclass);
754     } else {
755         $tds.toggleClass(newclass, isAddClass);
756     }
760  * Handles browse foreign values modal dialog
762  * @param object $this_a reference to the browse foreign value link
763  */
764 function browseForeignDialog($this_a)
766     var formId = '#browse_foreign_form';
767     var showAllId = '#foreign_showAll';
768     var tableId = '#browse_foreign_table';
769     var filterId = '#input_foreign_filter';
770     var $dialog = null;
771     $.get($this_a.attr('href'), {'ajax_request': true}, function (data) {
772         // Creates browse foreign value dialog
773         $dialog = $('<div>').append(data.message).dialog({
774             title: PMA_messages.strBrowseForeignValues,
775             width: Math.min($(window).width() - 100, 700),
776             maxHeight: $(window).height() - 100,
777             dialogClass: 'browse_foreign_modal',
778             close: function (ev, ui) {
779                 // remove event handlers attached to elements related to dialog
780                 $(tableId).off('click', 'td a.foreign_value');
781                 $(formId).off('click', showAllId);
782                 $(formId).off('submit');
783                 // remove dialog itself
784                 $(this).remove();
785             },
786             modal: true
787         });
788     }).done(function () {
789         var showAll = false;
790         $(tableId).on('click', 'td a.foreign_value', function (e) {
791             e.preventDefault();
792             var $input = $this_a.prev('input[type=text]');
793             // Check if input exists or get CEdit edit_box
794             if ($input.length === 0 ) {
795                 $input = $this_a.closest('.edit_area').prev('.edit_box');
796             }
797             // Set selected value as input value
798             $input.val($(this).data('key'));
799             $dialog.dialog('close');
800         });
801         $(formId).on('click', showAllId, function () {
802             showAll = true;
803         });
804         $(formId).on('submit', function (e) {
805             e.preventDefault();
806             // if filter value is not equal to old value
807             // then reset page number to 1
808             if ($(filterId).val() != $(filterId).data('old')) {
809                 $(formId).find('select[name=pos]').val('0');
810             }
811             var postParams = $(this).serializeArray();
812             // if showAll button was clicked to submit form then
813             // add showAll button parameter to form
814             if (showAll) {
815                 postParams.push({
816                     name: $(showAllId).attr('name'),
817                     value: $(showAllId).val()
818                 });
819             }
820             // updates values in dialog
821             $.post($(this).attr('action') + '?ajax_request=1', postParams, function (data) {
822                 var $obj = $('<div>').html(data.message);
823                 $(formId).html($obj.find(formId).html());
824                 $(tableId).html($obj.find(tableId).html());
825             });
826             showAll = false;
827         });
828     });
831 AJAX.registerOnload('sql.js', function () {
832     $('body').on('click', 'a.browse_foreign', function (e) {
833         e.preventDefault();
834         browseForeignDialog($(this));
835     });
837     /**
838      * vertical column highlighting in horizontal mode when hovering over the column header
839      */
840     $(document).on('mouseenter', 'th.column_heading.pointer', function (e) {
841         PMA_changeClassForColumn($(this), 'hover', true);
842     });
843     $(document).on('mouseleave', 'th.column_heading.pointer', function (e) {
844         PMA_changeClassForColumn($(this), 'hover', false);
845     });
847     /**
848      * vertical column marking in horizontal mode when clicking the column header
849      */
850     $(document).on('click', 'th.column_heading.marker', function () {
851         PMA_changeClassForColumn($(this), 'marked');
852     });
854     /**
855      * create resizable table
856      */
857     $(".sqlqueryresults").trigger('makegrid').trigger('stickycolumns');
861  * Profiling Chart
862  */
863 function makeProfilingChart()
865     if ($('#profilingchart').length === 0 ||
866         $('#profilingchart').html().length !== 0 ||
867         !$.jqplot || !$.jqplot.Highlighter || !$.jqplot.PieRenderer
868     ) {
869         return;
870     }
872     var data = [];
873     $.each(JSON.parse($('#profilingChartData').html()), function (key, value) {
874         data.push([key, parseFloat(value)]);
875     });
877     // Remove chart and data divs contents
878     $('#profilingchart').html('').show();
879     $('#profilingChartData').html('');
881     PMA_createProfilingChart('profilingchart', data);
885  * initialize profiling data tables
886  */
887 function initProfilingTables()
889     if (!$.tablesorter) {
890         return;
891     }
893     $('#profiletable').tablesorter({
894         widgets: ['zebra'],
895         sortList: [[0, 0]],
896         textExtraction: function (node) {
897             if (node.children.length > 0) {
898                 return node.children[0].innerHTML;
899             } else {
900                 return node.innerHTML;
901             }
902         }
903     });
905     $('#profilesummarytable').tablesorter({
906         widgets: ['zebra'],
907         sortList: [[1, 1]],
908         textExtraction: function (node) {
909             if (node.children.length > 0) {
910                 return node.children[0].innerHTML;
911             } else {
912                 return node.innerHTML;
913             }
914         }
915     });
919  * Set position, left, top, width of sticky_columns div
920  */
921 function setStickyColumnsPosition($sticky_columns, $table_results, position, top, left, margin_left) {
922     $sticky_columns
923         .css("position", position)
924         .css("top", top)
925         .css("left", left ? left : "auto")
926         .css("margin-left", margin_left ? margin_left : "0px")
927         .css("width", $table_results.width());
931  * Initialize sticky columns
932  */
933 function initStickyColumns($table_results) {
934     return $('<table class="sticky_columns"></table>')
935             .insertBefore($table_results)
936             .css("position", "fixed")
937             .css("z-index", "99")
938             .css("width", $table_results.width())
939             .css("margin-left", $('#page_content').css("margin-left"))
940             .css("top", $('#floating_menubar').height())
941             .css("display", "none");
945  * Arrange/Rearrange columns in sticky header
946  */
947 function rearrangeStickyColumns($sticky_columns, $table_results) {
948     var $originalHeader = $table_results.find("thead");
949     var $originalColumns = $originalHeader.find("tr:first").children();
950     var $clonedHeader = $originalHeader.clone();
951     // clone width per cell
952     $clonedHeader.find("tr:first").children().width(function(i,val) {
953         var width = $originalColumns.eq(i).width();
954         var is_firefox = navigator.userAgent.indexOf('Firefox') > -1;
955         if (! is_firefox) {
956             width += 1;
957         }
958         return width;
959     });
960     $sticky_columns.empty().append($clonedHeader);
964  * Adjust sticky columns on horizontal/vertical scroll for all tables
965  */
966 function handleAllStickyColumns() {
967     $('.sticky_columns').each(function () {
968         handleStickyColumns($(this), $(this).next('.table_results'));
969     });
973  * Adjust sticky columns on horizontal/vertical scroll
974  */
975 function handleStickyColumns($sticky_columns, $table_results) {
976     var currentScrollX = $(window).scrollLeft();
977     var windowOffset = $(window).scrollTop();
978     var tableStartOffset = $table_results.offset().top;
979     var tableEndOffset = tableStartOffset + $table_results.height();
980     if (windowOffset >= tableStartOffset && windowOffset <= tableEndOffset) {
981         //for horizontal scrolling
982         if(prevScrollX != currentScrollX) {
983             prevScrollX = currentScrollX;
984             setStickyColumnsPosition($sticky_columns, $table_results, "absolute", $('#floating_menubar').height() + windowOffset - tableStartOffset);
985         //for vertical scrolling
986         } else {
987             setStickyColumnsPosition($sticky_columns, $table_results, "fixed", $('#floating_menubar').height(), $("#pma_navigation").width() - currentScrollX, $('#page_content').css("margin-left"));
988         }
989         $sticky_columns.show();
990     } else {
991         $sticky_columns.hide();
992     }
995 AJAX.registerOnload('sql.js', function () {
996     makeProfilingChart();
997     initProfilingTables();