Update from master
[phpmyadmin.git] / js / sql.js
blob73a2b78e3eb0ad67ec32d9ea8c0c514c18a551d6
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;
12 /**
13  * decode a string URL_encoded
14  *
15  * @param string str
16  * @return string the URL-decoded string
17  */
18 function PMA_urldecode(str) {
19     return decodeURIComponent(str.replace(/\+/g, '%20'));
22 function PMA_urlencode(str) {
23     return encodeURIComponent(str.replace(/\%20/g, '+'));
26 /**
27  * Get the field name for the current field.  Required to construct the query
28  * for inline editing
29  *
30  * @param   $this_field  jQuery object that points to the current field's tr
31  * @param   disp_mode    string
32  */
33 function getFieldName($this_field, disp_mode) {
35     if(disp_mode == 'vertical') {
36         var field_name = $this_field.siblings('th').find('a').text();
37         // happens when just one row (headings contain no a)
38         if ("" == field_name) {
39             field_name = $this_field.siblings('th').text();
40         }
41     }
42     else {
43         var this_field_index = $this_field.index();
44         // ltr or rtl direction does not impact how the DOM was generated
45         //
46         // 5 columns to account for the checkbox, edit, appended inline edit, copy and delete anchors but index is zero-based so substract 4
47         var field_name = $('#table_results').find('thead').find('th:nth('+ (this_field_index-4 )+') a').text();
48         // happens when just one row (headings contain no a)
49         if ("" == field_name) {
50             field_name = $('#table_results').find('thead').find('th:nth('+ (this_field_index-4 )+')').text();
51         }
52     }
54     field_name = $.trim(field_name);
56     return field_name;
59 /**
60  * The function that iterates over each row in the table_results and appends a
61  * new inline edit anchor to each table row.
62  *
63  */
64 function appendInlineAnchor() {
65     var disp_mode = $("#top_direction_dropdown").val();
67     if (disp_mode == 'vertical') {
68         // there can be one or two tr containing this class, depending
69         // on the ModifyDeleteAtLeft and ModifyDeleteAtRight cfg parameters
70         $('#table_results tr')
71             .find('.edit_row_anchor')
72             .removeClass('edit_row_anchor')
73             .parent().each(function() {
74             var $this_tr = $(this);
75             var $cloned_tr = $this_tr.clone();
77             var $img_object = $cloned_tr.find('img:first').attr('title', PMA_messages['strInlineEdit']);
78             if ($img_object.length != 0) {
79                 var img_src = $img_object.attr('src').replace(/b_edit/,'b_inline_edit');
80                 $img_object.attr('src', img_src);
81             }
83             $cloned_tr.find('td')
84              .addClass('inline_edit_anchor')
85              .find('a').attr('href', '#');
86             var $edit_span = $cloned_tr.find('span:contains("' + PMA_messages['strEdit'] + '")');
87             var $span = $cloned_tr.find('a').find('span');
88             if ($edit_span.length > 0) {
89                 $span.text(' ' + PMA_messages['strInlineEdit']);
90                 $span.prepend($img_object);
91             } else {
92                 $span.text('');
93                 $span.append($img_object);
94             }
96             $cloned_tr.insertAfter($this_tr);
97         });
99         $('#rowsDeleteForm').find('tbody').find('th').each(function() {
100             var $this_th = $(this);
101             if ($this_th.attr('rowspan') == 4) {
102                 $this_th.attr('rowspan', '5');
103             }
104         });
105     }
106     else {
107         // horizontal mode
108         $('.edit_row_anchor').each(function() {
110             var $this_td = $(this);
111             $this_td.removeClass('edit_row_anchor');
113             var $cloned_anchor = $this_td.clone();
115             var $img_object = $cloned_anchor.find('img').attr('title', PMA_messages['strInlineEdit']);
116             if ($img_object.length != 0) {
117                 var img_src = $img_object.attr('src').replace(/b_edit/,'b_inline_edit');
118                 $img_object.attr('src', img_src);
119                 $cloned_anchor.find('a').attr('href', '#');
120                 var $edit_span = $cloned_anchor.find('span:contains("' + PMA_messages['strEdit'] + '")');
121                 var $span = $cloned_anchor.find('a').find('span');
122                 if ($edit_span.length > 0) {
123                     $span.text(' ' + PMA_messages['strInlineEdit']);
124                     $span.prepend($img_object);
125                 } else {
126                     $span.text('');
127                     $span.append($img_object);
128                 }
129             } else {
130                 // Only text is displayed. See $cfg['PropertiesIconic']
131                 $cloned_anchor.find('a').attr('href', '#');
132                 $cloned_anchor.find('a span').text(PMA_messages['strInlineEdit']);
134                 // the link was too big so <input type="image"> is there
135                 $img_object = $cloned_anchor.find('input:image').attr('title', PMA_messages['strInlineEdit']);
136                 if ($img_object.length > 0) {
137                     var img_src = $img_object.attr('src').replace(/b_edit/,'b_inline_edit');
138                     $img_object.attr('src', img_src);
139                 }
140                 $cloned_anchor
141                  .find('.clickprevimage')
142                  .text(' ' + PMA_messages['strInlineEdit']);
143             }
145             $cloned_anchor
146              .addClass('inline_edit_anchor');
148             $this_td.after($cloned_anchor);
149         });
151         $('#rowsDeleteForm').find('thead, tbody').find('th').each(function() {
152             var $this_th = $(this);
153             if ($this_th.attr('colspan') == 4) {
154                 $this_th.attr('colspan', '5');
155             }
156         });
157     }
160 /**#@+
161  * @namespace   jQuery
162  */
165  * @description <p>Ajax scripts for sql and browse pages</p>
167  * Actions ajaxified here:
168  * <ul>
169  * <li>Retrieve results of an SQL query</li>
170  * <li>Paginate the results table</li>
171  * <li>Sort the results table</li>
172  * <li>Change table according to display options</li>
173  * <li>Inline editing of data</li>
174  * </ul>
176  * @name        document.ready
177  * @memberOf    jQuery
178  */
179 $(document).ready(function() {
181     /**
182      * Set a parameter for all Ajax queries made on this page.  Don't let the
183      * web server serve cached pages
184      */
185     $.ajaxSetup({
186         cache: 'false'
187     });
189     /**
190      * current value of the direction in which the table is displayed
191      * @type    String
192      * @fieldOf jQuery
193      * @name    disp_mode
194      */
195     var disp_mode = $("#top_direction_dropdown").val();
197     /**
198      * Update value of {@link jQuery.disp_mode} everytime the direction dropdown changes value
199      * @memberOf    jQuery
200      * @name        direction_dropdown_change
201      */
202     $("#top_direction_dropdown, #bottom_direction_dropdown").live('change', function(event) {
203         disp_mode = $(this).val();
204     })
206     /**
207      * Attach the {@link appendInlineAnchor} function to a custom event, which
208      * will be triggered manually everytime the table of results is reloaded
209      * @memberOf    jQuery
210      */
211     $("#sqlqueryresults").live('appendAnchor',function() {
212         appendInlineAnchor();
213     })
215     /**
216      * Trigger the appendAnchor event to prepare the first table for inline edit
217      * (see $GLOBALS['cfg']['AjaxEnable'])
218      * @memberOf    jQuery
219      * @name        sqlqueryresults_trigger
220      */
221     $("#sqlqueryresults.ajax").trigger('appendAnchor');
223     /**
224      * Append the "Show/Hide query box" message to the query input form
225      *
226      * @memberOf jQuery
227      * @name    appendToggleSpan
228      */
229     // do not add this link more than once
230     if (! $('#sqlqueryform').find('a').is('#togglequerybox')) {
231         $('<a id="togglequerybox"></a>')
232         .html(PMA_messages['strHideQueryBox'])
233         .appendTo("#sqlqueryform")
234         // initially hidden because at this point, nothing else
235         // appears under the link
236         .hide();
238         // Attach the toggling of the query box visibility to a click
239         $("#togglequerybox").bind('click', function() {
240             var $link = $(this)
241             $link.siblings().slideToggle("fast");
242             if ($link.text() == PMA_messages['strHideQueryBox']) {
243                 $link.text(PMA_messages['strShowQueryBox']);
244                 // cheap trick to add a spacer between the menu tabs
245                 // and "Show query box"; feel free to improve!
246                 $('#togglequerybox_spacer').remove();
247                 $link.before('<br id="togglequerybox_spacer" />');
248             } else {
249                 $link.text(PMA_messages['strHideQueryBox']);
250             }
251             // avoid default click action
252             return false;
253         })
254     }
256     /**
257      * Ajax Event handler for 'SQL Query Submit'
258      *
259      * @see         PMA_ajaxShowMessage()
260      * @see         $cfg['AjaxEnable']
261      * @memberOf    jQuery
262      * @name        sqlqueryform_submit
263      */
264     $("#sqlqueryform.ajax").live('submit', function(event) {
265         event.preventDefault();
267         $form = $(this);
268         if (! checkSqlQuery($form[0])) {
269             return false;
270         }
272         // remove any div containing a previous error message
273         $('.error').remove();
275         var $msgbox = PMA_ajaxShowMessage();
277         PMA_prepareForAjaxRequest($form);
279         $.post($(this).attr('action'), $(this).serialize() , function(data) {
280             if(data.success == true) {
281                 // fade out previous messages, if any
282                 $('.success').fadeOut();
283                 $('.sqlquery_message').fadeOut();
284                 // show a message that stays on screen
285                 if (typeof data.sql_query != 'undefined') {
286                     $('<div class="sqlquery_message"></div>')
287                      .html(data.sql_query)
288                      .insertBefore('#sqlqueryform');
289                     // unnecessary div that came from data.sql_query
290                     $('.notice').remove();
291                 } else {
292                     $('#sqlqueryform').before(data.message);
293                 }
294                 $('#sqlqueryresults').show();
295                 // this happens if a USE command was typed
296                 if (typeof data.reload != 'undefined') {
297                     // Unbind the submit event before reloading. See bug #3295529
298                     $("#sqlqueryform.ajax").die('submit');
299                     $form.find('input[name=db]').val(data.db);
300                     // need to regenerate the whole upper part
301                     $form.find('input[name=ajax_request]').remove();
302                     $form.append('<input type="hidden" name="reload" value="true" />');
303                     $.post('db_sql.php', $form.serialize(), function(data) {
304                         $('body').html(data);
305                     }); // end inner post
306                 }
307             }
308             else if (data.success == false ) {
309                 // show an error message that stays on screen
310                 $('#sqlqueryform').before(data.error);
311                 $('#sqlqueryresults').hide();
312             }
313             else {
314                 // real results are returned
315                 // fade out previous messages, if any
316                 $('.success').fadeOut();
317                 $('.sqlquery_message').fadeOut();
318                 $received_data = $(data);
319                 $zero_row_results = $received_data.find('textarea[name="sql_query"]');
320                 // if zero rows are returned from the query execution
321                 if ($zero_row_results.length > 0) {
322                     $('#sqlquery').val($zero_row_results.val());
323                 } else {
324                     $('#sqlqueryresults').show();
325                     $("#sqlqueryresults").html(data);
326                     $("#sqlqueryresults").trigger('appendAnchor');
327                     $('#togglequerybox').show();
328                     if($("#togglequerybox").siblings(":visible").length > 0) {
329                         $("#togglequerybox").trigger('click');
330                     }
331                     PMA_init_slider();
332                 }
333             }
334             PMA_ajaxRemoveMessage($msgbox);
336         }) // end $.post()
337     }) // end SQL Query submit
339     /**
340      * Ajax Event handlers for Paginating the results table
341      */
343     /**
344      * Paginate when we click any of the navigation buttons
345      * (only if the element has the ajax class, see $cfg['AjaxEnable'])
346      * @memberOf    jQuery
347      * @name        paginate_nav_button_click
348      * @uses        PMA_ajaxShowMessage()
349      * @see         $cfg['AjaxEnable']
350      */
351     $("input[name=navig].ajax").live('click', function(event) {
352         /** @lends jQuery */
353         event.preventDefault();
355         var $msgbox = PMA_ajaxShowMessage();
357         /**
358          * @var $the_form    Object referring to the form element that paginates the results table
359          */
360         var $the_form = $(this).parent("form");
362         $the_form.append('<input type="hidden" name="ajax_request" value="true" />');
364         $.post($the_form.attr('action'), $the_form.serialize(), function(data) {
365             $("#sqlqueryresults").html(data);
366             $("#sqlqueryresults").trigger('appendAnchor');
367             PMA_init_slider();
368             
369             PMA_ajaxRemoveMessage($msgbox);
370         }) // end $.post()
371     })// end Paginate results table
373     /**
374      * Paginate results with Page Selector dropdown
375      * @memberOf    jQuery
376      * @name        paginate_dropdown_change
377      * @see         $cfg['AjaxEnable']
378      */
379     $("#pageselector").live('change', function(event) {
380         var $the_form = $(this).parent("form");
382         if ($(this).hasClass('ajax')) {
383             event.preventDefault();
385             var $msgbox = PMA_ajaxShowMessage();
387             $.post($the_form.attr('action'), $the_form.serialize() + '&ajax_request=true', function(data) {
388                 $("#sqlqueryresults").html(data);
389                 $("#sqlqueryresults").trigger('appendAnchor');
390                 PMA_init_slider();
391                 PMA_ajaxRemoveMessage($msgbox); 
392             }) // end $.post()
393         } else {
394             $the_form.submit();
395         }
397     })// end Paginate results with Page Selector
399     /**
400      * Ajax Event handler for sorting the results table
401      * @memberOf    jQuery
402      * @name        table_results_sort_click
403      * @see         $cfg['AjaxEnable']
404      */
405     $("#table_results.ajax").find("a[title=Sort]").live('click', function(event) {
406         event.preventDefault();
408         var $msgbox = PMA_ajaxShowMessage();
410         $anchor = $(this);
412         $.get($anchor.attr('href'), $anchor.serialize() + '&ajax_request=true', function(data) {
413             $("#sqlqueryresults")
414              .html(data)
415              .trigger('appendAnchor');
416             PMA_ajaxRemoveMessage($msgbox);
417         }) // end $.get()
418     })//end Sort results table
420     /**
421      * Ajax Event handler for the display options
422      * @memberOf    jQuery
423      * @name        displayOptionsForm_submit
424      * @see         $cfg['AjaxEnable']
425      */
426     $("#displayOptionsForm.ajax").live('submit', function(event) {
427         event.preventDefault();
429         $form = $(this);
431         $.post($form.attr('action'), $form.serialize() + '&ajax_request=true' , function(data) {
432             $("#sqlqueryresults")
433              .html(data)
434              .trigger('appendAnchor');
435             PMA_init_slider();
436         }) // end $.post()
437     })
438     //end displayOptionsForm handler
440     /**
441      * Ajax Event handlers for Inline Editing
442      */
444     /**
445      * On click, replace the fields of current row with an input/textarea
446      * @memberOf    jQuery
447      * @name        inline_edit_start
448      * @see         PMA_ajaxShowMessage()
449      * @see         getFieldName()
450      */
451     $(".inline_edit_anchor span a").live('click', function(event) {
452         /** @lends jQuery */
453         event.preventDefault();
455         var $edit_td = $(this).parents('td');
456         $edit_td.removeClass('inline_edit_anchor').addClass('inline_edit_active').parent('tr').addClass('noclick');
458         // Adding submit and hide buttons to inline edit <td>.
459         // For "hide" button the original data to be restored is 
460         //  kept in the jQuery data element 'original_data' inside the <td>.
461         // Looping through all columns or rows, to find the required data and then storing it in an array.
463         var $this_children = $edit_td.children('span.nowrap').children('a').children('span.nowrap');
464         // Keep the original data preserved.
465         $data_a = $edit_td.children('span.nowrap').children('a').clone();
467         // Change the inline edit to save.
468         var $img_object = $this_children.find('img');
470         // If texts are displayed. See $cfg['PropertiesIconic']
471         if ($this_children.parent('a').find('span:contains("' + PMA_messages['strInlineEdit'] + '")').length > 0) {
472             $this_children.text(' ' + PMA_messages['strSave']);
473         } else {
474             $this_children.empty();
475         }
477         // If icons are displayed. See $cfg['PropertiesIconic']
478         if ($img_object.length > 0) {
479             $img_object.attr('title', PMA_messages['strSave']);
480             var img_src = $img_object.attr('src').replace(/b_inline_edit/,'b_save');
481             $img_object.attr('src', img_src);
482             $this_children.prepend($img_object);
483         }
485         // Clone the save link and change it to create the hide link.
486         var $hide_a = $edit_td.children('span.nowrap').children('a').clone().attr('id', 'hide');
487         var $hide_span = $hide_a.find('span');
488         var $img_object = $hide_a.find('span img');
490         // If texts are displayed. See $cfg['PropertiesIconic']
491         if ($hide_a.find('span:contains("' + PMA_messages['strSave'] + '")').length > 0) {
492             $hide_span.text(' ' + PMA_messages['strHide']);
493         } else {
494             $hide_span.empty();
495         }
497         // If icons are displayed. See $cfg['PropertiesIconic']
498         if ($img_object.length > 0) {
499             $img_object.attr('title', PMA_messages['strHide']);
500             var img_src = $img_object.attr('src').replace(/b_save/,'b_close');
501             $img_object.attr('src', img_src);
502             $hide_span.prepend($img_object);
503         }
505         // Add hide icon and/or text.
506         $edit_td.children('span.nowrap').append($('<br /><br />')).append($hide_a);
508         if (disp_mode != 'vertical') {
509             $('#table_results tbody tr td span a#hide').click(function() {
510                 var $this_hide = $(this).parents('td');
512                 var $this_span = $this_hide.find('span');
513                 $this_span.find('a, br').remove();
514                 $this_span.append($data_a.clone());
516                 $this_hide.removeClass("inline_edit_active hover").addClass("inline_edit_anchor");
517                 $this_hide.parent().removeClass("hover noclick");
518                 $this_hide.siblings().removeClass("hover");
520                 var last_column = $this_hide.siblings().length;
521                 var txt = '';
522                 for(var i = 4; i < last_column; i++) {
523                     if($this_hide.siblings("td:eq(" + i + ")").hasClass("inline_edit") == false) {
524                         continue;
525                     }
526                     txt = $this_hide.siblings("td:eq(" + i + ")").data('original_data');
527                     if($this_hide.siblings("td:eq(" + i + ")").children().length != 0) {
528                         $this_hide.siblings("td:eq(" + i + ")").empty();
529                         $this_hide.siblings("td:eq(" + i + ")").append(txt);
530                     }
531                 }
532                 $(this).prev().prev().remove();
533                 $(this).prev().remove();
534                 $(this).remove();
535             });
536         } else {
537             var txt = '';
538             var rows = $edit_td.parent().siblings().length;
540             $('#table_results tbody tr td span a#hide').click(function() {
541                 var $hide_a = $(this);
542                 var pos = $hide_a.parents('td').index();
544                 var $this_span = $hide_a.parent();
545                 $this_span.find('a, br').remove();
546                 $this_span.append($data_a.clone());
548                 var $this_row = $this_span.parents('tr');
549                 // changing inline_edit_active to inline_edit_anchor
550                 $this_row.siblings("tr:eq(3) td:eq(" + pos + ")").removeClass("inline_edit_active").addClass("inline_edit_anchor");
552                 // removing marked and hover classes.
553                 $this_row.parent('tbody').find('tr').find("td:eq(" + pos + ")").removeClass("marked hover");
555                 for( var i = 6; i <= rows + 2; i++){
556                     if( $this_row.siblings("tr:eq(" + i + ") td:eq(" + pos + ")").hasClass("inline_edit") == false) {
557                         continue;
558                     }
559                     txt = $this_row.siblings("tr:eq(" + i + ") td:eq(" + pos + ")").data('original_data');
560                     $this_row.siblings("tr:eq(" + i + ") td:eq(" + pos + ")").empty();
561                     $this_row.siblings("tr:eq(" + i + ") td:eq(" + pos + ")").append(txt);
562                 }
563                 $(this).prev().remove();
564                 $(this).prev().remove();
565                 $(this).remove();
566             });
567         }
569         // Initialize some variables
570         if(disp_mode == 'vertical') {
571             /**
572              * @var this_row_index  Index of the current <td> in the parent <tr>
573              *                      Current <td> is the inline edit anchor.
574              */
575             var this_row_index = $edit_td.index();
576             /**
577              * @var $input_siblings  Object referring to all inline editable events from same row
578              */
579             var $input_siblings = $edit_td.parents('tbody').find('tr').find('.inline_edit:nth('+this_row_index+')');
580             /**
581              * @var where_clause    String containing the WHERE clause to select this row
582              */
583             var where_clause = $edit_td.parents('tbody').find('tr').find('.where_clause:nth('+this_row_index+')').val();
584         }
585         // horizontal mode
586         else {
587             var this_row_index = $edit_td.parent().index();
588             var $input_siblings = $edit_td.parent('tr').find('.inline_edit');
589             var where_clause = $edit_td.parent('tr').find('.where_clause').val();
590         }
592         $input_siblings.each(function() {
593             /** @lends jQuery */
594             /**
595              * @var data_value  Current value of this field
596              */
597             var data_value = $(this).html();
599             // We need to retrieve the value from the server for truncated/relation fields
600             // Find the field name
602             /**
603              * @var this_field  Object referring to this field (<td>)
604              */
605             var $this_field = $(this);
606             /**
607              * @var field_name  String containing the name of this field.
608              * @see getFieldName()
609              */
610             var field_name = getFieldName($this_field, disp_mode);
611             /**
612              * @var relation_curr_value String current value of the field (for fields that are foreign keyed).
613              */
614             var relation_curr_value = $this_field.find('a').text();
615             /**
616              * @var relation_key_or_display_column String relational key if in 'Relational display column' mode,
617              * relational display column if in 'Relational key' mode (for fields that are foreign keyed).
618              */
619             var relation_key_or_display_column = $this_field.find('a').attr('title');
620             /**
621              * @var curr_value String current value of the field (for fields that are of type enum or set).
622              */
623             var curr_value = $this_field.text();
625             if($this_field.is(':not(.not_null)')){
626                 // add a checkbox to mark null for all the field that are nullable.
627                 $this_field.html('<div class="null_div">Null :<input type="checkbox" class="checkbox_null_'+ field_name + '_' + this_row_index +'"></div>');
628                 // check the 'checkbox_null_<field_name>_<row_index>' if the corresponding value is null
629                 if($this_field.is('.null')) {
630                     $('.checkbox_null_' + field_name + '_' + this_row_index).attr('checked', true);
631                 }
633                 // if the select/editor is changed un-check the 'checkbox_null_<field_name>_<row_index>'.
634                 if ($this_field.is('.enum, .set')) {
635                     $this_field.find('select').live('change', function(e) {
636                         $('.checkbox_null_' + field_name + '_' + this_row_index).attr('checked', false);
637                     })
638                 } else if ($this_field.is('.relation')) {
639                     $this_field.find('select').live('change', function(e) {
640                         $('.checkbox_null_' + field_name + '_' + this_row_index).attr('checked', false);
641                     })
642                     $this_field.find('.browse_foreign').live('click', function(e) {
643                         $('.checkbox_null_' + field_name + '_' + this_row_index).attr('checked', false);
644                     })
645                 } else {
646                     $this_field.find('textarea').live('keypress', function(e) {
647                         $('.checkbox_null_' + field_name + '_' + this_row_index).attr('checked', false);
648                     })
649                 }
651                 // if 'checkbox_null_<field_name>_<row_index>' is clicked empty the corresponding select/editor.
652                 $('.checkbox_null_' + field_name + '_' + this_row_index).bind('click', function(e) {
653                     if ($this_field.is('.enum')) {
654                         $this_field.find('select').attr('value', '');
655                     } else if ($this_field.is('.set')) {
656                         $this_field.find('select').find('option').each(function() {
657                             var $option = $(this);
658                             $option.attr('selected', false);
659                         })
660                     } else if ($this_field.is('.relation')) {
661                         // if the dropdown is there to select the foreign value
662                         if ($this_field.find('select').length > 0) {
663                             $this_field.find('select').attr('value', '');
664                         // if foriegn value is selected by browsing foreing values
665                         } else {
666                             $this_field.find('span.curr_value').empty();
667                         }
668                     } else {
669                         $this_field.find('textarea').val('');
670                     }
671                 })
673             } else {
674                 $this_field.html('<div class="null_div"></div>');
675             }
677             // In each input sibling, wrap the current value in a textarea
678             // and store the current value in a hidden span
679             if($this_field.is(':not(.truncated, .transformed, .relation, .enum, .set, .null)')) {
680                 // handle non-truncated, non-transformed, non-relation values
681                 // We don't need to get any more data, just wrap the value
682                 $this_field.append('<textarea>'+data_value+'</textarea>');
683                 $this_field.data('original_data', data_value);
684             }
685             else if($this_field.is('.truncated, .transformed')) {
686                 /** @lends jQuery */
687                 //handle truncated/transformed values values
689                 /**
690                  * @var sql_query   String containing the SQL query used to retrieve value of truncated/transformed data
691                  */
692                 var sql_query = 'SELECT `' + field_name + '` FROM `' + window.parent.table + '` WHERE ' + PMA_urldecode(where_clause);
694                 // Make the Ajax call and get the data, wrap it and insert it
695                 $.post('sql.php', {
696                     'token' : window.parent.token,
697                     'db' : window.parent.db,
698                     'ajax_request' : true,
699                     'sql_query' : sql_query,
700                     'inline_edit' : true
701                 }, function(data) {
702                     if(data.success == true) {
703                         $this_field.append('<textarea>'+data.value+'</textarea>');
704                         $this_field.data('original_data', data_value);
705                     }
706                     else {
707                         PMA_ajaxShowMessage(data.error);
708                     }
709                 }) // end $.post()
710             }
711             else if($this_field.is('.relation')) {
712                 /** @lends jQuery */
713                 //handle relations
715                 /**
716                  * @var post_params Object containing parameters for the POST request
717                  */
718                 var post_params = {
719                         'ajax_request' : true,
720                         'get_relational_values' : true,
721                         'db' : window.parent.db,
722                         'table' : window.parent.table,
723                         'column' : field_name,
724                         'token' : window.parent.token,
725                         'curr_value' : relation_curr_value,
726                         'relation_key_or_display_column' : relation_key_or_display_column
727                 }
729                 $.post('sql.php', post_params, function(data) {
730                     $this_field.append(data.dropdown);
731                     $this_field.data('original_data', data_value);
732                 }) // end $.post()
733             }
734             else if($this_field.is('.enum')) {
735                 /** @lends jQuery */
736                 //handle enum fields
738                 /**
739                  * @var post_params Object containing parameters for the POST request
740                  */
741                 var post_params = {
742                         'ajax_request' : true,
743                         'get_enum_values' : true,
744                         'db' : window.parent.db,
745                         'table' : window.parent.table,
746                         'column' : field_name,
747                         'token' : window.parent.token,
748                         'curr_value' : curr_value
749                 }
750                 $.post('sql.php', post_params, function(data) {
751                     $this_field.append(data.dropdown);
752                     $this_field.data('original_data', data_value);
753                 }) // end $.post()
754             }
755             else if($this_field.is('.set')) {
756                 /** @lends jQuery */
757                 //handle set fields
759                 /**
760                  * @var post_params Object containing parameters for the POST request
761                  */
762                 var post_params = {
763                         'ajax_request' : true,
764                         'get_set_values' : true,
765                         'db' : window.parent.db,
766                         'table' : window.parent.table,
767                         'column' : field_name,
768                         'token' : window.parent.token,
769                         'curr_value' : curr_value
770                 }
772                 $.post('sql.php', post_params, function(data) {
773                     $this_field.append(data.select);
774                     $this_field.data('original_data', data_value);
775                 }) // end $.post()
776             }
777             else if($this_field.is('.null')) {
778                 //handle null fields
779                 $this_field.append('<textarea></textarea>');
780                 $this_field.data('original_data', 'NULL');
781             }
782         })
783     }) // End On click, replace the current field with an input/textarea
785     /**
786      * After editing, clicking again should post data
787      *
788      * @memberOf    jQuery
789      * @name        inline_edit_save
790      * @see         PMA_ajaxShowMessage()
791      * @see         getFieldName()
792      */
793     $(".inline_edit_active span a").live('click', function(event) {
794         /** @lends jQuery */
796         event.preventDefault();
798         /**
799          * @var $this_td    Object referring to the td containing the
800          * "Inline Edit" link that was clicked to save the row that is
801          * being edited
802          *
803          */
804         var $this_td = $(this).parent().parent();
805         var $test_element = ''; // to test the presence of a element
807         // Initialize variables
808         if(disp_mode == 'vertical') {
809             /**
810              * @var this_td_index  Index of the current <td> in the parent <tr>
811              *                      Current <td> is the inline edit anchor.
812              */
813             var this_td_index = $this_td.index();
814             /**
815              * @var $input_siblings  Object referring to all inline editable events from same row
816              */
817             var $input_siblings = $this_td.parents('tbody').find('tr').find('.inline_edit:nth('+this_td_index+')');
818             /**
819              * @var where_clause    String containing the WHERE clause to select this row
820              */
821             var where_clause = $this_td.parents('tbody').find('tr').find('.where_clause:nth('+this_td_index+')').val();
822         } else {
823             var $input_siblings = $this_td.parent('tr').find('.inline_edit');
824             var where_clause = $this_td.parent('tr').find('.where_clause').val();
825         }
827         /**
828          * @var nonunique   Boolean, whether this row is unique or not
829          */
830         if($this_td.is('.nonunique')) {
831             var nonunique = 0;
832         }
833         else {
834             var nonunique = 1;
835         }
837         // Collect values of all fields to submit, we don't know which changed
838         /**
839          * @var relation_fields Array containing the name/value pairs of relational fields
840          */
841         var relation_fields = {};
842         /**
843          * @var relational_display string 'K' if relational key, 'D' if relational display column 
844          */
845         var relational_display = $("#relational_display_K").attr('checked') ? 'K' : 'D';
846         /**
847          * @var transform_fields    Array containing the name/value pairs for transformed fields
848          */
849         var transform_fields = {};
850         /**
851          * @var transformation_fields   Boolean, if there are any transformed fields in this row
852          */
853         var transformation_fields = false;
855         /**
856          * @var sql_query String containing the SQL query to update this row
857          */
858         var sql_query = 'UPDATE `' + window.parent.table + '` SET ';
860         var need_to_post = false;
861         
862         var new_clause = '';
863         var prev_index = -1;
865         $input_siblings.each(function() {
866             /** @lends jQuery */
867             /**
868              * @var this_field  Object referring to this field (<td>)
869              */
870             var $this_field = $(this);
872             /**
873              * @var field_name  String containing the name of this field.
874              * @see getFieldName()
875              */
876             var field_name = getFieldName($this_field, disp_mode);
878             /**
879              * @var this_field_params   Array temporary storage for the name/value of current field
880              */
881             var this_field_params = {};
883             if($this_field.is('.transformed')) {
884                 transformation_fields =  true;
885             }
886             /**
887              * @var is_null String capturing whether 'checkbox_null_<field_name>_<row_index>' is checked.
888              */
889             var is_null = $this_field.find('input:checkbox').is(':checked');
890             var value;
891             var addQuotes = true;
893             if (is_null) {
894                 sql_query += ' `' + field_name + "`=NULL , ";
895                 need_to_post = true;
896             } else {
897                 if($this_field.is(":not(.relation, .enum, .set, .bit)")) {
898                     this_field_params[field_name] = $this_field.find('textarea').val();
899                     if($this_field.is('.transformed')) {
900                         $.extend(transform_fields, this_field_params);
901                     }
902                 } else if ($this_field.is('.bit')) {
903                     this_field_params[field_name] = '0b' + $this_field.find('textarea').val();
904                     addQuotes = false;
905                 } else if ($this_field.is('.set')) {
906                     $test_element = $this_field.find('select');
907                     this_field_params[field_name] = $test_element.map(function(){
908                         return $(this).val();
909                     }).get().join(",");
910                 } else {
911                     // results from a drop-down
912                     $test_element = $this_field.find('select');
913                     if ($test_element.length != 0) {
914                         this_field_params[field_name] = $test_element.val();
915                     }
917                     // results from Browse foreign value
918                     $test_element = $this_field.find('span.curr_value');
919                     if ($test_element.length != 0) {
920                         this_field_params[field_name] = $test_element.text();
921                     }
923                     if($this_field.is('.relation')) {
924                         $.extend(relation_fields, this_field_params);
925                     }
926                 }
927                     if(where_clause.indexOf(field_name) > prev_index){
928                         new_clause += '`' + window.parent.table + '`.' + '`' + field_name + "` = '" + this_field_params[field_name].replace(/'/g,"''") + "'" + ' AND ';
929                     }
930                 if (this_field_params[field_name] != $this_field.data('original_data')) {
931                     if (addQuotes == true) {
932                         sql_query += ' `' + field_name + "`='" + this_field_params[field_name].replace(/'/g, "''") + "', ";
933                     } else {
934                         sql_query += ' `' + field_name + "`=" + this_field_params[field_name].replace(/'/g, "''") + ", ";
935                     }
936                     need_to_post = true;
937                 }
938             }
939         })
941         /*  
942          * update the where_clause, remove the last appended ' AND '
943          * */
945         //Remove the last ',' appended in the above loop
946         sql_query = sql_query.replace(/,\s$/, '');
947         //Fix non-escaped backslashes
948         sql_query = sql_query.replace(/\\/g, '\\\\');
949         new_clause = new_clause.substring(0, new_clause.length-5);
950         new_clause = PMA_urlencode(new_clause);
951         sql_query += ' WHERE ' + PMA_urldecode(where_clause);
952         // Avoid updating more than one row in case there is no primary key
953         // (happened only for duplicate rows)
954         sql_query += ' LIMIT 1';
955         /**
956          * @var rel_fields_list  String, url encoded representation of {@link relations_fields}
957          */
958         var rel_fields_list = $.param(relation_fields);
960         /**
961          * @var transform_fields_list  String, url encoded representation of {@link transform_fields}
962          */
963         var transform_fields_list = $.param(transform_fields);
965         // if inline_edit is successful, we need to go back to default view
966         var $del_hide = $(this).parent();
967         var $chg_submit = $(this);
969         if (need_to_post) {
970             // Make the Ajax post after setting all parameters
971             /**
972              * @var post_params Object containing parameters for the POST request
973              */
974             var post_params = {'ajax_request' : true,
975                             'sql_query' : sql_query,
976                             'disp_direction' : disp_mode,
977                             'token' : window.parent.token,
978                             'db' : window.parent.db,
979                             'table' : window.parent.table,
980                             'clause_is_unique' : nonunique,
981                             'where_clause' : where_clause,
982                             'rel_fields_list' : rel_fields_list,
983                             'do_transformations' : transformation_fields,
984                             'transform_fields_list' : transform_fields_list,
985                             'relational_display' : relational_display,
986                             'goto' : 'sql.php',
987                             'submit_type' : 'save'
988                           };
990             $.post('tbl_replace.php', post_params, function(data) {
991                 if(data.success == true) {
992                     PMA_ajaxShowMessage(data.message);
993                     if(disp_mode == 'vertical') {
994                         $this_td.parents('tbody').find('tr').find('.where_clause:nth(' + this_td_index + ')').attr('value', new_clause);
995                     }
996                     else {
997                         $this_td.parent('tr').find('.where_clause').attr('value', new_clause);
998                     }
999                     // remove possible previous feedback message
1000                     $('#result_query').remove();
1001                     if (typeof data.sql_query != 'undefined') {
1002                         // display feedback
1003                         $('#sqlqueryresults').prepend(data.sql_query);
1004                     }
1005                     PMA_unInlineEditRow($del_hide, $chg_submit, $this_td, $input_siblings, data, disp_mode);
1006                 } else {
1007                     PMA_ajaxShowMessage(data.error);
1008                 };
1009             }) // end $.post()
1010         } else {
1011             // no posting was done but still need to display the row
1012             // in its previous format
1013             PMA_unInlineEditRow($del_hide, $chg_submit, $this_td, $input_siblings, '', disp_mode);
1014         }
1015     }) // End After editing, clicking again should post data
1016 }, 'top.frame_content') // end $(document).ready()
1020  * Visually put back the row in the state it was before entering Inline edit 
1022  * (when called in the situation where no posting was done, the data
1023  * parameter is empty) 
1024  */
1025 function PMA_unInlineEditRow($del_hide, $chg_submit, $this_td, $input_siblings, data, disp_mode) {
1027     // deleting the hide button. remove <br><br><a> tags
1028     $del_hide.find('a, br').remove();
1029     // append inline edit button.
1030     $del_hide.append($data_a.clone());
1032     // changing inline_edit_active to inline_edit_anchor
1033     $this_td.removeClass('inline_edit_active').addClass('inline_edit_anchor');
1035     // removing hover, marked and noclick classes
1036     $this_td.parent('tr').removeClass('noclick');
1037     if(disp_mode != 'vertical') {
1038         $this_td.parent('tr').removeClass('hover').find('td').removeClass('hover');
1039     } else {
1040         $this_td.parents('tbody').find('tr').find('td:eq(' + $this_td.index() + ')').removeClass('marked hover');
1041     }
1043     $input_siblings.each(function() {
1044         // Inline edit post has been successful.
1045         $this_sibling = $(this);
1047         var is_null = $this_sibling.find('input:checkbox').is(':checked');
1048         if (is_null) {
1049             $this_sibling.html('NULL');
1050             $this_sibling.addClass('null');
1051         } else {
1052             $this_sibling.removeClass('null');
1053             if($this_sibling.is(':not(.relation, .enum, .set)')) {
1054                 /**
1055                  * @var new_html    String containing value of the data field after edit
1056                  */
1057                 var new_html = $this_sibling.find('textarea').val();
1059                 if($this_sibling.is('.transformed')) {
1060                     var field_name = getFieldName($this_sibling, disp_mode);
1061                     if (typeof data.transformations != 'undefined') {
1062                         $.each(data.transformations, function(key, value) {
1063                             if(key == field_name) {
1064                                 if($this_sibling.is('.text_plain, .application_octetstream')) {
1065                                     new_html = value;
1066                                     return false;
1067                                 } else {
1068                                     var new_value = $this_sibling.find('textarea').val();
1069                                     new_html = $(value).append(new_value);
1070                                     return false;
1071                                 }
1072                             }
1073                         })
1074                     }
1075                 }
1076             } else {
1077                 var new_html = '';
1078                 var new_value = '';
1079                 $test_element = $this_sibling.find('select');
1080                 if ($test_element.length != 0) {
1081                     new_value = $test_element.val();
1082                 }
1083                 $test_element = $this_sibling.find('span.curr_value');
1084                 if ($test_element.length != 0) {
1085                     new_value = $test_element.text();
1086                 }
1088                 if($this_sibling.is('.relation')) {
1089                     var field_name = getFieldName($this_sibling, disp_mode);
1090                     if (typeof data.relations != 'undefined') {
1091                         $.each(data.relations, function(key, value) {
1092                             if(key == field_name) {
1093                                 new_html = $(value);
1094                                 return false;
1095                             }
1096                         })
1097                     }
1098                 } else if ($this_sibling.is('.enum')) {
1099                     new_html = new_value;
1100                 } else if ($this_sibling.is('.set')) {
1101                     if (new_value != null) {
1102                         $.each(new_value, function(key, value) {
1103                             new_html = new_html + value + ',';
1104                         })
1105                         new_html = new_html.substring(0, new_html.length-1);
1106                     }
1107                 }
1108             }
1109             $this_sibling.html(new_html);
1110         }
1111     })
1115  * Starting from some th, change the class of all td under it
1116  */
1117 function PMA_changeClassForColumn($this_th, newclass) {
1118     // index 0 is the th containing the big T
1119     var th_index = $this_th.index();
1120     var has_big_t = !$this_th.closest('tr').children(':first').hasClass('column_heading');
1121     // .eq() is zero-based
1122     if (has_big_t) {
1123         th_index--;
1124     }
1125     var $tds = $this_th.closest('table').find('tbody tr').find('td.data:eq('+th_index+')');
1126     if ($this_th.data('has_class_'+newclass)) {
1127         $tds.removeClass(newclass);
1128         $this_th.data('has_class_'+newclass, false);
1129     } else {
1130         $tds.addClass(newclass);
1131         $this_th.data('has_class_'+newclass, true);
1132     }
1135 $(document).ready(function() {
1137     $('.browse_foreign').live('click', function(e) {
1138         e.preventDefault();
1139         window.open(this.href, 'foreigners', 'width=640,height=240,scrollbars=yes,resizable=yes');
1140         $anchor = $(this);
1141         $anchor.addClass('browse_foreign_clicked');
1142         return false;
1143     });
1145     /**
1146      * vertical column highlighting in horizontal mode when hovering over the column header
1147      */
1148     $('.column_heading').live('hover', function() {
1149         PMA_changeClassForColumn($(this), 'hover');
1150         });
1152     /**
1153      * vertical column marking in horizontal mode when clicking the column header
1154      */
1155     $('.column_heading').live('click', function() {
1156         PMA_changeClassForColumn($(this), 'marked');
1157         });
1160 /**#@- */