1 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 * @fileoverview functions used wherever an sql query form is used
6 * @requires js/functions.js
11 * decode a string URL_encoded
14 * @return string the URL-decoded string
16 function PMA_urldecode(str) {
17 return decodeURIComponent(str.replace(/\+/g, '%20'));
21 * Get the field name for the current field. Required to construct the query
24 * @param $this_field jQuery object that points to the current field's tr
25 * @param disp_mode string
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();
37 var this_field_index = $this_field.index();
38 // ltr or rtl direction does not impact how the DOM was generated
40 // 5 columns to account for the checkbox, edit, appended inline edit, copy and delete anchors but index is zero-based so substract 4
41 var field_name = $('#table_results').find('thead').find('th:nth('+ (this_field_index-4 )+') a').text();
42 // happens when just one row (headings contain no a)
43 if ("" == field_name) {
44 field_name = $('#table_results').find('thead').find('th:nth('+ (this_field_index-4 )+')').text();
48 field_name = $.trim(field_name);
54 * The function that iterates over each row in the table_results and appends a
55 * new inline edit anchor to each table row.
58 function appendInlineAnchor() {
59 var disp_mode = $("#top_direction_dropdown").val();
61 if (disp_mode == 'vertical') {
62 // there can be one or two tr containing this class, depending
63 // on the ModifyDeleteAtLeft and ModifyDeleteAtRight cfg parameters
64 $('#table_results tr')
65 .find('.edit_row_anchor')
66 .removeClass('.edit_row_anchor')
67 .parent().each(function() {
68 var $this_tr = $(this);
69 var $cloned_tr = $this_tr.clone();
71 var $img_object = $cloned_tr.find('img:first').attr('title', PMA_messages['strInlineEdit']);
72 var img_src = $img_object.attr('src').replace(/b_edit/,'b_inline_edit');
73 $img_object.attr('src', img_src);
76 .addClass('inline_edit_anchor')
77 .find('a').attr('href', '#')
79 .text(' ' + PMA_messages['strInlineEdit'])
80 .prepend($img_object);
82 $cloned_tr.insertAfter($this_tr);
85 $('#rowsDeleteForm').find('tbody').find('th').each(function() {
86 var $this_th = $(this);
87 if ($this_th.attr('rowspan') == 4) {
88 $this_th.attr('rowspan', '5');
93 $('.edit_row_anchor').each(function() {
96 $this_td.removeClass('edit_row_anchor');
98 var $cloned_anchor = $this_td.clone();
100 var $img_object = $cloned_anchor.find('img').attr('title', PMA_messages['strInlineEdit']);
101 var img_src = $img_object.attr('src').replace(/b_edit/,'b_inline_edit');
102 $img_object.attr('src', img_src);
104 $cloned_anchor.addClass('inline_edit_anchor')
105 .find('a').attr('href', '#')
107 .text(' ' + PMA_messages['strInlineEdit'])
108 .prepend($img_object);
110 $this_td.after($cloned_anchor);
113 $('#rowsDeleteForm').find('thead, tbody').find('th').each(function() {
114 var $this_th = $(this);
115 if ($this_th.attr('colspan') == 4) {
116 $this_th.attr('colspan', '5');
127 * @description <p>Ajax scripts for sql and browse pages</p>
129 * Actions ajaxified here:
131 * <li>Retrieve results of an SQL query</li>
132 * <li>Paginate the results table</li>
133 * <li>Sort the results table</li>
134 * <li>Change table according to display options</li>
135 * <li>Inline editing of data</li>
138 * @name document.ready
141 $(document).ready(function() {
144 * Set a parameter for all Ajax queries made on this page. Don't let the
145 * web server serve cached pages
152 * current value of the direction in which the table is displayed
157 var disp_mode = $("#top_direction_dropdown").val();
160 * Update value of {@link jQuery.disp_mode} everytime the direction dropdown changes value
162 * @name direction_dropdown_change
164 $("#top_direction_dropdown, #bottom_direction_dropdown").live('change', function(event) {
165 disp_mode = $(this).val();
169 * Attach the {@link appendInlineAnchor} function to a custom event, which
170 * will be triggered manually everytime the table of results is reloaded
173 $("#sqlqueryresults").live('appendAnchor',function() {
174 appendInlineAnchor();
178 * Trigger the appendAnchor event to prepare the first table for inline edit
179 * (see $GLOBALS['cfg']['AjaxEnable'])
181 * @name sqlqueryresults_trigger
183 $("#sqlqueryresults.ajax").trigger('appendAnchor');
186 * Append the "Show/Hide query box" message to the query input form
189 * @name appendToggleSpan
191 // do not add this link more than once
192 if (! $('#sqlqueryform').find('a').is('#togglequerybox')) {
193 $('<a id="togglequerybox"></a>')
194 .html(PMA_messages['strHideQueryBox'])
195 .appendTo("#sqlqueryform")
196 // initially hidden because at this point, nothing else
197 // appears under the link
200 // Attach the toggling of the query box visibility to a click
201 $("#togglequerybox").bind('click', function() {
203 $link.siblings().slideToggle("fast");
204 if ($link.text() == PMA_messages['strHideQueryBox']) {
205 $link.text(PMA_messages['strShowQueryBox']);
207 $link.text(PMA_messages['strHideQueryBox']);
209 // avoid default click action
215 * Ajax Event handler for 'SQL Query Submit'
217 * @see PMA_ajaxShowMessage()
218 * @see $cfg['AjaxEnable']
220 * @name sqlqueryform_submit
222 $("#sqlqueryform.ajax").live('submit', function(event) {
223 event.preventDefault();
224 // remove any div containing a previous error message
225 $('.error').remove();
228 PMA_ajaxShowMessage();
230 if (! $form.find('input:hidden').is('#ajax_request_hidden')) {
231 $form.append('<input type="hidden" id="ajax_request_hidden" name="ajax_request" value="true" />');
234 $.post($(this).attr('action'), $(this).serialize() , function(data) {
235 if(data.success == true) {
236 // fade out previous messages, if any
237 $('.success').fadeOut();
238 $('.sqlquery_message').fadeOut();
239 // show a message that stays on screen
240 $('#sqlqueryform').before(data.message);
241 // and display the query
242 $('<div class="sqlquery_message"></div>')
243 .html(data.sql_query)
244 .insertBefore('#sqlqueryform');
245 // unnecessary div that came from data.sql_query
246 $('.notice').remove();
247 $('#sqlqueryresults').show();
248 // this happens if a USE command was typed
249 if (typeof data.reload != 'undefined') {
250 $form.find('input[name=db]').val(data.db);
251 // need to regenerate the whole upper part
252 $form.find('input[name=ajax_request]').remove();
253 $form.append('<input type="hidden" name="reload" value="true" />');
254 $.post('db_sql.php', $form.serialize(), function(data) {
255 $('body').html(data);
256 }); // end inner post
259 else if (data.success == false ) {
260 // show an error message that stays on screen
261 $('#sqlqueryform').before(data.error);
262 $('#sqlqueryresults').hide();
265 // real results are returned
266 $('#sqlqueryresults').show();
267 $("#sqlqueryresults").html(data);
268 $("#sqlqueryresults").trigger('appendAnchor');
269 $('#togglequerybox').show();
270 if($("#togglequerybox").siblings(":visible").length > 0) {
271 $("#togglequerybox").trigger('click');
276 }) // end SQL Query submit
279 * Ajax Event handlers for Paginating the results table
283 * Paginate when we click any of the navigation buttons
284 * (only if the element has the ajax class, see $cfg['AjaxEnable'])
286 * @name paginate_nav_button_click
287 * @uses PMA_ajaxShowMessage()
288 * @see $cfg['AjaxEnable']
290 $("input[name=navig].ajax").live('click', function(event) {
292 event.preventDefault();
294 PMA_ajaxShowMessage();
297 * @var $the_form Object referring to the form element that paginates the results table
299 var $the_form = $(this).parent("form");
301 $the_form.append('<input type="hidden" name="ajax_request" value="true" />');
303 $.post($the_form.attr('action'), $the_form.serialize(), function(data) {
304 $("#sqlqueryresults").html(data);
305 $("#sqlqueryresults").trigger('appendAnchor');
308 })// end Paginate results table
311 * Paginate results with Page Selector dropdown
313 * @name paginate_dropdown_change
314 * @see $cfg['AjaxEnable']
316 $("#pageselector").live('change', function(event) {
317 var $the_form = $(this).parent("form");
319 if ($(this).hasClass('ajax')) {
320 event.preventDefault();
322 PMA_ajaxShowMessage();
324 $.post($the_form.attr('action'), $the_form.serialize() + '&ajax_request=true', function(data) {
325 $("#sqlqueryresults").html(data);
326 $("#sqlqueryresults").trigger('appendAnchor');
333 })// end Paginate results with Page Selector
336 * Ajax Event handler for sorting the results table
338 * @name table_results_sort_click
339 * @see $cfg['AjaxEnable']
341 $("#table_results.ajax").find("a[title=Sort]").live('click', function(event) {
342 event.preventDefault();
344 PMA_ajaxShowMessage();
348 $.get($anchor.attr('href'), $anchor.serialize() + '&ajax_request=true', function(data) {
349 $("#sqlqueryresults")
351 .trigger('appendAnchor');
353 })//end Sort results table
356 * Ajax Event handler for the display options
358 * @name displayOptionsForm_submit
359 * @see $cfg['AjaxEnable']
361 $("#displayOptionsForm.ajax").live('submit', function(event) {
362 event.preventDefault();
366 $.post($form.attr('action'), $form.serialize() + '&ajax_request=true' , function(data) {
367 $("#sqlqueryresults")
369 .trigger('appendAnchor');
373 //end displayOptionsForm handler
376 * Ajax Event handlers for Inline Editing
380 * On click, replace the fields of current row with an input/textarea
382 * @name inline_edit_start
383 * @see PMA_ajaxShowMessage()
384 * @see getFieldName()
386 $(".inline_edit_anchor").live('click', function(event) {
388 event.preventDefault();
390 $(this).removeClass('inline_edit_anchor').addClass('inline_edit_active');
392 // Initialize some variables
393 if(disp_mode == 'vertical') {
395 * @var this_row_index Index of the current <td> in the parent <tr>
396 * Current <td> is the inline edit anchor.
398 var this_row_index = $(this).index();
400 * @var $input_siblings Object referring to all inline editable events from same row
402 var $input_siblings = $(this).parents('tbody').find('tr').find('.inline_edit:nth('+this_row_index+')');
404 * @var where_clause String containing the WHERE clause to select this row
406 var where_clause = $(this).parents('tbody').find('tr').find('.where_clause:nth('+this_row_index+')').val();
409 var $input_siblings = $(this).parent('tr').find('.inline_edit');
410 var where_clause = $(this).parent('tr').find('.where_clause').val();
413 $input_siblings.each(function() {
416 * @var data_value Current value of this field
418 var data_value = $(this).html();
420 // We need to retrieve the value from the server for truncated/relation fields
421 // Find the field name
424 * @var this_field Object referring to this field (<td>)
426 var $this_field = $(this);
428 * @var field_name String containing the name of this field.
429 * @see getFieldName()
431 var field_name = getFieldName($this_field, disp_mode);
433 // In each input sibling, wrap the current value in a textarea
434 // and store the current value in a hidden span
435 if($this_field.is(':not(.truncated, .transformed, .relation, .enum, .null)')) {
436 // handle non-truncated, non-transformed, non-relation values
437 // We don't need to get any more data, just wrap the value
438 $this_field.html('<textarea>'+data_value+'</textarea>')
439 .append('<span class="original_data">'+data_value+'</span>');
440 $(".original_data").hide();
442 else if($this_field.is('.truncated, .transformed')) {
444 //handle truncated/transformed values values
447 * @var sql_query String containing the SQL query used to retrieve value of truncated/transformed data
449 var sql_query = 'SELECT ' + field_name + ' FROM ' + window.parent.table + ' WHERE ' + where_clause;
451 // Make the Ajax call and get the data, wrap it and insert it
453 'token' : window.parent.token,
454 'db' : window.parent.db,
455 'ajax_request' : true,
456 'sql_query' : sql_query,
459 if(data.success == true) {
460 $this_field.html('<textarea>'+data.value+'</textarea>')
461 .append('<span class="original_data">'+data_value+'</span>');
462 $(".original_data").hide();
465 PMA_ajaxShowMessage(data.error);
469 else if($this_field.is('.relation')) {
474 * @var curr_value String containing the current value of this relational field
476 var curr_value = $this_field.find('a').text();
479 * @var post_params Object containing parameters for the POST request
482 'ajax_request' : true,
483 'get_relational_values' : true,
484 'db' : window.parent.db,
485 'table' : window.parent.table,
486 'column' : field_name,
487 'token' : window.parent.token,
488 'curr_value' : curr_value
491 $.post('sql.php', post_params, function(data) {
492 $this_field.html(data.dropdown)
493 .append('<span class="original_data">'+data_value+'</span>');
494 $(".original_data").hide();
497 else if($this_field.is('.enum')) {
501 * @var curr_value String containing the current value of this relational field
503 var curr_value = $this_field.text();
506 * @var post_params Object containing parameters for the POST request
509 'ajax_request' : true,
510 'get_enum_values' : true,
511 'db' : window.parent.db,
512 'table' : window.parent.table,
513 'column' : field_name,
514 'token' : window.parent.token,
515 'curr_value' : curr_value
518 $.post('sql.php', post_params, function(data) {
519 $this_field.html(data.dropdown)
520 .append('<span class="original_data">'+data_value+'</span>');
521 $(".original_data").hide();
524 else if($this_field.is('.null')) {
526 $this_field.html('<textarea></textarea>')
527 .append('<span class="original_data">NULL</span>');
528 $(".original_data").hide();
531 }) // End On click, replace the current field with an input/textarea
534 * After editing, clicking again should post data
537 * @name inline_edit_save
538 * @see PMA_ajaxShowMessage()
539 * @see getFieldName()
541 $(".inline_edit_active").live('click', function(event) {
543 event.preventDefault();
546 * @var $this_td Object referring to the td containing the
547 * "Inline Edit" link that was clicked to save the row that is
551 var $this_td = $(this);
553 var $test_element = ''; // to test the presence of a element
555 // Initialize variables
556 if(disp_mode == 'vertical') {
558 * @var this_td_index Index of the current <td> in the parent <tr>
559 * Current <td> is the inline edit anchor.
561 var this_td_index = $this_td.index();
563 * @var $input_siblings Object referring to all inline editable events from same row
565 var $input_siblings = $this_td.parents('tbody').find('tr').find('.inline_edit:nth('+this_td_index+')');
567 * @var where_clause String containing the WHERE clause to select this row
569 var where_clause = $this_td.parents('tbody').find('tr').find('.where_clause:nth('+this_td_index+')').val();
572 var $input_siblings = $this_td.parent('tr').find('.inline_edit');
573 var where_clause = $this_td.parent('tr').find('.where_clause').val();
577 * @var nonunique Boolean, whether this row is unique or not
579 if($this_td.is('.nonunique')) {
586 // Collect values of all fields to submit, we don't know which changed
588 * @var params_to_submit Array containing the name/value pairs of all fields
590 var params_to_submit = {};
592 * @var relation_fields Array containing the name/value pairs of relational fields
594 var relation_fields = {};
596 * @var transform_fields Array containing the name/value pairs for transformed fields
598 var transform_fields = {};
600 * @var transformation_fields Boolean, if there are any transformed fields in this row
602 var transformation_fields = false;
604 $input_siblings.each(function() {
607 * @var this_field Object referring to this field (<td>)
609 var $this_field = $(this);
611 * @var field_name String containing the name of this field.
612 * @see getFieldName()
614 var field_name = getFieldName($this_field, disp_mode);
617 * @var this_field_params Array temporary storage for the name/value of current field
619 var this_field_params = {};
621 if($this_field.is('.transformed')) {
622 transformation_fields = true;
625 if($this_field.is(":not(.relation, .enum)")) {
626 this_field_params[field_name] = $this_field.find('textarea').val();
627 if($this_field.is('.transformed')) {
628 $.extend(transform_fields, this_field_params);
632 // results from a drop-down
633 $test_element = $this_field.find('select');
634 if ($test_element.length != 0) {
635 this_field_params[field_name] = $test_element.val();
638 // results from Browse foreign value
639 $test_element = $this_field.find('span.curr_value');
640 if ($test_element.length != 0) {
641 this_field_params[field_name] = $test_element.text();
644 if($this_field.is('.relation')) {
645 $.extend(relation_fields, this_field_params);
649 $.extend(params_to_submit, this_field_params);
653 * @var sql_query String containing the SQL query to update this row
655 var sql_query = 'UPDATE ' + window.parent.table + ' SET ';
657 // $.each() not used here since it cause problems when there is a column
658 // in the table with the name 'length'. See bug #3184827
660 for (var key in params_to_submit) {
661 value = params_to_submit[key];
662 if (value.length == 0) {
663 sql_query += ' ' + key + "=NULL, ";
665 sql_query += ' ' + key + "='" + value.replace(/'/g, "''") + "' , ";
668 //Remove the last ',' appended in the above loop
669 sql_query = sql_query.replace(/,\s$/, '');
670 sql_query += ' WHERE ' + PMA_urldecode(where_clause);
673 * @var rel_fields_list String, url encoded representation of {@link relations_fields}
675 var rel_fields_list = $.param(relation_fields);
678 * @var transform_fields_list String, url encoded representation of {@link transform_fields}
680 var transform_fields_list = $.param(transform_fields);
682 // Make the Ajax post after setting all parameters
684 * @var post_params Object containing parameters for the POST request
686 var post_params = {'ajax_request' : true,
687 'sql_query' : sql_query,
688 'disp_direction' : disp_mode,
689 'token' : window.parent.token,
690 'db' : window.parent.db,
691 'table' : window.parent.table,
692 'clause_is_unique' : nonunique,
693 'where_clause' : where_clause,
694 'rel_fields_list' : rel_fields_list,
695 'do_transformations' : transformation_fields,
696 'transform_fields_list' : transform_fields_list,
698 'submit_type' : 'save'
701 $.post('tbl_replace.php', post_params, function(data) {
702 if(data.success == true) {
703 PMA_ajaxShowMessage(data.message);
704 $this_td.removeClass('inline_edit_active').addClass('inline_edit_anchor');
706 $input_siblings.each(function() {
707 // Inline edit post has been successful.
708 $this_sibling = $(this);
709 if($this_sibling.is(':not(.relation, .enum)')) {
711 * @var new_html String containing value of the data field after edit
713 var new_html = $this_sibling.find('textarea').val();
715 if($this_sibling.is('.transformed')) {
716 var field_name = getFieldName($this_sibling, disp_mode);
717 $.each(data.transformations, function(key, value) {
718 if(key == field_name) {
719 if($this_sibling.is('.text_plain, .application_octetstream')) {
724 var new_value = $this_sibling.find('textarea').val();
725 new_html = $(value).append(new_value);
735 $test_element = $this_sibling.find('select');
736 if ($test_element.length != 0) {
737 new_value = $test_element.val();
740 $test_element = $this_sibling.find('span.curr_value');
741 if ($test_element.length != 0) {
742 new_value = $test_element.text();
746 if($this_sibling.is('.relation')) {
747 var field_name = getFieldName($this_sibling, disp_mode);
748 $.each(data.relations, function(key, value) {
749 if(key == field_name) {
750 new_html = $(value).append(new_value);
755 if($this_sibling.is('.enum')) {
756 new_html = new_value;
759 $this_sibling.html(new_html);
763 PMA_ajaxShowMessage(data.error);
766 }) // End After editing, clicking again should post data
767 }, 'top.frame_content') // end $(document).ready()
770 * Starting from some th, change the class of all td under it
772 function PMA_changeClassForColumn($this_th, newclass) {
773 // index 0 is the th containing the big T
774 var th_index = $this_th.index();
775 // .eq() is zero-based
777 var $tr_with_data = $this_th.closest('table').find('tbody tr ').has('td.data');
778 $tr_with_data.each(function() {
779 $(this).find('td.data:eq('+th_index+')').toggleClass(newclass);
783 $(document).ready(function() {
785 $('.browse_foreign').live('click', function(e) {
787 window.open(this.href, 'foreigners', 'width=640,height=240,scrollbars=yes,resizable=yes');
789 $anchor.addClass('browse_foreign_clicked');
794 * vertical column highlighting in horizontal mode when hovering over the column header
796 $('.column_heading').live('hover', function() {
797 PMA_changeClassForColumn($(this), 'hover');
801 * vertical column marking in horizontal mode when clicking the column header
803 $('.column_heading').live('click', function() {
804 PMA_changeClassForColumn($(this), 'marked');