UPDATE 4.4.0.0
[phpmyadmin.git] / js / sql.js
blobaa4e1474b9c7110089fcd5f9f2af5af58b4ca155
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  * Get the field name for the current field.  Required to construct the query
41  * for grid editing
42  *
43  * @param $table_results enclosing results table
44  * @param $this_field    jQuery object that points to the current field's tr
45  */
46 function getFieldName($table_results, $this_field)
49     var this_field_index = $this_field.index();
50     // ltr or rtl direction does not impact how the DOM was generated
51     // check if the action column in the left exist
52     var left_action_exist = !$table_results.find('th:first').hasClass('draggable');
53     // number of column span for checkbox and Actions
54     var left_action_skip = left_action_exist ? $table_results.find('th:first').attr('colspan') - 1 : 0;
56     // If this column was sorted, the text of the a element contains something
57     // like <small>1</small> that is useful to indicate the order in case
58     // of a sort on multiple columns; however, we dont want this as part
59     // of the column name so we strip it ( .clone() to .end() )
60     var field_name = $table_results
61         .find('thead')
62         .find('th:eq(' + (this_field_index - left_action_skip) + ') a')
63         .clone()    // clone the element
64         .children() // select all the children
65         .remove()   // remove all of them
66         .end()      // go back to the selected element
67         .text();    // grab the text
68     // happens when just one row (headings contain no a)
69     if (field_name === '') {
70         var $heading = $table_results.find('thead').find('th:eq(' + (this_field_index - left_action_skip) + ')').children('span');
71         // may contain column comment enclosed in a span - detach it temporarily to read the column name
72         var $tempColComment = $heading.children().detach();
73         field_name = $heading.text();
74         // re-attach the column comment
75         $heading.append($tempColComment);
76     }
78     field_name = $.trim(field_name);
80     return field_name;
83 /**
84  * Unbind all event handlers before tearing down a page
85  */
86 AJAX.registerTeardown('sql.js', function () {
87     $(document).off('click', 'a.delete_row.ajax');
88     $(document).off('submit', '.bookmarkQueryForm');
89     $('input#bkm_label').unbind('keyup');
90     $(document).off('makegrid', ".sqlqueryresults");
91     $(document).off('stickycolumns', ".sqlqueryresults");
92     $("#togglequerybox").unbind('click');
93     $(document).off('click', "#button_submit_query");
94     $("input[name=bookmark_variable]").unbind("keypress");
95     $(document).off('submit', "#sqlqueryform.ajax");
96     $(document).off('click', "input[name=navig].ajax");
97     $(document).off('submit', "form[name='displayOptionsForm'].ajax");
98     $(document).off('mouseenter', 'th.column_heading.pointer');
99     $(document).off('mouseleave', 'th.column_heading.pointer');
100     $(document).off('click', 'th.column_heading.marker');
101     $(window).unbind('scroll');
102     $(document).off("keyup", ".filter_rows");
103     if (codemirror_editor) {
104         codemirror_editor.off('change');
105     } else {
106         $('#sqlquery').off('input propertychange');
107     }
108     $('body').off('click', '.navigation .showAllRows');
109     $('body').off('click','a.browse_foreign');
110     $('body').off('click', '#simulate_dml');
111     $('body').off('keyup', '#sqlqueryform');
112     $('body').off('click', 'form[name="resultsForm"].ajax button[name="submit_mult"], form[name="resultsForm"].ajax input[name="submit_mult"]');
116  * @description <p>Ajax scripts for sql and browse pages</p>
118  * Actions ajaxified here:
119  * <ul>
120  * <li>Retrieve results of an SQL query</li>
121  * <li>Paginate the results table</li>
122  * <li>Sort the results table</li>
123  * <li>Change table according to display options</li>
124  * <li>Grid editing of data</li>
125  * <li>Saving a bookmark</li>
126  * </ul>
128  * @name        document.ready
129  * @memberOf    jQuery
130  */
131 AJAX.registerOnload('sql.js', function () {
133     $(function () {
134         if (codemirror_editor) {
135             codemirror_editor.on('change', function () {
136                 var query = codemirror_editor.getValue();
137                 if (query) {
138                     $.cookie('auto_saved_sql', query);
139                 }
140             });
141         } else {
142             $('#sqlquery').on('input propertychange', function () {
143                 var query = $('#sqlquery').val();
144                 if (query) {
145                     $.cookie('auto_saved_sql', query);
146                 }
147             });
148         }
149     });
151     // Delete row from SQL results
152     $(document).on('click', 'a.delete_row.ajax', function (e) {
153         e.preventDefault();
154         var question =  PMA_sprintf(PMA_messages.strDoYouReally, escapeHtml($(this).closest('td').find('div').text()));
155         var $link = $(this);
156         $link.PMA_confirm(question, $link.attr('href'), function (url) {
157             $msgbox = PMA_ajaxShowMessage();
158             $.get(url, {'ajax_request': true, 'is_js_confirmed': true}, function (data) {
159                 if (data.success) {
160                     PMA_ajaxShowMessage(data.message);
161                     $link.closest('tr').remove();
162                 } else {
163                     PMA_ajaxShowMessage(data.error, false);
164                 }
165             });
166         });
167     });
169     // Ajaxification for 'Bookmark this SQL query'
170     $(document).on('submit', '.bookmarkQueryForm', function (e) {
171         e.preventDefault();
172         PMA_ajaxShowMessage();
173         $.post($(this).attr('action'), 'ajax_request=1&' + $(this).serialize(), function (data) {
174             if (data.success) {
175                 PMA_ajaxShowMessage(data.message);
176             } else {
177                 PMA_ajaxShowMessage(data.error, false);
178             }
179         });
180     });
182     /* Hides the bookmarkoptions checkboxes when the bookmark label is empty */
183     $('input#bkm_label').keyup(function () {
184         $('input#id_bkm_all_users, input#id_bkm_replace')
185             .parent()
186             .toggle($(this).val().length > 0);
187     }).trigger('keyup');
189     /**
190      * Attach the {@link makegrid} function to a custom event, which will be
191      * triggered manually everytime the table of results is reloaded
192      * @memberOf    jQuery
193      */
194     $(document).on('makegrid', ".sqlqueryresults", function () {
195         $('.table_results').each(function () {
196             PMA_makegrid(this);
197         });
198     });
200     /*
201      * Attach a custom event for sticky column headings which will be
202      * triggered manually everytime the table of results is reloaded
203      * @memberOf    jQuery
204      */
205     $(document).on('stickycolumns', ".sqlqueryresults", function () {
206         $(".sticky_columns").remove();
207         $(".table_results").each(function () {
208             var $table_results = $(this);
209             //add sticky columns div
210             var $stick_columns = initStickyColumns($table_results);
211             rearrangeStickyColumns($stick_columns, $table_results);
212             //adjust sticky columns on scroll
213             $(window).bind('scroll', function() {
214                 handleStickyColumns($stick_columns, $table_results);
215             });
216         });
217     });
219     /**
220      * Append the "Show/Hide query box" message to the query input form
221      *
222      * @memberOf jQuery
223      * @name    appendToggleSpan
224      */
225     // do not add this link more than once
226     if (! $('#sqlqueryform').find('a').is('#togglequerybox')) {
227         $('<a id="togglequerybox"></a>')
228         .html(PMA_messages.strHideQueryBox)
229         .appendTo("#sqlqueryform")
230         // initially hidden because at this point, nothing else
231         // appears under the link
232         .hide();
234         // Attach the toggling of the query box visibility to a click
235         $("#togglequerybox").bind('click', function () {
236             var $link = $(this);
237             $link.siblings().slideToggle("fast");
238             if ($link.text() == PMA_messages.strHideQueryBox) {
239                 $link.text(PMA_messages.strShowQueryBox);
240                 // cheap trick to add a spacer between the menu tabs
241                 // and "Show query box"; feel free to improve!
242                 $('#togglequerybox_spacer').remove();
243                 $link.before('<br id="togglequerybox_spacer" />');
244             } else {
245                 $link.text(PMA_messages.strHideQueryBox);
246             }
247             // avoid default click action
248             return false;
249         });
250     }
253     /**
254      * Event handler for sqlqueryform.ajax button_submit_query
255      *
256      * @memberOf    jQuery
257      */
258     $(document).on('click', "#button_submit_query", function (event) {
259         $(".success,.error").hide();
260         //hide already existing error or success message
261         var $form = $(this).closest("form");
262         // the Go button related to query submission was clicked,
263         // instead of the one related to Bookmarks, so empty the
264         // id_bookmark selector to avoid misinterpretation in
265         // import.php about what needs to be done
266         $form.find("select[name=id_bookmark]").val("");
267         // let normal event propagation happen
268     });
270     /**
271      * Event handler for hitting enter on sqlqueryform bookmark_variable
272      * (the Variable textfield in Bookmarked SQL query section)
273      *
274      * @memberOf    jQuery
275      */
276     $("input[name=bookmark_variable]").bind("keypress", function (event) {
277         // force the 'Enter Key' to implicitly click the #button_submit_bookmark
278         var keycode = (event.keyCode ? event.keyCode : (event.which ? event.which : event.charCode));
279         if (keycode == 13) { // keycode for enter key
280             // When you press enter in the sqlqueryform, which
281             // has 2 submit buttons, the default is to run the
282             // #button_submit_query, because of the tabindex
283             // attribute.
284             // This submits #button_submit_bookmark instead,
285             // because when you are in the Bookmarked SQL query
286             // section and hit enter, you expect it to do the
287             // same action as the Go button in that section.
288             $("#button_submit_bookmark").click();
289             return false;
290         } else  {
291             return true;
292         }
293     });
295     /**
296      * Ajax Event handler for 'SQL Query Submit'
297      *
298      * @see         PMA_ajaxShowMessage()
299      * @memberOf    jQuery
300      * @name        sqlqueryform_submit
301      */
302     $(document).on('submit', "#sqlqueryform.ajax", function (event) {
303         event.preventDefault();
305         var $form = $(this);
306         if (codemirror_editor) {
307             $form[0].elements.sql_query.value = codemirror_editor.getValue();
308         }
309         if (! checkSqlQuery($form[0])) {
310             return false;
311         }
313         // remove any div containing a previous error message
314         $('div.error').remove();
316         var $msgbox = PMA_ajaxShowMessage();
317         var $sqlqueryresultsouter = $('#sqlqueryresultsouter');
319         PMA_prepareForAjaxRequest($form);
321         $.post($form.attr('action'), $form.serialize() + '&ajax_page_request=true', function (data) {
322             if (typeof data !== 'undefined' && data.success === true) {
323                 // success happens if the query returns rows or not
325                 // show a message that stays on screen
326                 if (typeof data.action_bookmark != 'undefined') {
327                     // view only
328                     if ('1' == data.action_bookmark) {
329                         $('#sqlquery').text(data.sql_query);
330                         // send to codemirror if possible
331                         setQuery(data.sql_query);
332                     }
333                     // delete
334                     if ('2' == data.action_bookmark) {
335                         $("#id_bookmark option[value='" + data.id_bookmark + "']").remove();
336                         // if there are no bookmarked queries now (only the empty option),
337                         // remove the bookmark section
338                         if ($('#id_bookmark option').length == 1) {
339                             $('#fieldsetBookmarkOptions').hide();
340                             $('#fieldsetBookmarkOptionsFooter').hide();
341                         }
342                     }
343                 }
344                 $sqlqueryresultsouter
345                     .show()
346                     .html(data.message);
347                 PMA_highlightSQL($sqlqueryresultsouter);
349                 if (data._menu) {
350                     AJAX.cache.menus.replace(data._menu);
351                     AJAX.cache.menus.add(data._menuHash, data._menu);
352                 } else if (data._menuHash) {
353                     AJAX.cache.menus.replace(AJAX.cache.menus.get(data._menuHash));
354                 }
356                 if (data._params) {
357                     PMA_commonParams.setAll(data._params);
358                 }
360                 if (typeof data.ajax_reload != 'undefined') {
361                     if (data.ajax_reload.reload) {
362                         if (data.ajax_reload.table_name) {
363                             PMA_commonParams.set('table', data.ajax_reload.table_name);
364                             PMA_commonActions.refreshMain();
365                         } else {
366                             PMA_reloadNavigation();
367                         }
368                     }
369                 } else if (typeof data.reload != 'undefined') {
370                     // this happens if a USE or DROP command was typed
371                     PMA_commonActions.setDb(data.db);
372                     var url;
373                     if (data.db) {
374                         if (data.table) {
375                             url = 'table_sql.php';
376                         } else {
377                             url = 'db_sql.php';
378                         }
379                     } else {
380                         url = 'server_sql.php';
381                     }
382                     PMA_commonActions.refreshMain(url, function () {
383                         $('#sqlqueryresultsouter')
384                             .show()
385                             .html(data.message);
386                         PMA_highlightSQL($('#sqlqueryresultsouter'));
387                     });
388                 }
390                 $('.sqlqueryresults').trigger('makegrid').trigger('stickycolumns');
391                 $('#togglequerybox').show();
392                 PMA_init_slider();
394                 if (typeof data.action_bookmark == 'undefined') {
395                     if ($('#sqlqueryform input[name="retain_query_box"]').is(':checked') !== true) {
396                         if ($("#togglequerybox").siblings(":visible").length > 0) {
397                             $("#togglequerybox").trigger('click');
398                         }
399                     }
400                 }
401             } else if (typeof data !== 'undefined' && data.success === false) {
402                 // show an error message that stays on screen
403                 $sqlqueryresultsouter
404                     .show()
405                     .html(data.error);
406             }
407             PMA_ajaxRemoveMessage($msgbox);
408         }); // end $.post()
409     }); // end SQL Query submit
411     /**
412      * Ajax Event handler for the display options
413      * @memberOf    jQuery
414      * @name        displayOptionsForm_submit
415      */
416     $(document).on('submit', "form[name='displayOptionsForm'].ajax", function (event) {
417         event.preventDefault();
419         $form = $(this);
421         var $msgbox = PMA_ajaxShowMessage();
422         $.post($form.attr('action'), $form.serialize() + '&ajax_request=true', function (data) {
423             PMA_ajaxRemoveMessage($msgbox);
424             var $sqlqueryresults = $form.parents(".sqlqueryresults");
425             $sqlqueryresults
426              .html(data.message)
427              .trigger('makegrid')
428              .trigger('stickycolumns');
429             PMA_init_slider();
430             PMA_highlightSQL($sqlqueryresults);
431         }); // end $.post()
432     }); //end displayOptionsForm handler
434     // Filter row handling. --STARTS--
435     $(document).on("keyup", ".filter_rows", function () {
436         var unique_id = $(this).data("for");
437         var $target_table = $(".table_results[data-uniqueId='" + unique_id + "']");
438         var $header_cells = $target_table.find("th[data-column]");
439         var target_columns = Array();
440         // To handle colspan=4, in case of edit,copy etc options.
441         var dummy_th = ($(".edit_row_anchor").length !== 0 ?
442             '<th class="hide dummy_th"></th><th class="hide dummy_th"></th><th class="hide dummy_th"></th>'
443             : '');
444         // Selecting columns that will be considered for filtering and searching.
445         $header_cells.each(function () {
446             target_columns.push($.trim($(this).text()));
447         });
449         var phrase = $(this).val();
450         // Set same value to both Filter rows fields.
451         $(".filter_rows[data-for='" + unique_id + "']").not(this).val(phrase);
452         // Handle colspan.
453         $target_table.find("thead > tr").prepend(dummy_th);
454         $.uiTableFilter($target_table, phrase, target_columns);
455         $target_table.find("th.dummy_th").remove();
456     });
457     // Filter row handling. --ENDS--
459     // Prompt to confirm on Show All
460     $('body').on('click', '.navigation .showAllRows', function (e) {
461         e.preventDefault();
462         $form = $(this).parents('form');
464         if (! $(this).is(':checked')) { // already showing all rows
465             submitShowAllForm();
466         } else {
467             $form.PMA_confirm(PMA_messages.strShowAllRowsWarning, $form.attr('action'), function (url) {
468                 submitShowAllForm();
469             });
470         }
472         function submitShowAllForm() {
473             var submitData = $form.serialize() + '&ajax_request=true&ajax_page_request=true';
474             PMA_ajaxShowMessage();
475             $.post($form.attr('action'), submitData, AJAX.responseHandler);
476         }
477     });
479     $('body').on('keyup', '#sqlqueryform', function () {
480         PMA_handleSimulateQueryButton();
481     });
483     /**
484      * Ajax event handler for 'Simulate DML'.
485      */
486     $('body').on('click', '#simulate_dml', function () {
487         var $form = $('#sqlqueryform');
488         var query = '';
489         var delimiter = $('#id_sql_delimiter').val();
490         var db_name = $form.find('input[name="db"]').val();
492         if (codemirror_editor) {
493             query = codemirror_editor.getValue();
494         } else {
495             query = $('#sqlquery').val();
496         }
498         if (query.length === 0) {
499             alert(PMA_messages.strFormEmpty);
500             $('#sqlquery').focus();
501             return false;
502         }
504         var $msgbox = PMA_ajaxShowMessage();
505         $.ajax({
506             type: 'POST',
507             url: $form.attr('action'),
508             data: {
509                 token: $form.find('input[name="token"]').val(),
510                 db: db_name,
511                 ajax_request: '1',
512                 simulate_dml: '1',
513                 sql_query: query,
514                 sql_delimiter: delimiter
515             },
516             success: function (response) {
517                 PMA_ajaxRemoveMessage($msgbox);
518                 if (response.success) {
519                     var dialog_content = '<div class="preview_sql">';
520                     if (response.sql_data) {
521                         var len = response.sql_data.length;
522                         for (var i=0; i<len; i++) {
523                             dialog_content += '<strong>' + PMA_messages.strSQLQuery +
524                                 '</strong>' + response.sql_data[i].sql_query +
525                                 PMA_messages.strMatchedRows +
526                                 ' <a href="' + response.sql_data[i].matched_rows_url +
527                                 '">' + response.sql_data[i].matched_rows + '</a><br>';
528                             if (i<len-1) {
529                                 dialog_content += '<hr>';
530                             }
531                         }
532                     } else {
533                         dialog_content += response.message;
534                     }
535                     dialog_content += '</div>';
536                     $dialog_content = $(dialog_content);
537                     var button_options = {};
538                     button_options[PMA_messages.strClose] = function () {
539                         $(this).dialog('close');
540                     };
541                     var $response_dialog = $('<div />').append($dialog_content).dialog({
542                         minWidth: 540,
543                         maxHeight: 400,
544                         modal: true,
545                         buttons: button_options,
546                         title: PMA_messages.strSimulateDML,
547                         open: function () {
548                             PMA_highlightSQL($(this));
549                         },
550                         close: function () {
551                             $(this).remove();
552                         }
553                     });
554                 } else {
555                     PMA_ajaxShowMessage(response.error);
556                 }
557             },
558             error: function (response) {
559                 PMA_ajaxShowMessage(PMA_messages.strErrorProcessingRequest);
560             }
561         });
562     });
564     /**
565      * Handles multi submits of results browsing page such as edit, delete and export
566      */
567     $('body').on('click', 'form[name="resultsForm"].ajax button[name="submit_mult"], form[name="resultsForm"].ajax input[name="submit_mult"]', function (e) {
568         e.preventDefault();
569         var $button = $(this);
570         var $form = $button.parent('form');
571         var submitData = $form.serialize() + '&ajax_request=true&ajax_page_request=true&submit_mult=' + $button.val();
572         PMA_ajaxShowMessage();
573         $.get($form.attr('action'), submitData, AJAX.responseHandler);
574     });
575 }); // end $()
578  * Starting from some th, change the class of all td under it.
579  * If isAddClass is specified, it will be used to determine whether to add or remove the class.
580  */
581 function PMA_changeClassForColumn($this_th, newclass, isAddClass)
583     // index 0 is the th containing the big T
584     var th_index = $this_th.index();
585     var has_big_t = $this_th.closest('tr').children(':first').hasClass('column_action');
586     // .eq() is zero-based
587     if (has_big_t) {
588         th_index--;
589     }
590     var $tds = $this_th.parents(".table_results").find('tbody tr').find('td.data:eq(' + th_index + ')');
591     if (isAddClass === undefined) {
592         $tds.toggleClass(newclass);
593     } else {
594         $tds.toggleClass(newclass, isAddClass);
595     }
599  * Handles browse foreign values modal dialog
601  * @param object $this_a reference to the browse foreign value link
602  */
603 function browseForeignDialog($this_a)
605     var formId = '#browse_foreign_form';
606     var showAllId = '#foreign_showAll';
607     var tableId = '#browse_foreign_table';
608     var filterId = '#input_foreign_filter';
609     var $dialog = null;
610     $.get($this_a.attr('href'), {'ajax_request': true}, function (data) {
611         // Creates browse foreign value dialog
612         $dialog = $('<div>').append(data.message).dialog({
613             title: PMA_messages.strBrowseForeignValues,
614             width: Math.min($(window).width() - 100, 700),
615             dialogClass: 'browse_foreign_modal',
616             close: function (ev, ui) {
617                 // remove event handlers attached to elements related to dialog
618                 $(tableId).off('click', 'td a.foreign_value');
619                 $(formId).off('click', showAllId);
620                 $(formId).off('submit');
621                 // remove dialog itself
622                 $(this).remove();
623             },
624             create: function () {
625                 $(this).css('maxHeight', $(window).height() - 100);
626             },
627             modal: true
628         });
629     }).done(function () {
630         var showAll = false;
631         $(tableId).on('click', 'td a.foreign_value', function () {
632             var $input = $this_a.prev('input[type=text]');
633             // Check if input exists or get CEdit edit_box
634             if ($input.length === 0 ) {
635                 $input = $this_a.closest('.edit_area').prev('.edit_box');
636             }
637             // Set selected value as input value
638             $input.val($(this).data('key'));
639             $dialog.dialog('close');
640         });
641         $(formId).on('click', showAllId, function () {
642             showAll = true;
643         });
644         $(formId).on('submit', function (e) {
645             e.preventDefault();
646             // if filter value is not equal to old value
647             // then reset page number to 1
648             if ($(filterId).val() != $(filterId).data('old')) {
649                 $(formId).find('select[name=pos]').val('0');
650             }
651             var postParams = $(this).serializeArray();
652             // if showAll button was clicked to submit form then
653             // add showAll button parameter to form
654             if (showAll) {
655                 postParams.push({
656                     name: $(showAllId).attr('name'),
657                     value: $(showAllId).val()
658                 });
659             }
660             // updates values in dialog
661             $.post($(this).attr('action') + '?ajax_request=1', postParams, function (data) {
662                 var $obj = $('<div>').html(data.message);
663                 $(formId).html($obj.find(formId).html());
664                 $(tableId).html($obj.find(tableId).html());
665             });
666             showAll = false;
667         });
668     });
671 AJAX.registerOnload('sql.js', function () {
672     $('body').on('click', 'a.browse_foreign', function (e) {
673         e.preventDefault();
674         browseForeignDialog($(this));
675     });
677     /**
678      * vertical column highlighting in horizontal mode when hovering over the column header
679      */
680     $(document).on('mouseenter', 'th.column_heading.pointer', function (e) {
681         PMA_changeClassForColumn($(this), 'hover', true);
682     });
683     $(document).on('mouseleave', 'th.column_heading.pointer', function (e) {
684         PMA_changeClassForColumn($(this), 'hover', false);
685     });
687     /**
688      * vertical column marking in horizontal mode when clicking the column header
689      */
690     $(document).on('click', 'th.column_heading.marker', function () {
691         PMA_changeClassForColumn($(this), 'marked');
692     });
694     /**
695      * create resizable table
696      */
697     $(".sqlqueryresults").trigger('makegrid').trigger('stickycolumns');
701  * Profiling Chart
702  */
703 function makeProfilingChart()
705     if ($('#profilingchart').length === 0 ||
706         $('#profilingchart').html().length !== 0 ||
707         !$.jqplot || !$.jqplot.Highlighter || !$.jqplot.PieRenderer
708     ) {
709         return;
710     }
712     var data = [];
713     $.each(jQuery.parseJSON($('#profilingChartData').html()), function (key, value) {
714         data.push([key, parseFloat(value)]);
715     });
717     // Remove chart and data divs contents
718     $('#profilingchart').html('').show();
719     $('#profilingChartData').html('');
721     PMA_createProfilingChartJqplot('profilingchart', data);
725  * initialize profiling data tables
726  */
727 function initProfilingTables()
729     if (!$.tablesorter) {
730         return;
731     }
733     $('#profiletable').tablesorter({
734         widgets: ['zebra'],
735         sortList: [[0, 0]],
736         textExtraction: function (node) {
737             if (node.children.length > 0) {
738                 return node.children[0].innerHTML;
739             } else {
740                 return node.innerHTML;
741             }
742         }
743     });
745     $('#profilesummarytable').tablesorter({
746         widgets: ['zebra'],
747         sortList: [[1, 1]],
748         textExtraction: function (node) {
749             if (node.children.length > 0) {
750                 return node.children[0].innerHTML;
751             } else {
752                 return node.innerHTML;
753             }
754         }
755     });
759  * Set position, left, top, width of sticky_columns div
760  */
761 function setStickyColumnsPosition($sticky_columns, $table_results, position, top, left, margin_left) {
762     $sticky_columns
763         .css("position", position)
764         .css("top", top)
765         .css("left", left ? left : "auto")
766         .css("margin-left", margin_left ? margin_left : "0px")
767         .css("width", $table_results.width());
771  * Initialize sticky columns
772  */
773 function initStickyColumns($table_results) {
774     var $sticky_columns = $('<table class="sticky_columns"></table>')
775             .insertBefore($table_results)
776             .css("position", "fixed")
777             .css("z-index", "99")
778             .css("width", $table_results.width())
779             .css("margin-left", $('#page_content').css("margin-left"))
780             .css("top", $('#floating_menubar').height())
781             .css("display", "none");
782     return $sticky_columns;
786  * Arrange/Rearrange columns in sticky header
787  */
788 function rearrangeStickyColumns($sticky_columns, $table_results) {
789     var $originalHeader = $table_results.find("thead");
790     var $originalColumns = $originalHeader.find("tr:first").children();
791     var $clonedHeader = $originalHeader.clone();
792     // clone width per cell
793     $clonedHeader.find("tr:first").children().width(function(i,val) {
794         var width = $originalColumns.eq(i).width();
795         var is_firefox = navigator.userAgent.indexOf('Firefox') > -1;
796         if (! is_firefox) {
797             width += 1;
798         }
799         return width;
800     });
801     $sticky_columns.empty().append($clonedHeader);
805  * Adjust sticky columns on horizontal/vertical scroll for all tables
806  */
807 function handleAllStickyColumns() {
808     $('.sticky_columns').each(function () {
809         handleStickyColumns($(this), $(this).next('.table_results'));
810     });
814  * Adjust sticky columns on horizontal/vertical scroll
815  */
816 function handleStickyColumns($sticky_columns, $table_results) {
817     var currentScrollX = $(window).scrollLeft();
818     var windowOffset = $(window).scrollTop();
819     var tableStartOffset = $table_results.offset().top;
820     var tableEndOffset = tableStartOffset + $table_results.height();
821     if (windowOffset >= tableStartOffset && windowOffset <= tableEndOffset) {
822         //for horizontal scrolling
823         if(prevScrollX != currentScrollX) {
824             prevScrollX = currentScrollX;
825             setStickyColumnsPosition($sticky_columns, $table_results, "absolute", $('#floating_menubar').height() + windowOffset - tableStartOffset);
826         //for vertical scrolling
827         } else {
828             setStickyColumnsPosition($sticky_columns, $table_results, "fixed", $('#floating_menubar').height(), $("#pma_navigation").width() - currentScrollX, $('#page_content').css("margin-left"));
829         }
830         $sticky_columns.show();
831     } else {
832         $sticky_columns.hide();
833     }
836 AJAX.registerOnload('sql.js', function () {
837     makeProfilingChart();
838     initProfilingTables();