other fixes for inline edit links in vertical mode
[phpmyadmin/gandalfml.git] / js / sql.js
blob457396f77f37b9eb371e784d901c1be129537595
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_obj  jQuery object that points to the current field's tr
25  * @param   disp_mode       string
26  */
27 function getFieldName(this_field_obj, disp_mode) {
29     if(disp_mode == 'vertical') {
30         var field_name = $(this_field_obj).siblings('th').find('a').text();
31     }
32     else {
33         var this_field_index = $(this_field_obj).index();
34         if(window.parent.text_dir == 'ltr') {
35             // 4 columns to account for the checkbox, edit, delete and appended inline edit anchors but index is zero-based so substract 3
36             var field_name = $(this_field_obj).parents('table').find('thead').find('th:nth('+ (this_field_index-3 )+') a').text();
37         }
38         else {
39             var field_name = $(this_field_obj).parents('table').find('thead').find('th:nth('+ this_field_index+') a').text();
40         }
41     }
43     field_name = $.trim(field_name);
45     return field_name;
48 /**
49  * The function that iterates over each row in the table_results and appends a
50  * new inline edit anchor to each table row.
51  *
52  * @param   disp_mode   string
53  */
54 function appendInlineAnchor(disp_mode) {
55     if(disp_mode == 'vertical') {
56         // there can be one or two tr containing this class, depending
57         // on the ModifyDeleteAtLeft and ModifyDeleteAtRight cfg parameters 
58         $('#table_results tr').find('.edit_row_anchor').parent().each(function() {
59             var $this_tr = $(this);
60             var $cloned_tr = $this_tr.clone();
62             var $img_object = $cloned_tr.find('img:first').attr('title', PMA_messages['strInlineEdit']);
64             $cloned_tr.find('td')
65              .find('a').attr('href', '#')
66              .find('span')
67              .text(PMA_messages['strInlineEdit'])
68              .prepend($img_object);
70             $cloned_tr.insertAfter($this_tr);
71         });
73         $("#table_results").find('tr').find(':checkbox').closest('tr').find('th')
74          .attr('rowspan', '4');
75     }
76     else {
77         $('.edit_row_anchor').each(function() {
79             $this_td = $(this)
80             $this_td.removeClass('edit_row_anchor');
82             var $cloned_anchor = $this_td.clone();
84             var $img_object = $cloned_anchor.find('img').attr('title', PMA_messages['strInlineEdit']);
86             $cloned_anchor.addClass('edit_row_anchor')
87             .find('a').attr('href', '#')
88             .find('span')
89             .text(PMA_messages['strInlineEdit'])
90             .prepend($img_object);
92             $this_td.after($cloned_anchor);
93         });
95         $('#rowsDeleteForm').find('thead').find('th').each(function() {
96             if($(this).attr('colspan') == 3) {
97                 $(this).attr('colspan', '4')
98             }
99         })
100     }
103 /**#@+
104  * @namespace   jQuery
105  */
108  * @description <p>Ajax scripts for sql and browse pages</p>
110  * Actions ajaxified here:
111  * <ul>
112  * <li>Retrieve results of an SQL query</li>
113  * <li>Paginate the results table</li>
114  * <li>Sort the results table</li>
115  * <li>Change table according to display options</li>
116  * <li>Inline editing of data</li>
117  * </ul>
119  * @name        document.ready
120  * @memberOf    jQuery
121  */
122 $(document).ready(function() {
124     /**
125      * Set a parameter for all Ajax queries made on this page.  Don't let the
126      * web server serve cached pages
127      */
128     $.ajaxSetup({
129         cache: 'false'
130     });
132     /**
133      * current value of the direction in which the table is displayed
134      * @type    String
135      * @fieldOf jQuery
136      * @name    disp_mode
137      */
138     var disp_mode = $("#top_direction_dropdown").val();
140     /**
141      * Update value of {@link jQuery.disp_mode} everytime the direction dropdown changes value
142      * @memberOf    jQuery
143      * @name        direction_dropdown_change
144      */
145     $("#top_direction_dropdown, #bottom_direction_dropdown").live('change', function(event) {
146         disp_mode = $(this).val();
147     })
149     /**
150      * Attach the {@link appendInlineAnchor} function to a custom event, which
151      * will be triggered manually everytime the table of results is reloaded
152      * @memberOf    jQuery
153      * @name        sqlqueryresults_live
154      */
155     $("#sqlqueryresults").live('appendAnchor',function() {
156         appendInlineAnchor(disp_mode);
157     })
159     /**
160      * Trigger the appendAnchor event to prepare the first table for inline edit
161      *
162      * @memberOf    jQuery
163      * @name        sqlqueryresults_trigger
164      */
165     $("#sqlqueryresults").trigger('appendAnchor');
167     /**
168      * Append the "Show/Hide query box" message to the query input form
169      *
170      * @memberOf jQuery
171      * @name    appendToggleSpan
172      */
173     // do not add this link more than once
174     if (! $('#sqlqueryform').find('a').is('#togglequerybox')) {
175         $('<a id="togglequerybox"></a>')
176         .html(PMA_messages['strHideQueryBox'])
177         .appendTo("#sqlqueryform");
179         // Attach the toggling of the query box visibility to a click
180         $("#togglequerybox").bind('click', function() {
181             var $link = $(this)
182             $link.siblings().slideToggle("medium");
183             if ($link.text() == PMA_messages['strHideQueryBox']) {
184                 $link.text(PMA_messages['strShowQueryBox']);
185             } else {
186                 $link.text(PMA_messages['strHideQueryBox']);
187             }
188             // avoid default click action
189             return false;
190         })
191     }
192     
193     /**
194      * Ajax Event handler for 'SQL Query Submit'
195      *
196      * @see         PMA_ajaxShowMessage()
197      * @memberOf    jQuery
198      * @name        sqlqueryform_submit
199      */
200     $("#sqlqueryform").live('submit', function(event) {
201         event.preventDefault();
202         // remove any div containing a previous error message
203         $('.error').remove();
205         $form = $(this);
206         PMA_ajaxShowMessage();
208             if (! $form.find('input:hidden').is('#ajax_request_hidden')) {
209                 $form.append('<input type="hidden" id="ajax_request_hidden" name="ajax_request" value="true" />');
210             }
212         $.post($(this).attr('action'), $(this).serialize() , function(data) {
213             if(data.success == true) {
214                 PMA_ajaxShowMessage(data.message);
215                 $('#sqlqueryresults').show();
216                 // this happens if a USE command was typed
217                 if (typeof data.reload != 'undefined') {
218                     $form.find('input[name=db]').val(data.db);
219                     // need to regenerate the whole upper part
220                     $form.find('input[name=ajax_request]').remove();
221                     $form.append('<input type="hidden" name="reload" value="true" />');
222                     $.post('db_sql.php', $form.serialize(), function(data) {
223                         $('body').html(data);
224                     }); // end inner post
225                 }
226             }
227             else if (data.success == false ) {
228                 // show an error message that stays on screen 
229                 $('#sqlqueryform').before(data.error);
230                 $('#sqlqueryresults').hide();
231             }
232             else {
233                 $('#sqlqueryresults').show();
234                 $("#sqlqueryresults").html(data);
235                 $("#sqlqueryresults").trigger('appendAnchor');
236                 if($("#togglequerybox").siblings(":visible").length > 0) {
237                     $("#togglequerybox").trigger('click');
238                 }
239             }
240         }) // end $.post()
241     }) // end SQL Query submit
243     /**
244      * Ajax Event handlers for Paginating the results table
245      */
247     /**
248      * Paginate when we click any of the navigation buttons
249      * @memberOf    jQuery
250      * @name        paginate_nav_button_click
251      * @uses        PMA_ajaxShowMessage()
252      */
253     $("input[name=navig]").live('click', function(event) {
254         /** @lends jQuery */
255         event.preventDefault();
257         PMA_ajaxShowMessage();
258         
259         /**
260          * @var the_form    Object referring to the form element that paginates the results table
261          */
262         var the_form = $(this).parent("form");
264         $(the_form).append('<input type="hidden" name="ajax_request" value="true" />');
266         $.post($(the_form).attr('action'), $(the_form).serialize(), function(data) {
267             $("#sqlqueryresults").html(data);
268             $("#sqlqueryresults").trigger('appendAnchor');
269         }) // end $.post()
270     })// end Paginate results table
272     /**
273      * Paginate results with Page Selector dropdown
274      * @memberOf    jQuery
275      * @name        paginate_dropdown_change
276      */
277     $("#pageselector").live('change', function(event) {
278         event.preventDefault();
280         PMA_ajaxShowMessage();
282         $.get($(this).attr('href'), $(this).serialize() + '&ajax_request=true', function(data) {
283             $("#sqlqueryresults").html(data);
284             $("#sqlqueryresults").trigger('appendAnchor');
285         }) // end $.get()
286     })// end Paginate results with Page Selector
288     /**
289      * Ajax Event handler for sorting the results table
290      * @memberOf    jQuery
291      * @name        table_results_sort_click
292      */
293     $("#table_results").find("a[title=Sort]").live('click', function(event) {
294         event.preventDefault();
296         PMA_ajaxShowMessage();
298         $.get($(this).attr('href'), $(this).serialize() + '&ajax_request=true', function(data) {
299             $("#sqlqueryresults").html(data);
300             $("#sqlqueryresults").trigger('appendAnchor');
301         }) // end $.get()
302     })//end Sort results table
304     /**
305      * Ajax Event handler for the display options
306      * @memberOf    jQuery
307      * @name        displayOptionsForm_submit
308      */
309     $("#displayOptionsForm").live('submit', function(event) {
310         event.preventDefault();
312         $.post($(this).attr('action'), $(this).serialize() + '&ajax_request=true' , function(data) {
313             $("#sqlqueryresults").html(data);
314             $("#sqlqueryresults").trigger('appendAnchor');
315         }) // end $.post()
316     })
317     //end displayOptionsForm handler
319     /**
320      * Ajax Event handlers for Inline Editing
321      */
323     /**
324      * On click, replace the fields of current row with an input/textarea
325      * @memberOf    jQuery
326      * @name        inline_edit_start
327      * @see         PMA_ajaxShowMessage()
328      * @see         getFieldName()
329      */
330     $(".edit_row_anchor").live('click', function(event) {
331         /** @lends jQuery */
332         event.preventDefault();
334         $(this).removeClass('edit_row_anchor').addClass('edit_row_anchor_active');
336         // Initialize some variables
337         if(disp_mode == 'vertical') {
338             /**
339              * @var this_row_index  Index of the current <td> in the parent <tr>
340              *                      Current <td> is the inline edit anchor.
341              */
342             var this_row_index = $(this).index();
343             /**
344              * @var input_siblings  Object referring to all inline editable events from same row
345              */
346             var input_siblings = $(this).parents('tbody').find('tr').find('.data_inline_edit:nth('+this_row_index+')');
347             /**
348              * @var where_clause    String containing the WHERE clause to select this row
349              */
350             var where_clause = $(this).parents('tbody').find('tr').find('.where_clause:nth('+this_row_index+')').val();
351         }
352         else {
353             var input_siblings = $(this).parent('tr').find('.data_inline_edit');
354             var where_clause = $(this).parent('tr').find('.where_clause').val();
355         }
357         $(input_siblings).each(function() {
358             /** @lends jQuery */
359             /**
360              * @var data_value  Current value of this field
361              */
362             var data_value = $(this).html();
364             // We need to retrieve the value from the server for truncated/relation fields
365             // Find the field name
366             
367             /**
368              * @var this_field  Object referring to this field (<td>)
369              */
370             var this_field = $(this);
371             /**
372              * @var field_name  String containing the name of this field.
373              * @see getFieldName()
374              */
375             var field_name = getFieldName($(this), disp_mode);
377             // In each input sibling, wrap the current value in a textarea
378             // and store the current value in a hidden span
379             if($(this).is(':not(.truncated, .transformed, .relation, .enum, .null)')) {
380                 // handle non-truncated, non-transformed, non-relation values
381                 // We don't need to get any more data, just wrap the value
382                 $(this).html('<textarea>'+data_value+'</textarea>')
383                 .append('<span class="original_data">'+data_value+'</span>');
384                 $(".original_data").hide();
385             }
386             else if($(this).is('.truncated, .transformed')) {
387                 /** @lends jQuery */
388                 //handle truncated/transformed values values
390                 /**
391                  * @var sql_query   String containing the SQL query used to retrieve value of truncated/transformed data
392                  */
393                 var sql_query = 'SELECT ' + field_name + ' FROM ' + window.parent.table + ' WHERE ' + where_clause;
395                 // Make the Ajax call and get the data, wrap it and insert it
396                 $.post('sql.php', {
397                     'token' : window.parent.token,
398                     'db' : window.parent.db,
399                     'ajax_request' : true,
400                     'sql_query' : sql_query,
401                     'inline_edit' : true
402                 }, function(data) {
403                     if(data.success == true) {
404                         $(this_field).html('<textarea>'+data.value+'</textarea>')
405                         .append('<span class="original_data">'+data_value+'</span>');
406                         $(".original_data").hide();
407                     }
408                     else {
409                         PMA_ajaxShowMessage(data.error);
410                     }
411                 }) // end $.post()
412             }
413             else if($(this).is('.relation')) {
414                 /** @lends jQuery */
415                 //handle relations
417                 /**
418                  * @var curr_value  String containing the current value of this relational field
419                  */
420                 var curr_value = $(this).find('a').text();
422                 /**
423                  * @var post_params Object containing parameters for the POST request
424                  */
425                 var post_params = {
426                         'ajax_request' : true,
427                         'get_relational_values' : true,
428                         'db' : window.parent.db,
429                         'table' : window.parent.table,
430                         'column' : field_name,
431                         'token' : window.parent.token,
432                         'curr_value' : curr_value
433                 }
435                 $.post('sql.php', post_params, function(data) {
436                     $(this_field).html(data.dropdown)
437                     .append('<span class="original_data">'+data_value+'</span>');
438                     $(".original_data").hide();
439                 }) // end $.post()
440             }
441             else if($(this).is('.enum')) {
442                 /** @lends jQuery */
443                 //handle enum fields
444                 /**
445                  * @var curr_value  String containing the current value of this relational field
446                  */
447                 var curr_value = $(this).text();
449                 /**
450                  * @var post_params Object containing parameters for the POST request
451                  */
452                 var post_params = {
453                         'ajax_request' : true,
454                         'get_enum_values' : true,
455                         'db' : window.parent.db,
456                         'table' : window.parent.table,
457                         'column' : field_name,
458                         'token' : window.parent.token,
459                         'curr_value' : curr_value
460                 }
462                 $.post('sql.php', post_params, function(data) {
463                     $(this_field).html(data.dropdown)
464                     .append('<span class="original_data">'+data_value+'</span>');
465                     $(".original_data").hide();
466                 }) // end $.post()
467             }
468             else if($(this).is('.null')) {
469                 //handle null fields
470                 $(this_field).html('<textarea></textarea>')
471                 .append('<span class="original_data">NULL</span>');
472                 $(".original_data").hide();
473             }
474         })
475     }) // End On click, replace the current field with an input/textarea
477     /**
478      * After editing, clicking again should post data
479      *
480      * @memberOf    jQuery
481      * @name        inline_edit_save
482      * @see         PMA_ajaxShowMessage()
483      * @see         getFieldName()
484      */
485     $(".edit_row_anchor_active").live('click', function(event) {
486         /** @lends jQuery */
487         event.preventDefault();
489         /**
490          * @var $this_td    Object referring to the td containing the 
491          * "Inline Edit" link that was clicked to save the row that is 
492          * being edited
493          *
494          */
495         var $this_td = $(this);
497         // Initialize variables
498         if(disp_mode == 'vertical') {
499             /**
500              * @var this_td_index  Index of the current <td> in the parent <tr>
501              *                      Current <td> is the inline edit anchor.
502              */
503             var this_td_index = $this_td.index();
504             /**
505              * @var input_siblings  Object referring to all inline editable events from same row
506              */
507             var input_siblings = $this_td.parents('tbody').find('tr').find('.data_inline_edit:nth('+this_td_index+')');
508             /**
509              * @var where_clause    String containing the WHERE clause to select this row
510              */
511             var where_clause = $this_td.parents('tbody').find('tr').find('.where_clause:nth('+this_td_index+')').val();
512         }
513         else {
514             var input_siblings = $this_td.parent('tr').find('.data_inline_edit');
515             var where_clause = $this_td.parent('tr').find('.where_clause').val();
516         }
518         /**
519          * @var nonunique   Boolean, whether this row is unique or not
520          */
521         if($this_td.is('.nonunique')) {
522             var nonunique = 0;
523         }
524         else {
525             var nonunique = 1;
526         }
528         // Collect values of all fields to submit, we don't know which changed
529         /**
530          * @var params_to_submit    Array containing the name/value pairs of all fields
531          */
532         var params_to_submit = {};
533         /**
534          * @var relation_fields Array containing the name/value pairs of relational fields
535          */
536         var relation_fields = {};
537         /**
538          * @var transform_fields    Array containing the name/value pairs for transformed fields
539          */
540         var transform_fields = {};
541         /**
542          * @var transformation_fields   Boolean, if there are any transformed fields in this row
543          */
544         var transformation_fields = false;
546         $(input_siblings).each(function() {
547             /** @lends jQuery */
548             /**
549              * @var this_field  Object referring to this field (<td>)
550              */
551             var $this_field = $(this);
552             /**
553              * @var field_name  String containing the name of this field.
554              * @see getFieldName()
555              */
556             var field_name = getFieldName($this_field, disp_mode);
558             /**
559              * @var this_field_params   Array temporary storage for the name/value of current field
560              */
561             var this_field_params = {};
563             if($this_field.is('.transformed')) {
564                 transformation_fields =  true;
565             }
567             if($this_field.is(":not(.relation, .enum)")) {
568                 this_field_params[field_name] = $this_field.find('textarea').val();
569                 if($this_field.is('.transformed')) {
570                     $.extend(transform_fields, this_field_params);
571                 }
572             }
573             else {
574                 this_field_params[field_name] = $this_field.find('select').val();
576                 if($this_field.is('.relation')) {
577                     $.extend(relation_fields, this_field_params);
578                 }
579             }
581             $.extend(params_to_submit, this_field_params);
582         })
584         /**
585          * @var sql_query   String containing the SQL query to update this row
586          */
587         var sql_query = 'UPDATE ' + window.parent.table + ' SET ';
589         $.each(params_to_submit, function(key, value) {
590             if(value.length == 0) {
591                 value = 'NULL'
592             }
593            sql_query += ' ' + key + "='" + value + "' , ";
594         })
595         //Remove the last ',' appended in the above loop
596         sql_query = sql_query.replace(/,\s$/, '');
597         sql_query += ' WHERE ' + PMA_urldecode(where_clause);
599         /**
600          * @var rel_fields_list  String, url encoded representation of {@link relations_fields}
601          */
602         var rel_fields_list = $.param(relation_fields);
604         /**
605          * @var transform_fields_list  String, url encoded representation of {@link transform_fields}
606          */
607         var transform_fields_list = $.param(transform_fields);
609         // Make the Ajax post after setting all parameters
610         /**
611          * @var post_params Object containing parameters for the POST request
612          */
613         var post_params = {'ajax_request' : true,
614                             'sql_query' : sql_query,
615                             'disp_direction' : disp_mode,
616                             'token' : window.parent.token,
617                             'db' : window.parent.db,
618                             'table' : window.parent.table,
619                             'clause_is_unique' : nonunique,
620                             'where_clause' : where_clause,
621                             'rel_fields_list' : rel_fields_list,
622                             'do_transformations' : transformation_fields,
623                             'transform_fields_list' : transform_fields_list,
624                             'goto' : 'sql.php'
625                           };
627         $.post('tbl_replace.php', post_params, function(data) {
628             if(data.success == true) {
629                 PMA_ajaxShowMessage(data.message);
630                 $this_td.removeClass('edit_row_anchor_active').addClass('edit_row_anchor');
632                 $(input_siblings).each(function() {
633                     // Inline edit post has been successful.
634                     if($(this).is(':not(.relation, .enum)')) {
635                         /**
636                          * @var new_html    String containing value of the data field after edit
637                          */
638                         var new_html = $(this).find('textarea').val();
640                         if($(this).is('.transformed')) {
641                             var field_name = getFieldName($(this), disp_mode);
642                             var this_field = $(this);
644                             $.each(data.transformations, function(key, value) {
645                                 if(key == field_name) {
646                                     if($(this_field).is('.text_plain, .application_octetstream')) {
647                                         new_html = value;
648                                         return false;
649                                     }
650                                     else {
651                                         var new_value = $(this_field).find('textarea').val();
652                                         new_html = $(value).append(new_value);
653                                         return false;
654                                     }
655                                 }
656                             })
657                         }
658                     }
659                     else {
660                         var new_html = $(this).find('select').val();
661                         if($(this).is('.relation')) {
662                             var field_name = getFieldName($(this), disp_mode);
663                             var this_field = $(this);
665                             $.each(data.relations, function(key, value) {
666                                 if(key == field_name) {
667                                     var new_value = $(this_field).find('select').val();
668                                     new_html = $(value).append(new_value);
669                                     return false;
670                                 }
671                             })
672                         }
673                     }
674                     $(this).html(new_html);
675                 })
676             }
677             else {
678                 PMA_ajaxShowMessage(data.error);
679             };
680         }) // end $.post()
681     }) // End After editing, clicking again should post data
682 }, 'top.frame_content') // end $(document).ready()
684 /**#@- */