bug #3125624 Inline editing fails when just one row (fixed for LTR languages in horiz...
[phpmyadmin/last10db.git] / js / sql.js
blob97cc42f879a55afae109bce3524aef234f631387
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         if(window.parent.text_dir == 'ltr') {
39             // 4 columns to account for the checkbox, edit, delete and appended inline edit anchors but index is zero-based so substract 3
40             var field_name = $('#table_results').find('thead').find('th:nth('+ (this_field_index-3 )+') a').text();
41             // happens when just one row (headings contain no a)
42             if ("" == field_name) {
43                 field_name = $('#table_results').find('thead').find('th:nth('+ (this_field_index-3 )+')').text();
44             }
45         }
46         else {
47             var field_name = $('#table_results').find('thead').find('th:nth('+ this_field_index+') a').text();
48         }
49     }
51     field_name = $.trim(field_name);
53     return field_name;
56 /**
57  * The function that iterates over each row in the table_results and appends a
58  * new inline edit anchor to each table row.
59  *
60  * @param   disp_mode   string
61  */
62 function appendInlineAnchor(disp_mode) {
63     if(disp_mode == 'vertical') {
64         // there can be one or two tr containing this class, depending
65         // on the ModifyDeleteAtLeft and ModifyDeleteAtRight cfg parameters 
66         $('#table_results tr')
67             .find('.edit_row_anchor')
68             .removeClass('.edit_row_anchor')
69             .parent().each(function() {
70             var $this_tr = $(this);
71             var $cloned_tr = $this_tr.clone();
73             var $img_object = $cloned_tr.find('img:first').attr('title', PMA_messages['strInlineEdit']);
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         $("#table_results").find('tr').find(':checkbox').closest('tr').find('th')
86          .attr('rowspan', '4');
87     }
88     else {
89         $('.edit_row_anchor').each(function() {
91             $this_td = $(this)
92             $this_td.removeClass('edit_row_anchor');
94             var $cloned_anchor = $this_td.clone();
96             var $img_object = $cloned_anchor.find('img').attr('title', PMA_messages['strInlineEdit']);
98             $cloned_anchor.addClass('inline_edit_anchor')
99             .find('a').attr('href', '#')
100             .find('span')
101             .text(PMA_messages['strInlineEdit'])
102             .prepend($img_object);
104             $this_td.after($cloned_anchor);
105         });
107         $('#rowsDeleteForm').find('thead').find('th').each(function() {
108             if($(this).attr('colspan') == 3) {
109                 $(this).attr('colspan', '4')
110             }
111         })
112     }
115 /**#@+
116  * @namespace   jQuery
117  */
120  * @description <p>Ajax scripts for sql and browse pages</p>
122  * Actions ajaxified here:
123  * <ul>
124  * <li>Retrieve results of an SQL query</li>
125  * <li>Paginate the results table</li>
126  * <li>Sort the results table</li>
127  * <li>Change table according to display options</li>
128  * <li>Inline editing of data</li>
129  * </ul>
131  * @name        document.ready
132  * @memberOf    jQuery
133  */
134 $(document).ready(function() {
136     /**
137      * Set a parameter for all Ajax queries made on this page.  Don't let the
138      * web server serve cached pages
139      */
140     $.ajaxSetup({
141         cache: 'false'
142     });
144     /**
145      * current value of the direction in which the table is displayed
146      * @type    String
147      * @fieldOf jQuery
148      * @name    disp_mode
149      */
150     var disp_mode = $("#top_direction_dropdown").val();
152     /**
153      * Update value of {@link jQuery.disp_mode} everytime the direction dropdown changes value
154      * @memberOf    jQuery
155      * @name        direction_dropdown_change
156      */
157     $("#top_direction_dropdown, #bottom_direction_dropdown").live('change', function(event) {
158         disp_mode = $(this).val();
159     })
161     /**
162      * Attach the {@link appendInlineAnchor} function to a custom event, which
163      * will be triggered manually everytime the table of results is reloaded
164      * @memberOf    jQuery
165      * @name        sqlqueryresults_live
166      */
167     $("#sqlqueryresults").live('appendAnchor',function() {
168         appendInlineAnchor(disp_mode);
169     })
171     /**
172      * Trigger the appendAnchor event to prepare the first table for inline edit
173      *
174      * @memberOf    jQuery
175      * @name        sqlqueryresults_trigger
176      */
177     $("#sqlqueryresults").trigger('appendAnchor');
179     /**
180      * Append the "Show/Hide query box" message to the query input form
181      *
182      * @memberOf jQuery
183      * @name    appendToggleSpan
184      */
185     // do not add this link more than once
186     if (! $('#sqlqueryform').find('a').is('#togglequerybox')) {
187         $('<a id="togglequerybox"></a>')
188         .html(PMA_messages['strHideQueryBox'])
189         .appendTo("#sqlqueryform");
191         // Attach the toggling of the query box visibility to a click
192         $("#togglequerybox").bind('click', function() {
193             var $link = $(this)
194             $link.siblings().slideToggle("medium");
195             if ($link.text() == PMA_messages['strHideQueryBox']) {
196                 $link.text(PMA_messages['strShowQueryBox']);
197             } else {
198                 $link.text(PMA_messages['strHideQueryBox']);
199             }
200             // avoid default click action
201             return false;
202         })
203     }
204     
205     /**
206      * Ajax Event handler for 'SQL Query Submit'
207      *
208      * @see         PMA_ajaxShowMessage()
209      * @memberOf    jQuery
210      * @name        sqlqueryform_submit
211      */
212     $("#sqlqueryform").live('submit', function(event) {
213         event.preventDefault();
214         // remove any div containing a previous error message
215         $('.error').remove();
217         $form = $(this);
218         PMA_ajaxShowMessage();
220             if (! $form.find('input:hidden').is('#ajax_request_hidden')) {
221                 $form.append('<input type="hidden" id="ajax_request_hidden" name="ajax_request" value="true" />');
222             }
224         $.post($(this).attr('action'), $(this).serialize() , function(data) {
225             if(data.success == true) {
226                 PMA_ajaxShowMessage(data.message);
227                 $('#sqlqueryresults').show();
228                 // this happens if a USE command was typed
229                 if (typeof data.reload != 'undefined') {
230                     $form.find('input[name=db]').val(data.db);
231                     // need to regenerate the whole upper part
232                     $form.find('input[name=ajax_request]').remove();
233                     $form.append('<input type="hidden" name="reload" value="true" />');
234                     $.post('db_sql.php', $form.serialize(), function(data) {
235                         $('body').html(data);
236                     }); // end inner post
237                 }
238             }
239             else if (data.success == false ) {
240                 // show an error message that stays on screen 
241                 $('#sqlqueryform').before(data.error);
242                 $('#sqlqueryresults').hide();
243             }
244             else {
245                 $('#sqlqueryresults').show();
246                 $("#sqlqueryresults").html(data);
247                 $("#sqlqueryresults").trigger('appendAnchor');
248                 if($("#togglequerybox").siblings(":visible").length > 0) {
249                     $("#togglequerybox").trigger('click');
250                 }
251             }
252         }) // end $.post()
253     }) // end SQL Query submit
255     /**
256      * Ajax Event handlers for Paginating the results table
257      */
259     /**
260      * Paginate when we click any of the navigation buttons
261      * @memberOf    jQuery
262      * @name        paginate_nav_button_click
263      * @uses        PMA_ajaxShowMessage()
264      */
265     $("input[name=navig]").live('click', function(event) {
266         /** @lends jQuery */
267         event.preventDefault();
269         PMA_ajaxShowMessage();
270         
271         /**
272          * @var the_form    Object referring to the form element that paginates the results table
273          */
274         var the_form = $(this).parent("form");
276         $(the_form).append('<input type="hidden" name="ajax_request" value="true" />');
278         $.post($(the_form).attr('action'), $(the_form).serialize(), function(data) {
279             $("#sqlqueryresults").html(data);
280             $("#sqlqueryresults").trigger('appendAnchor');
281         }) // end $.post()
282     })// end Paginate results table
284     /**
285      * Paginate results with Page Selector dropdown
286      * @memberOf    jQuery
287      * @name        paginate_dropdown_change
288      */
289     $("#pageselector").live('change', function(event) {
290         event.preventDefault();
292         PMA_ajaxShowMessage();
294         $.get($(this).attr('href'), $(this).serialize() + '&ajax_request=true', function(data) {
295             $("#sqlqueryresults").html(data);
296             $("#sqlqueryresults").trigger('appendAnchor');
297         }) // end $.get()
298     })// end Paginate results with Page Selector
300     /**
301      * Ajax Event handler for sorting the results table
302      * @memberOf    jQuery
303      * @name        table_results_sort_click
304      */
305     $("#table_results").find("a[title=Sort]").live('click', function(event) {
306         event.preventDefault();
308         PMA_ajaxShowMessage();
310         $.get($(this).attr('href'), $(this).serialize() + '&ajax_request=true', function(data) {
311             $("#sqlqueryresults").html(data);
312             $("#sqlqueryresults").trigger('appendAnchor');
313         }) // end $.get()
314     })//end Sort results table
316     /**
317      * Ajax Event handler for the display options
318      * @memberOf    jQuery
319      * @name        displayOptionsForm_submit
320      */
321     $("#displayOptionsForm").live('submit', function(event) {
322         event.preventDefault();
324         $.post($(this).attr('action'), $(this).serialize() + '&ajax_request=true' , function(data) {
325             $("#sqlqueryresults").html(data);
326             $("#sqlqueryresults").trigger('appendAnchor');
327         }) // end $.post()
328     })
329     //end displayOptionsForm handler
331     /**
332      * Ajax Event handlers for Inline Editing
333      */
335     /**
336      * On click, replace the fields of current row with an input/textarea
337      * @memberOf    jQuery
338      * @name        inline_edit_start
339      * @see         PMA_ajaxShowMessage()
340      * @see         getFieldName()
341      */
342     $(".inline_edit_anchor").live('click', function(event) {
343         /** @lends jQuery */
344         event.preventDefault();
346         $(this).removeClass('inline_edit_anchor').addClass('inline_edit_active');
348         // Initialize some variables
349         if(disp_mode == 'vertical') {
350             /**
351              * @var this_row_index  Index of the current <td> in the parent <tr>
352              *                      Current <td> is the inline edit anchor.
353              */
354             var this_row_index = $(this).index();
355             /**
356              * @var $input_siblings  Object referring to all inline editable events from same row
357              */
358             var $input_siblings = $(this).parents('tbody').find('tr').find('.data_inline_edit:nth('+this_row_index+')');
359             /**
360              * @var where_clause    String containing the WHERE clause to select this row
361              */
362             var where_clause = $(this).parents('tbody').find('tr').find('.where_clause:nth('+this_row_index+')').val();
363         }
364         else {
365             var $input_siblings = $(this).parent('tr').find('.data_inline_edit');
366             var where_clause = $(this).parent('tr').find('.where_clause').val();
367         }
369         $input_siblings.each(function() {
370             /** @lends jQuery */
371             /**
372              * @var data_value  Current value of this field
373              */
374             var data_value = $(this).html();
376             // We need to retrieve the value from the server for truncated/relation fields
377             // Find the field name
378             
379             /**
380              * @var this_field  Object referring to this field (<td>)
381              */
382             var $this_field = $(this);
383             /**
384              * @var field_name  String containing the name of this field.
385              * @see getFieldName()
386              */
387             var field_name = getFieldName($this_field, disp_mode);
389             // In each input sibling, wrap the current value in a textarea
390             // and store the current value in a hidden span
391             if($this_field.is(':not(.truncated, .transformed, .relation, .enum, .null)')) {
392                 // handle non-truncated, non-transformed, non-relation values
393                 // We don't need to get any more data, just wrap the value
394                 $this_field.html('<textarea>'+data_value+'</textarea>')
395                 .append('<span class="original_data">'+data_value+'</span>');
396                 $(".original_data").hide();
397             }
398             else if($this_field.is('.truncated, .transformed')) {
399                 /** @lends jQuery */
400                 //handle truncated/transformed values values
402                 /**
403                  * @var sql_query   String containing the SQL query used to retrieve value of truncated/transformed data
404                  */
405                 var sql_query = 'SELECT ' + field_name + ' FROM ' + window.parent.table + ' WHERE ' + where_clause;
407                 // Make the Ajax call and get the data, wrap it and insert it
408                 $.post('sql.php', {
409                     'token' : window.parent.token,
410                     'db' : window.parent.db,
411                     'ajax_request' : true,
412                     'sql_query' : sql_query,
413                     'inline_edit' : true
414                 }, function(data) {
415                     if(data.success == true) {
416                         $this_field.html('<textarea>'+data.value+'</textarea>')
417                         .append('<span class="original_data">'+data_value+'</span>');
418                         $(".original_data").hide();
419                     }
420                     else {
421                         PMA_ajaxShowMessage(data.error);
422                     }
423                 }) // end $.post()
424             }
425             else if($this_field.is('.relation')) {
426                 /** @lends jQuery */
427                 //handle relations
429                 /**
430                  * @var curr_value  String containing the current value of this relational field
431                  */
432                 var curr_value = $this_field.find('a').text();
434                 /**
435                  * @var post_params Object containing parameters for the POST request
436                  */
437                 var post_params = {
438                         'ajax_request' : true,
439                         'get_relational_values' : true,
440                         'db' : window.parent.db,
441                         'table' : window.parent.table,
442                         'column' : field_name,
443                         'token' : window.parent.token,
444                         'curr_value' : curr_value
445                 }
447                 $.post('sql.php', post_params, function(data) {
448                     $this_field.html(data.dropdown)
449                     .append('<span class="original_data">'+data_value+'</span>');
450                     $(".original_data").hide();
451                 }) // end $.post()
452             }
453             else if($this_field.is('.enum')) {
454                 /** @lends jQuery */
455                 //handle enum fields
456                 /**
457                  * @var curr_value  String containing the current value of this relational field
458                  */
459                 var curr_value = $this_field.text();
461                 /**
462                  * @var post_params Object containing parameters for the POST request
463                  */
464                 var post_params = {
465                         'ajax_request' : true,
466                         'get_enum_values' : true,
467                         'db' : window.parent.db,
468                         'table' : window.parent.table,
469                         'column' : field_name,
470                         'token' : window.parent.token,
471                         'curr_value' : curr_value
472                 }
474                 $.post('sql.php', post_params, function(data) {
475                     $this_field.html(data.dropdown)
476                     .append('<span class="original_data">'+data_value+'</span>');
477                     $(".original_data").hide();
478                 }) // end $.post()
479             }
480             else if($this_field.is('.null')) {
481                 //handle null fields
482                 $this_field.html('<textarea></textarea>')
483                 .append('<span class="original_data">NULL</span>');
484                 $(".original_data").hide();
485             }
486         })
487     }) // End On click, replace the current field with an input/textarea
489     /**
490      * After editing, clicking again should post data
491      *
492      * @memberOf    jQuery
493      * @name        inline_edit_save
494      * @see         PMA_ajaxShowMessage()
495      * @see         getFieldName()
496      */
497     $(".inline_edit_active").live('click', function(event) {
498         /** @lends jQuery */
499         event.preventDefault();
501         /**
502          * @var $this_td    Object referring to the td containing the 
503          * "Inline Edit" link that was clicked to save the row that is 
504          * being edited
505          *
506          */
507         var $this_td = $(this);
509         // Initialize variables
510         if(disp_mode == 'vertical') {
511             /**
512              * @var this_td_index  Index of the current <td> in the parent <tr>
513              *                      Current <td> is the inline edit anchor.
514              */
515             var this_td_index = $this_td.index();
516             /**
517              * @var $input_siblings  Object referring to all inline editable events from same row
518              */
519             var $input_siblings = $this_td.parents('tbody').find('tr').find('.data_inline_edit:nth('+this_td_index+')');
520             /**
521              * @var where_clause    String containing the WHERE clause to select this row
522              */
523             var where_clause = $this_td.parents('tbody').find('tr').find('.where_clause:nth('+this_td_index+')').val();
524         }
525         else {
526             var $input_siblings = $this_td.parent('tr').find('.data_inline_edit');
527             var where_clause = $this_td.parent('tr').find('.where_clause').val();
528         }
530         /**
531          * @var nonunique   Boolean, whether this row is unique or not
532          */
533         if($this_td.is('.nonunique')) {
534             var nonunique = 0;
535         }
536         else {
537             var nonunique = 1;
538         }
540         // Collect values of all fields to submit, we don't know which changed
541         /**
542          * @var params_to_submit    Array containing the name/value pairs of all fields
543          */
544         var params_to_submit = {};
545         /**
546          * @var relation_fields Array containing the name/value pairs of relational fields
547          */
548         var relation_fields = {};
549         /**
550          * @var transform_fields    Array containing the name/value pairs for transformed fields
551          */
552         var transform_fields = {};
553         /**
554          * @var transformation_fields   Boolean, if there are any transformed fields in this row
555          */
556         var transformation_fields = false;
558         $input_siblings.each(function() {
559             /** @lends jQuery */
560             /**
561              * @var this_field  Object referring to this field (<td>)
562              */
563             var $this_field = $(this);
564             /**
565              * @var field_name  String containing the name of this field.
566              * @see getFieldName()
567              */
568             var field_name = getFieldName($this_field, disp_mode);
570             /**
571              * @var this_field_params   Array temporary storage for the name/value of current field
572              */
573             var this_field_params = {};
575             if($this_field.is('.transformed')) {
576                 transformation_fields =  true;
577             }
579             if($this_field.is(":not(.relation, .enum)")) {
580                 this_field_params[field_name] = $this_field.find('textarea').val();
581                 if($this_field.is('.transformed')) {
582                     $.extend(transform_fields, this_field_params);
583                 }
584             }
585             else {
586                 this_field_params[field_name] = $this_field.find('select').val();
588                 if($this_field.is('.relation')) {
589                     $.extend(relation_fields, this_field_params);
590                 }
591             }
593             $.extend(params_to_submit, this_field_params);
594         })
596         /**
597          * @var sql_query   String containing the SQL query to update this row
598          */
599         var sql_query = 'UPDATE ' + window.parent.table + ' SET ';
601         $.each(params_to_submit, function(key, value) {
602             if(value.length == 0) {
603                 value = 'NULL'
604             }
605            sql_query += ' ' + key + "='" + value + "' , ";
606         })
607         //Remove the last ',' appended in the above loop
608         sql_query = sql_query.replace(/,\s$/, '');
609         sql_query += ' WHERE ' + PMA_urldecode(where_clause);
611         /**
612          * @var rel_fields_list  String, url encoded representation of {@link relations_fields}
613          */
614         var rel_fields_list = $.param(relation_fields);
616         /**
617          * @var transform_fields_list  String, url encoded representation of {@link transform_fields}
618          */
619         var transform_fields_list = $.param(transform_fields);
621         // Make the Ajax post after setting all parameters
622         /**
623          * @var post_params Object containing parameters for the POST request
624          */
625         var post_params = {'ajax_request' : true,
626                             'sql_query' : sql_query,
627                             'disp_direction' : disp_mode,
628                             'token' : window.parent.token,
629                             'db' : window.parent.db,
630                             'table' : window.parent.table,
631                             'clause_is_unique' : nonunique,
632                             'where_clause' : where_clause,
633                             'rel_fields_list' : rel_fields_list,
634                             'do_transformations' : transformation_fields,
635                             'transform_fields_list' : transform_fields_list,
636                             'goto' : 'sql.php',
637                             'submit_type' : 'save'
638                           };
640         $.post('tbl_replace.php', post_params, function(data) {
641             if(data.success == true) {
642                 PMA_ajaxShowMessage(data.message);
643                 $this_td.removeClass('inline_edit_active').addClass('inline_edit_anchor');
645                 $input_siblings.each(function() {
646                     // Inline edit post has been successful.
647                     if($(this).is(':not(.relation, .enum)')) {
648                         /**
649                          * @var new_html    String containing value of the data field after edit
650                          */
651                         var new_html = $(this).find('textarea').val();
653                         if($(this).is('.transformed')) {
654                             var field_name = getFieldName($(this), disp_mode);
655                             var this_field = $(this);
657                             $.each(data.transformations, function(key, value) {
658                                 if(key == field_name) {
659                                     if($(this_field).is('.text_plain, .application_octetstream')) {
660                                         new_html = value;
661                                         return false;
662                                     }
663                                     else {
664                                         var new_value = $(this_field).find('textarea').val();
665                                         new_html = $(value).append(new_value);
666                                         return false;
667                                     }
668                                 }
669                             })
670                         }
671                     }
672                     else {
673                         var new_html = $(this).find('select').val();
674                         if($(this).is('.relation')) {
675                             var field_name = getFieldName($(this), disp_mode);
676                             var this_field = $(this);
678                             $.each(data.relations, function(key, value) {
679                                 if(key == field_name) {
680                                     var new_value = $(this_field).find('select').val();
681                                     new_html = $(value).append(new_value);
682                                     return false;
683                                 }
684                             })
685                         }
686                     }
687                     $(this).html(new_html);
688                 })
689             }
690             else {
691                 PMA_ajaxShowMessage(data.error);
692             };
693         }) // end $.post()
694     }) // End After editing, clicking again should post data
695 }, 'top.frame_content') // end $(document).ready()
698  * Starting from some th, change the class of all td under it
699  */
700 function PMA_changeClassForColumn($this_th, klass) {
701     // index 0 is the th containing the big T
702     var th_index = $this_th.index();
703     // .eq() is zero-based
704     th_index--;
705     var $tr_with_data = $this_th.closest('table').find('tbody tr ').has('td.data_inline_edit');
706     $tr_with_data.each(function() {
707         $(this).find('td.data_inline_edit:eq('+th_index+')').toggleClass(klass);
708     });
711 $(document).ready(function() {
712     /**
713      * column highlighting in horizontal mode when hovering over the column header
714      */
715     $('.column_heading').live('hover', function() {
716         PMA_changeClassForColumn($(this), 'hover'); 
717         });
719     /**
720      * column marking in horizontal mode when clicking the column header
721      */
722     $('.column_heading').live('click', function() {
723         PMA_changeClassForColumn($(this), 'marked'); 
724         });
727 /**#@- */