No need to show the "Hide query box" initially
[phpmyadmin-themes.git] / js / sql.js
blob219b28c405b30e91f269bfe48938284e7b840185
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 /**
11  * decode a string URL_encoded
12  *
13  * @param string str
14  * @return string the URL-decoded string
15  */
16 function PMA_urldecode(str) {
17     return decodeURIComponent(str.replace(/\+/g, '%20'));
20 /**
21  * Get the field name for the current field.  Required to construct the query
22  * for inline editing
23  *
24  * @param   $this_field  jQuery object that points to the current field's tr
25  * @param   disp_mode    string
26  */
27 function getFieldName($this_field, disp_mode) {
29     if(disp_mode == 'vertical') {
30         var field_name = $this_field.siblings('th').find('a').text();
31         // happens when just one row (headings contain no a)
32         if ("" == field_name) {
33             field_name = $this_field.siblings('th').text();
34         }
35     }
36     else {
37         var this_field_index = $this_field.index();
38         // ltr or rtl direction does not impact how the DOM was generated
39         //
40         // 5 columns to account for the checkbox, edit, appended inline edit, copy and delete anchors but index is zero-based so substract 4
41         var field_name = $('#table_results').find('thead').find('th:nth('+ (this_field_index-4 )+') a').text();
42         // happens when just one row (headings contain no a)
43         if ("" == field_name) {
44             field_name = $('#table_results').find('thead').find('th:nth('+ (this_field_index-4 )+')').text();
45         }
46     }
48     field_name = $.trim(field_name);
50     return field_name;
53 /**
54  * The function that iterates over each row in the table_results and appends a
55  * new inline edit anchor to each table row.
56  *
57  */
58 function appendInlineAnchor() {
59     var disp_mode = $("#top_direction_dropdown").val();
61     if (disp_mode == 'vertical') {
62         // there can be one or two tr containing this class, depending
63         // on the ModifyDeleteAtLeft and ModifyDeleteAtRight cfg parameters
64         $('#table_results tr')
65             .find('.edit_row_anchor')
66             .removeClass('.edit_row_anchor')
67             .parent().each(function() {
68             var $this_tr = $(this);
69             var $cloned_tr = $this_tr.clone();
71             var $img_object = $cloned_tr.find('img:first').attr('title', PMA_messages['strInlineEdit']);
72             var img_src = $img_object.attr('src').replace(/b_edit/,'b_inline_edit');
73             $img_object.attr('src', img_src);
75             $cloned_tr.find('td')
76              .addClass('inline_edit_anchor')
77              .find('a').attr('href', '#')
78              .find('span')
79              .text(' ' + PMA_messages['strInlineEdit'])
80              .prepend($img_object);
82             $cloned_tr.insertAfter($this_tr);
83         });
85         $('#rowsDeleteForm').find('tbody').find('th').each(function() {
86             var $this_th = $(this);
87             if ($this_th.attr('rowspan') == 4) {
88                 $this_th.attr('rowspan', '5');
89             }
90         });
91     }
92     else {
93         $('.edit_row_anchor').each(function() {
95             $this_td = $(this)
96             $this_td.removeClass('edit_row_anchor');
98             var $cloned_anchor = $this_td.clone();
100             var $img_object = $cloned_anchor.find('img').attr('title', PMA_messages['strInlineEdit']);
101             var img_src = $img_object.attr('src').replace(/b_edit/,'b_inline_edit');
102             $img_object.attr('src', img_src);
104             $cloned_anchor.addClass('inline_edit_anchor')
105             .find('a').attr('href', '#')
106             .find('span')
107             .text(' ' + PMA_messages['strInlineEdit'])
108             .prepend($img_object);
110             $this_td.after($cloned_anchor);
111         });
113         $('#rowsDeleteForm').find('thead, tbody').find('th').each(function() {
114             var $this_th = $(this);
115             if ($this_th.attr('colspan') == 4) {
116                 $this_th.attr('colspan', '5');
117             }
118         });
119     }
122 /**#@+
123  * @namespace   jQuery
124  */
127  * @description <p>Ajax scripts for sql and browse pages</p>
129  * Actions ajaxified here:
130  * <ul>
131  * <li>Retrieve results of an SQL query</li>
132  * <li>Paginate the results table</li>
133  * <li>Sort the results table</li>
134  * <li>Change table according to display options</li>
135  * <li>Inline editing of data</li>
136  * </ul>
138  * @name        document.ready
139  * @memberOf    jQuery
140  */
141 $(document).ready(function() {
143     /**
144      * Set a parameter for all Ajax queries made on this page.  Don't let the
145      * web server serve cached pages
146      */
147     $.ajaxSetup({
148         cache: 'false'
149     });
151     /**
152      * current value of the direction in which the table is displayed
153      * @type    String
154      * @fieldOf jQuery
155      * @name    disp_mode
156      */
157     var disp_mode = $("#top_direction_dropdown").val();
159     /**
160      * Update value of {@link jQuery.disp_mode} everytime the direction dropdown changes value
161      * @memberOf    jQuery
162      * @name        direction_dropdown_change
163      */
164     $("#top_direction_dropdown, #bottom_direction_dropdown").live('change', function(event) {
165         disp_mode = $(this).val();
166     })
168     /**
169      * Attach the {@link appendInlineAnchor} function to a custom event, which
170      * will be triggered manually everytime the table of results is reloaded
171      * @memberOf    jQuery
172      */
173     $("#sqlqueryresults").live('appendAnchor',function() {
174         appendInlineAnchor();
175     })
177     /**
178      * Trigger the appendAnchor event to prepare the first table for inline edit
179      * (see $GLOBALS['cfg']['AjaxEnable'])
180      * @memberOf    jQuery
181      * @name        sqlqueryresults_trigger
182      */
183     $("#sqlqueryresults.ajax").trigger('appendAnchor');
185     /**
186      * Append the "Show/Hide query box" message to the query input form
187      *
188      * @memberOf jQuery
189      * @name    appendToggleSpan
190      */
191     // do not add this link more than once
192     if (! $('#sqlqueryform').find('a').is('#togglequerybox')) {
193         $('<a id="togglequerybox"></a>')
194         .html(PMA_messages['strHideQueryBox'])
195         .appendTo("#sqlqueryform")
196         // initially hidden because at this point, nothing else
197         // appears under the link
198         .hide();
200         // Attach the toggling of the query box visibility to a click
201         $("#togglequerybox").bind('click', function() {
202             var $link = $(this)
203             $link.siblings().slideToggle("fast");
204             if ($link.text() == PMA_messages['strHideQueryBox']) {
205                 $link.text(PMA_messages['strShowQueryBox']);
206             } else {
207                 $link.text(PMA_messages['strHideQueryBox']);
208             }
209             // avoid default click action
210             return false;
211         })
212     }
214     /**
215      * Ajax Event handler for 'SQL Query Submit'
216      *
217      * @see         PMA_ajaxShowMessage()
218      * @see         $cfg['AjaxEnable']
219      * @memberOf    jQuery
220      * @name        sqlqueryform_submit
221      */
222     $("#sqlqueryform.ajax").live('submit', function(event) {
223         event.preventDefault();
224         // remove any div containing a previous error message
225         $('.error').remove();
227         $form = $(this);
228         PMA_ajaxShowMessage();
230             if (! $form.find('input:hidden').is('#ajax_request_hidden')) {
231                 $form.append('<input type="hidden" id="ajax_request_hidden" name="ajax_request" value="true" />');
232             }
234         $.post($(this).attr('action'), $(this).serialize() , function(data) {
235             if(data.success == true) {
236                 // fade out previous messages, if any
237                 $('.success').fadeOut();
238                 $('.sqlquery_message').fadeOut();
239                 // show a message that stays on screen
240                 $('#sqlqueryform').before(data.message);
241                 // and display the query
242                 $('<div class="sqlquery_message"></div>')
243                  .html(data.sql_query)
244                  .insertBefore('#sqlqueryform');
245                 // unnecessary div that came from data.sql_query
246                 $('.notice').remove();
247                 $('#sqlqueryresults').show();
248                 // this happens if a USE command was typed
249                 if (typeof data.reload != 'undefined') {
250                     $form.find('input[name=db]').val(data.db);
251                     // need to regenerate the whole upper part
252                     $form.find('input[name=ajax_request]').remove();
253                     $form.append('<input type="hidden" name="reload" value="true" />');
254                     $.post('db_sql.php', $form.serialize(), function(data) {
255                         $('body').html(data);
256                     }); // end inner post
257                 }
258             }
259             else if (data.success == false ) {
260                 // show an error message that stays on screen
261                 $('#sqlqueryform').before(data.error);
262                 $('#sqlqueryresults').hide();
263             }
264             else {
265                 // real results are returned
266                 $('#sqlqueryresults').show();
267                 $("#sqlqueryresults").html(data);
268                 $("#sqlqueryresults").trigger('appendAnchor');
269                 $('#togglequerybox').show();
270                 if($("#togglequerybox").siblings(":visible").length > 0) {
271                     $("#togglequerybox").trigger('click');
272                 }
273                 PMA_init_slider();
274             }
275         }) // end $.post()
276     }) // end SQL Query submit
278     /**
279      * Ajax Event handlers for Paginating the results table
280      */
282     /**
283      * Paginate when we click any of the navigation buttons
284      * (only if the element has the ajax class, see $cfg['AjaxEnable'])
285      * @memberOf    jQuery
286      * @name        paginate_nav_button_click
287      * @uses        PMA_ajaxShowMessage()
288      * @see         $cfg['AjaxEnable']
289      */
290     $("input[name=navig].ajax").live('click', function(event) {
291         /** @lends jQuery */
292         event.preventDefault();
294         PMA_ajaxShowMessage();
296         /**
297          * @var $the_form    Object referring to the form element that paginates the results table
298          */
299         var $the_form = $(this).parent("form");
301         $the_form.append('<input type="hidden" name="ajax_request" value="true" />');
303         $.post($the_form.attr('action'), $the_form.serialize(), function(data) {
304             $("#sqlqueryresults").html(data);
305             $("#sqlqueryresults").trigger('appendAnchor');
306             PMA_init_slider();
307         }) // end $.post()
308     })// end Paginate results table
310     /**
311      * Paginate results with Page Selector dropdown
312      * @memberOf    jQuery
313      * @name        paginate_dropdown_change
314      * @see         $cfg['AjaxEnable']
315      */
316     $("#pageselector").live('change', function(event) {
317         var $the_form = $(this).parent("form");
319         if ($(this).hasClass('ajax')) {
320             event.preventDefault();
322             PMA_ajaxShowMessage();
324             $.post($the_form.attr('action'), $the_form.serialize() + '&ajax_request=true', function(data) {
325                 $("#sqlqueryresults").html(data);
326                 $("#sqlqueryresults").trigger('appendAnchor');
327                 PMA_init_slider();
328             }) // end $.post()
329         } else {
330             $the_form.submit();
331         }
333     })// end Paginate results with Page Selector
335     /**
336      * Ajax Event handler for sorting the results table
337      * @memberOf    jQuery
338      * @name        table_results_sort_click
339      * @see         $cfg['AjaxEnable']
340      */
341     $("#table_results.ajax").find("a[title=Sort]").live('click', function(event) {
342         event.preventDefault();
344         PMA_ajaxShowMessage();
346         $anchor = $(this);
348         $.get($anchor.attr('href'), $anchor.serialize() + '&ajax_request=true', function(data) {
349             $("#sqlqueryresults")
350              .html(data)
351              .trigger('appendAnchor');
352         }) // end $.get()
353     })//end Sort results table
355     /**
356      * Ajax Event handler for the display options
357      * @memberOf    jQuery
358      * @name        displayOptionsForm_submit
359      * @see         $cfg['AjaxEnable']
360      */
361     $("#displayOptionsForm.ajax").live('submit', function(event) {
362         event.preventDefault();
364         $form = $(this);
366         $.post($form.attr('action'), $form.serialize() + '&ajax_request=true' , function(data) {
367             $("#sqlqueryresults")
368              .html(data)
369              .trigger('appendAnchor');
370             PMA_init_slider();
371         }) // end $.post()
372     })
373     //end displayOptionsForm handler
375     /**
376      * Ajax Event handlers for Inline Editing
377      */
379     /**
380      * On click, replace the fields of current row with an input/textarea
381      * @memberOf    jQuery
382      * @name        inline_edit_start
383      * @see         PMA_ajaxShowMessage()
384      * @see         getFieldName()
385      */
386     $(".inline_edit_anchor").live('click', function(event) {
387         /** @lends jQuery */
388         event.preventDefault();
390         $(this).removeClass('inline_edit_anchor').addClass('inline_edit_active');
392         // Initialize some variables
393         if(disp_mode == 'vertical') {
394             /**
395              * @var this_row_index  Index of the current <td> in the parent <tr>
396              *                      Current <td> is the inline edit anchor.
397              */
398             var this_row_index = $(this).index();
399             /**
400              * @var $input_siblings  Object referring to all inline editable events from same row
401              */
402             var $input_siblings = $(this).parents('tbody').find('tr').find('.inline_edit:nth('+this_row_index+')');
403             /**
404              * @var where_clause    String containing the WHERE clause to select this row
405              */
406             var where_clause = $(this).parents('tbody').find('tr').find('.where_clause:nth('+this_row_index+')').val();
407         }
408         else {
409             var $input_siblings = $(this).parent('tr').find('.inline_edit');
410             var where_clause = $(this).parent('tr').find('.where_clause').val();
411         }
413         $input_siblings.each(function() {
414             /** @lends jQuery */
415             /**
416              * @var data_value  Current value of this field
417              */
418             var data_value = $(this).html();
420             // We need to retrieve the value from the server for truncated/relation fields
421             // Find the field name
423             /**
424              * @var this_field  Object referring to this field (<td>)
425              */
426             var $this_field = $(this);
427             /**
428              * @var field_name  String containing the name of this field.
429              * @see getFieldName()
430              */
431             var field_name = getFieldName($this_field, disp_mode);
433             // In each input sibling, wrap the current value in a textarea
434             // and store the current value in a hidden span
435             if($this_field.is(':not(.truncated, .transformed, .relation, .enum, .null)')) {
436                 // handle non-truncated, non-transformed, non-relation values
437                 // We don't need to get any more data, just wrap the value
438                 $this_field.html('<textarea>'+data_value+'</textarea>')
439                 .append('<span class="original_data">'+data_value+'</span>');
440                 $(".original_data").hide();
441             }
442             else if($this_field.is('.truncated, .transformed')) {
443                 /** @lends jQuery */
444                 //handle truncated/transformed values values
446                 /**
447                  * @var sql_query   String containing the SQL query used to retrieve value of truncated/transformed data
448                  */
449                 var sql_query = 'SELECT ' + field_name + ' FROM ' + window.parent.table + ' WHERE ' + where_clause;
451                 // Make the Ajax call and get the data, wrap it and insert it
452                 $.post('sql.php', {
453                     'token' : window.parent.token,
454                     'db' : window.parent.db,
455                     'ajax_request' : true,
456                     'sql_query' : sql_query,
457                     'inline_edit' : true
458                 }, function(data) {
459                     if(data.success == true) {
460                         $this_field.html('<textarea>'+data.value+'</textarea>')
461                         .append('<span class="original_data">'+data_value+'</span>');
462                         $(".original_data").hide();
463                     }
464                     else {
465                         PMA_ajaxShowMessage(data.error);
466                     }
467                 }) // end $.post()
468             }
469             else if($this_field.is('.relation')) {
470                 /** @lends jQuery */
471                 //handle relations
473                 /**
474                  * @var curr_value  String containing the current value of this relational field
475                  */
476                 var curr_value = $this_field.find('a').text();
478                 /**
479                  * @var post_params Object containing parameters for the POST request
480                  */
481                 var post_params = {
482                         'ajax_request' : true,
483                         'get_relational_values' : true,
484                         'db' : window.parent.db,
485                         'table' : window.parent.table,
486                         'column' : field_name,
487                         'token' : window.parent.token,
488                         'curr_value' : curr_value
489                 }
491                 $.post('sql.php', post_params, function(data) {
492                     $this_field.html(data.dropdown)
493                     .append('<span class="original_data">'+data_value+'</span>');
494                     $(".original_data").hide();
495                 }) // end $.post()
496             }
497             else if($this_field.is('.enum')) {
498                 /** @lends jQuery */
499                 //handle enum fields
500                 /**
501                  * @var curr_value  String containing the current value of this relational field
502                  */
503                 var curr_value = $this_field.text();
505                 /**
506                  * @var post_params Object containing parameters for the POST request
507                  */
508                 var post_params = {
509                         'ajax_request' : true,
510                         'get_enum_values' : true,
511                         'db' : window.parent.db,
512                         'table' : window.parent.table,
513                         'column' : field_name,
514                         'token' : window.parent.token,
515                         'curr_value' : curr_value
516                 }
518                 $.post('sql.php', post_params, function(data) {
519                     $this_field.html(data.dropdown)
520                     .append('<span class="original_data">'+data_value+'</span>');
521                     $(".original_data").hide();
522                 }) // end $.post()
523             }
524             else if($this_field.is('.null')) {
525                 //handle null fields
526                 $this_field.html('<textarea></textarea>')
527                 .append('<span class="original_data">NULL</span>');
528                 $(".original_data").hide();
529             }
530         })
531     }) // End On click, replace the current field with an input/textarea
533     /**
534      * After editing, clicking again should post data
535      *
536      * @memberOf    jQuery
537      * @name        inline_edit_save
538      * @see         PMA_ajaxShowMessage()
539      * @see         getFieldName()
540      */
541     $(".inline_edit_active").live('click', function(event) {
542         /** @lends jQuery */
543         event.preventDefault();
545         /**
546          * @var $this_td    Object referring to the td containing the
547          * "Inline Edit" link that was clicked to save the row that is
548          * being edited
549          *
550          */
551         var $this_td = $(this);
553         var $test_element = ''; // to test the presence of a element
555         // Initialize variables
556         if(disp_mode == 'vertical') {
557             /**
558              * @var this_td_index  Index of the current <td> in the parent <tr>
559              *                      Current <td> is the inline edit anchor.
560              */
561             var this_td_index = $this_td.index();
562             /**
563              * @var $input_siblings  Object referring to all inline editable events from same row
564              */
565             var $input_siblings = $this_td.parents('tbody').find('tr').find('.inline_edit:nth('+this_td_index+')');
566             /**
567              * @var where_clause    String containing the WHERE clause to select this row
568              */
569             var where_clause = $this_td.parents('tbody').find('tr').find('.where_clause:nth('+this_td_index+')').val();
570         }
571         else {
572             var $input_siblings = $this_td.parent('tr').find('.inline_edit');
573             var where_clause = $this_td.parent('tr').find('.where_clause').val();
574         }
576         /**
577          * @var nonunique   Boolean, whether this row is unique or not
578          */
579         if($this_td.is('.nonunique')) {
580             var nonunique = 0;
581         }
582         else {
583             var nonunique = 1;
584         }
586         // Collect values of all fields to submit, we don't know which changed
587         /**
588          * @var params_to_submit    Array containing the name/value pairs of all fields
589          */
590         var params_to_submit = {};
591         /**
592          * @var relation_fields Array containing the name/value pairs of relational fields
593          */
594         var relation_fields = {};
595         /**
596          * @var transform_fields    Array containing the name/value pairs for transformed fields
597          */
598         var transform_fields = {};
599         /**
600          * @var transformation_fields   Boolean, if there are any transformed fields in this row
601          */
602         var transformation_fields = false;
604         $input_siblings.each(function() {
605             /** @lends jQuery */
606             /**
607              * @var this_field  Object referring to this field (<td>)
608              */
609             var $this_field = $(this);
610             /**
611              * @var field_name  String containing the name of this field.
612              * @see getFieldName()
613              */
614             var field_name = getFieldName($this_field, disp_mode);
616             /**
617              * @var this_field_params   Array temporary storage for the name/value of current field
618              */
619             var this_field_params = {};
621             if($this_field.is('.transformed')) {
622                 transformation_fields =  true;
623             }
625             if($this_field.is(":not(.relation, .enum)")) {
626                 this_field_params[field_name] = $this_field.find('textarea').val();
627                 if($this_field.is('.transformed')) {
628                     $.extend(transform_fields, this_field_params);
629                 }
630             }
631             else {
632                 // results from a drop-down
633                 $test_element = $this_field.find('select');
634                 if ($test_element.length != 0) {
635                     this_field_params[field_name] = $test_element.val();
636                 }
638                 // results from Browse foreign value
639                 $test_element = $this_field.find('span.curr_value');
640                 if ($test_element.length != 0) {
641                     this_field_params[field_name] = $test_element.text();
642                 }
644                 if($this_field.is('.relation')) {
645                     $.extend(relation_fields, this_field_params);
646                 }
647             }
649             $.extend(params_to_submit, this_field_params);
650         })
652         /**
653          * @var sql_query   String containing the SQL query to update this row
654          */
655         var sql_query = 'UPDATE ' + window.parent.table + ' SET ';
657         // $.each() not used here since it cause problems when there is a column 
658         // in the table with the name 'length'. See bug #3184827
659         var value;
660         for (var key in params_to_submit) {
661                 value = params_to_submit[key];
662                 if (value.length == 0) {
663                         sql_query += ' ' + key + "=NULL, ";
664                 } else {
665                         sql_query += ' ' + key + "='" + value.replace(/'/g, "''") + "' , ";
666                 }
667         }
668         //Remove the last ',' appended in the above loop
669         sql_query = sql_query.replace(/,\s$/, '');
670         sql_query += ' WHERE ' + PMA_urldecode(where_clause);
672         /**
673          * @var rel_fields_list  String, url encoded representation of {@link relations_fields}
674          */
675         var rel_fields_list = $.param(relation_fields);
677         /**
678          * @var transform_fields_list  String, url encoded representation of {@link transform_fields}
679          */
680         var transform_fields_list = $.param(transform_fields);
682         // Make the Ajax post after setting all parameters
683         /**
684          * @var post_params Object containing parameters for the POST request
685          */
686         var post_params = {'ajax_request' : true,
687                             'sql_query' : sql_query,
688                             'disp_direction' : disp_mode,
689                             'token' : window.parent.token,
690                             'db' : window.parent.db,
691                             'table' : window.parent.table,
692                             'clause_is_unique' : nonunique,
693                             'where_clause' : where_clause,
694                             'rel_fields_list' : rel_fields_list,
695                             'do_transformations' : transformation_fields,
696                             'transform_fields_list' : transform_fields_list,
697                             'goto' : 'sql.php',
698                             'submit_type' : 'save'
699                           };
701         $.post('tbl_replace.php', post_params, function(data) {
702             if(data.success == true) {
703                 PMA_ajaxShowMessage(data.message);
704                 $this_td.removeClass('inline_edit_active').addClass('inline_edit_anchor');
706                 $input_siblings.each(function() {
707                     // Inline edit post has been successful.
708                     $this_sibling = $(this);
709                     if($this_sibling.is(':not(.relation, .enum)')) {
710                         /**
711                          * @var new_html    String containing value of the data field after edit
712                          */
713                         var new_html = $this_sibling.find('textarea').val();
715                         if($this_sibling.is('.transformed')) {
716                             var field_name = getFieldName($this_sibling, disp_mode);
717                             $.each(data.transformations, function(key, value) {
718                                 if(key == field_name) {
719                                     if($this_sibling.is('.text_plain, .application_octetstream')) {
720                                         new_html = value;
721                                         return false;
722                                     }
723                                     else {
724                                         var new_value = $this_sibling.find('textarea').val();
725                                         new_html = $(value).append(new_value);
726                                         return false;
727                                     }
728                                 }
729                             })
730                         }
731                     }
732                     else {
733                         var new_html = '';
734                         var new_value = '';
735                         $test_element = $this_sibling.find('select');
736                         if ($test_element.length != 0) {
737                             new_value = $test_element.val();
738                         }
740                         $test_element = $this_sibling.find('span.curr_value');
741                         if ($test_element.length != 0) {
742                             new_value = $test_element.text();
743                         }
746                         if($this_sibling.is('.relation')) {
747                             var field_name = getFieldName($this_sibling, disp_mode);
748                             $.each(data.relations, function(key, value) {
749                                 if(key == field_name) {
750                                     new_html = $(value).append(new_value);
751                                     return false;
752                                 }
753                             })
754                         }
755                         if($this_sibling.is('.enum')) {
756                             new_html = new_value;
757                         }
758                     }
759                     $this_sibling.html(new_html);
760                 })
761             }
762             else {
763                 PMA_ajaxShowMessage(data.error);
764             };
765         }) // end $.post()
766     }) // End After editing, clicking again should post data
767 }, 'top.frame_content') // end $(document).ready()
770  * Starting from some th, change the class of all td under it
771  */
772 function PMA_changeClassForColumn($this_th, newclass) {
773     // index 0 is the th containing the big T
774     var th_index = $this_th.index();
775     // .eq() is zero-based
776     th_index--;
777     var $tr_with_data = $this_th.closest('table').find('tbody tr ').has('td.data');
778     $tr_with_data.each(function() {
779         $(this).find('td.data:eq('+th_index+')').toggleClass(newclass);
780     });
783 $(document).ready(function() {
785     $('.browse_foreign').live('click', function(e) {
786         e.preventDefault();
787         window.open(this.href, 'foreigners', 'width=640,height=240,scrollbars=yes,resizable=yes');
788         $anchor = $(this);
789         $anchor.addClass('browse_foreign_clicked');
790         return false;
791     });
793     /**
794      * vertical column highlighting in horizontal mode when hovering over the column header
795      */
796     $('.column_heading').live('hover', function() {
797         PMA_changeClassForColumn($(this), 'hover');
798         });
800     /**
801      * vertical column marking in horizontal mode when clicking the column header
802      */
803     $('.column_heading').live('click', function() {
804         PMA_changeClassForColumn($(this), 'marked');
805         });
808 /**#@- */