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 // 4 columns to account for the checkbox, edit, delete and appended inline edit anchors but index is zero-based so substract 3
41 var field_name = $('#table_results').find('thead').find('th:nth('+ (this_field_index-3 )+') 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-3 )+')').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 $("#table_results").find('tr').find(':checkbox').closest('tr').find('th')
86 .attr('rowspan', '4');
89 $('.edit_row_anchor').each(function() {
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']);
97 var img_src = $img_object.attr('src').replace(/b_edit/,'b_inline_edit');
98 $img_object.attr('src', img_src);
100 $cloned_anchor.addClass('inline_edit_anchor')
101 .find('a').attr('href', '#')
103 .text(' ' + PMA_messages['strInlineEdit'])
104 .prepend($img_object);
106 $this_td.after($cloned_anchor);
109 $('#rowsDeleteForm').find('thead, tbody').find('th').each(function() {
110 var $this_th = $(this);
111 if ($this_th.attr('colspan') == 3) {
112 $this_th.attr('colspan', '4')
123 * @description <p>Ajax scripts for sql and browse pages</p>
125 * Actions ajaxified here:
127 * <li>Retrieve results of an SQL query</li>
128 * <li>Paginate the results table</li>
129 * <li>Sort the results table</li>
130 * <li>Change table according to display options</li>
131 * <li>Inline editing of data</li>
134 * @name document.ready
137 $(document).ready(function() {
140 * Set a parameter for all Ajax queries made on this page. Don't let the
141 * web server serve cached pages
148 * current value of the direction in which the table is displayed
153 var disp_mode = $("#top_direction_dropdown").val();
156 * Update value of {@link jQuery.disp_mode} everytime the direction dropdown changes value
158 * @name direction_dropdown_change
160 $("#top_direction_dropdown, #bottom_direction_dropdown").live('change', function(event) {
161 disp_mode = $(this).val();
165 * Attach the {@link appendInlineAnchor} function to a custom event, which
166 * will be triggered manually everytime the table of results is reloaded
169 $("#sqlqueryresults").live('appendAnchor',function() {
170 appendInlineAnchor();
174 * Trigger the appendAnchor event to prepare the first table for inline edit
175 * (see $GLOBALS['cfg']['AjaxEnable'])
177 * @name sqlqueryresults_trigger
179 $("#sqlqueryresults.ajax").trigger('appendAnchor');
182 * Append the "Show/Hide query box" message to the query input form
185 * @name appendToggleSpan
187 // do not add this link more than once
188 if (! $('#sqlqueryform').find('a').is('#togglequerybox')) {
189 $('<a id="togglequerybox"></a>')
190 .html(PMA_messages['strHideQueryBox'])
191 .appendTo("#sqlqueryform");
193 // Attach the toggling of the query box visibility to a click
194 $("#togglequerybox").bind('click', function() {
196 $link.siblings().slideToggle("medium");
197 if ($link.text() == PMA_messages['strHideQueryBox']) {
198 $link.text(PMA_messages['strShowQueryBox']);
200 $link.text(PMA_messages['strHideQueryBox']);
202 // avoid default click action
208 * Ajax Event handler for 'SQL Query Submit'
210 * @see PMA_ajaxShowMessage()
211 * @see $cfg['AjaxEnable']
213 * @name sqlqueryform_submit
215 $("#sqlqueryform.ajax").live('submit', function(event) {
216 event.preventDefault();
217 // remove any div containing a previous error message
218 $('.error').remove();
221 PMA_ajaxShowMessage();
223 if (! $form.find('input:hidden').is('#ajax_request_hidden')) {
224 $form.append('<input type="hidden" id="ajax_request_hidden" name="ajax_request" value="true" />');
227 $.post($(this).attr('action'), $(this).serialize() , function(data) {
228 if(data.success == true) {
229 // fade out previous success message, if any
230 $('.success').fadeOut();
231 // show a message that stays on screen
232 $('#sqlqueryform').before(data.message);
233 $('#sqlqueryresults').show();
234 // this happens if a USE command was typed
235 if (typeof data.reload != 'undefined') {
236 $form.find('input[name=db]').val(data.db);
237 // need to regenerate the whole upper part
238 $form.find('input[name=ajax_request]').remove();
239 $form.append('<input type="hidden" name="reload" value="true" />');
240 $.post('db_sql.php', $form.serialize(), function(data) {
241 $('body').html(data);
242 }); // end inner post
245 else if (data.success == false ) {
246 // show an error message that stays on screen
247 $('#sqlqueryform').before(data.error);
248 $('#sqlqueryresults').hide();
251 // real results are returned
252 $('#sqlqueryresults').show();
253 $("#sqlqueryresults").html(data);
254 $("#sqlqueryresults").trigger('appendAnchor');
255 if($("#togglequerybox").siblings(":visible").length > 0) {
256 $("#togglequerybox").trigger('click');
260 }) // end SQL Query submit
263 * Ajax Event handlers for Paginating the results table
267 * Paginate when we click any of the navigation buttons
268 * (only if the element has the ajax class, see $cfg['AjaxEnable'])
270 * @name paginate_nav_button_click
271 * @uses PMA_ajaxShowMessage()
272 * @see $cfg['AjaxEnable']
274 $("input[name=navig].ajax").live('click', function(event) {
276 event.preventDefault();
278 PMA_ajaxShowMessage();
281 * @var $the_form Object referring to the form element that paginates the results table
283 var $the_form = $(this).parent("form");
285 $the_form.append('<input type="hidden" name="ajax_request" value="true" />');
287 $.post($the_form.attr('action'), $the_form.serialize(), function(data) {
288 $("#sqlqueryresults").html(data);
289 $("#sqlqueryresults").trigger('appendAnchor');
291 })// end Paginate results table
294 * Paginate results with Page Selector dropdown
296 * @name paginate_dropdown_change
297 * @see $cfg['AjaxEnable']
299 $("#pageselector").live('change', function(event) {
300 var $the_form = $(this).parent("form");
302 if ($(this).hasClass('ajax')) {
303 event.preventDefault();
305 PMA_ajaxShowMessage();
307 $.post($the_form.attr('action'), $the_form.serialize() + '&ajax_request=true', function(data) {
308 $("#sqlqueryresults").html(data);
309 $("#sqlqueryresults").trigger('appendAnchor');
315 })// end Paginate results with Page Selector
318 * Ajax Event handler for sorting the results table
320 * @name table_results_sort_click
321 * @see $cfg['AjaxEnable']
323 $("#table_results.ajax").find("a[title=Sort]").live('click', function(event) {
324 event.preventDefault();
326 PMA_ajaxShowMessage();
330 $.get($anchor.attr('href'), $anchor.serialize() + '&ajax_request=true', function(data) {
331 $("#sqlqueryresults")
333 .trigger('appendAnchor');
335 })//end Sort results table
338 * Ajax Event handler for the display options
340 * @name displayOptionsForm_submit
341 * @see $cfg['AjaxEnable']
343 $("#displayOptionsForm.ajax").live('submit', function(event) {
344 event.preventDefault();
348 $.post($form.attr('action'), $form.serialize() + '&ajax_request=true' , function(data) {
349 $("#sqlqueryresults")
351 .trigger('appendAnchor');
354 //end displayOptionsForm handler
357 * Ajax Event handlers for Inline Editing
361 * On click, replace the fields of current row with an input/textarea
363 * @name inline_edit_start
364 * @see PMA_ajaxShowMessage()
365 * @see getFieldName()
367 $(".inline_edit_anchor").live('click', function(event) {
369 event.preventDefault();
371 $(this).removeClass('inline_edit_anchor').addClass('inline_edit_active');
373 // Initialize some variables
374 if(disp_mode == 'vertical') {
376 * @var this_row_index Index of the current <td> in the parent <tr>
377 * Current <td> is the inline edit anchor.
379 var this_row_index = $(this).index();
381 * @var $input_siblings Object referring to all inline editable events from same row
383 var $input_siblings = $(this).parents('tbody').find('tr').find('.inline_edit:nth('+this_row_index+')');
385 * @var where_clause String containing the WHERE clause to select this row
387 var where_clause = $(this).parents('tbody').find('tr').find('.where_clause:nth('+this_row_index+')').val();
390 var $input_siblings = $(this).parent('tr').find('.inline_edit');
391 var where_clause = $(this).parent('tr').find('.where_clause').val();
394 $input_siblings.each(function() {
397 * @var data_value Current value of this field
399 var data_value = $(this).html();
401 // We need to retrieve the value from the server for truncated/relation fields
402 // Find the field name
405 * @var this_field Object referring to this field (<td>)
407 var $this_field = $(this);
409 * @var field_name String containing the name of this field.
410 * @see getFieldName()
412 var field_name = getFieldName($this_field, disp_mode);
414 // In each input sibling, wrap the current value in a textarea
415 // and store the current value in a hidden span
416 if($this_field.is(':not(.truncated, .transformed, .relation, .enum, .null)')) {
417 // handle non-truncated, non-transformed, non-relation values
418 // We don't need to get any more data, just wrap the value
419 $this_field.html('<textarea>'+data_value+'</textarea>')
420 .append('<span class="original_data">'+data_value+'</span>');
421 $(".original_data").hide();
423 else if($this_field.is('.truncated, .transformed')) {
425 //handle truncated/transformed values values
428 * @var sql_query String containing the SQL query used to retrieve value of truncated/transformed data
430 var sql_query = 'SELECT ' + field_name + ' FROM ' + window.parent.table + ' WHERE ' + where_clause;
432 // Make the Ajax call and get the data, wrap it and insert it
434 'token' : window.parent.token,
435 'db' : window.parent.db,
436 'ajax_request' : true,
437 'sql_query' : sql_query,
440 if(data.success == true) {
441 $this_field.html('<textarea>'+data.value+'</textarea>')
442 .append('<span class="original_data">'+data_value+'</span>');
443 $(".original_data").hide();
446 PMA_ajaxShowMessage(data.error);
450 else if($this_field.is('.relation')) {
455 * @var curr_value String containing the current value of this relational field
457 var curr_value = $this_field.find('a').text();
460 * @var post_params Object containing parameters for the POST request
463 'ajax_request' : true,
464 'get_relational_values' : true,
465 'db' : window.parent.db,
466 'table' : window.parent.table,
467 'column' : field_name,
468 'token' : window.parent.token,
469 'curr_value' : curr_value
472 $.post('sql.php', post_params, function(data) {
473 $this_field.html(data.dropdown)
474 .append('<span class="original_data">'+data_value+'</span>');
475 $(".original_data").hide();
478 else if($this_field.is('.enum')) {
482 * @var curr_value String containing the current value of this relational field
484 var curr_value = $this_field.text();
487 * @var post_params Object containing parameters for the POST request
490 'ajax_request' : true,
491 'get_enum_values' : true,
492 'db' : window.parent.db,
493 'table' : window.parent.table,
494 'column' : field_name,
495 'token' : window.parent.token,
496 'curr_value' : curr_value
499 $.post('sql.php', post_params, function(data) {
500 $this_field.html(data.dropdown)
501 .append('<span class="original_data">'+data_value+'</span>');
502 $(".original_data").hide();
505 else if($this_field.is('.null')) {
507 $this_field.html('<textarea></textarea>')
508 .append('<span class="original_data">NULL</span>');
509 $(".original_data").hide();
512 }) // End On click, replace the current field with an input/textarea
515 * After editing, clicking again should post data
518 * @name inline_edit_save
519 * @see PMA_ajaxShowMessage()
520 * @see getFieldName()
522 $(".inline_edit_active").live('click', function(event) {
524 event.preventDefault();
527 * @var $this_td Object referring to the td containing the
528 * "Inline Edit" link that was clicked to save the row that is
532 var $this_td = $(this);
534 var $test_element = ''; // to test the presence of a element
536 // Initialize variables
537 if(disp_mode == 'vertical') {
539 * @var this_td_index Index of the current <td> in the parent <tr>
540 * Current <td> is the inline edit anchor.
542 var this_td_index = $this_td.index();
544 * @var $input_siblings Object referring to all inline editable events from same row
546 var $input_siblings = $this_td.parents('tbody').find('tr').find('.inline_edit:nth('+this_td_index+')');
548 * @var where_clause String containing the WHERE clause to select this row
550 var where_clause = $this_td.parents('tbody').find('tr').find('.where_clause:nth('+this_td_index+')').val();
553 var $input_siblings = $this_td.parent('tr').find('.inline_edit');
554 var where_clause = $this_td.parent('tr').find('.where_clause').val();
558 * @var nonunique Boolean, whether this row is unique or not
560 if($this_td.is('.nonunique')) {
567 // Collect values of all fields to submit, we don't know which changed
569 * @var params_to_submit Array containing the name/value pairs of all fields
571 var params_to_submit = {};
573 * @var relation_fields Array containing the name/value pairs of relational fields
575 var relation_fields = {};
577 * @var transform_fields Array containing the name/value pairs for transformed fields
579 var transform_fields = {};
581 * @var transformation_fields Boolean, if there are any transformed fields in this row
583 var transformation_fields = false;
585 $input_siblings.each(function() {
588 * @var this_field Object referring to this field (<td>)
590 var $this_field = $(this);
592 * @var field_name String containing the name of this field.
593 * @see getFieldName()
595 var field_name = getFieldName($this_field, disp_mode);
598 * @var this_field_params Array temporary storage for the name/value of current field
600 var this_field_params = {};
602 if($this_field.is('.transformed')) {
603 transformation_fields = true;
606 if($this_field.is(":not(.relation, .enum)")) {
607 this_field_params[field_name] = $this_field.find('textarea').val();
608 if($this_field.is('.transformed')) {
609 $.extend(transform_fields, this_field_params);
613 // results from a drop-down
614 $test_element = $this_field.find('select');
615 if ($test_element.length != 0) {
616 this_field_params[field_name] = $test_element.val();
619 // results from Browse foreign value
620 $test_element = $this_field.find('span.curr_value');
621 if ($test_element.length != 0) {
622 this_field_params[field_name] = $test_element.text();
625 if($this_field.is('.relation')) {
626 $.extend(relation_fields, this_field_params);
630 $.extend(params_to_submit, this_field_params);
634 * @var sql_query String containing the SQL query to update this row
636 var sql_query = 'UPDATE ' + window.parent.table + ' SET ';
638 $.each(params_to_submit, function(key, value) {
639 if(value.length == 0) {
642 sql_query += ' ' + key + "='" + value.replace(/'/g, "''") + "' , ";
644 //Remove the last ',' appended in the above loop
645 sql_query = sql_query.replace(/,\s$/, '');
646 sql_query += ' WHERE ' + PMA_urldecode(where_clause);
649 * @var rel_fields_list String, url encoded representation of {@link relations_fields}
651 var rel_fields_list = $.param(relation_fields);
654 * @var transform_fields_list String, url encoded representation of {@link transform_fields}
656 var transform_fields_list = $.param(transform_fields);
658 // Make the Ajax post after setting all parameters
660 * @var post_params Object containing parameters for the POST request
662 var post_params = {'ajax_request' : true,
663 'sql_query' : sql_query,
664 'disp_direction' : disp_mode,
665 'token' : window.parent.token,
666 'db' : window.parent.db,
667 'table' : window.parent.table,
668 'clause_is_unique' : nonunique,
669 'where_clause' : where_clause,
670 'rel_fields_list' : rel_fields_list,
671 'do_transformations' : transformation_fields,
672 'transform_fields_list' : transform_fields_list,
674 'submit_type' : 'save'
677 $.post('tbl_replace.php', post_params, function(data) {
678 if(data.success == true) {
679 PMA_ajaxShowMessage(data.message);
680 $this_td.removeClass('inline_edit_active').addClass('inline_edit_anchor');
682 $input_siblings.each(function() {
683 // Inline edit post has been successful.
684 $this_sibling = $(this);
685 if($this_sibling.is(':not(.relation, .enum)')) {
687 * @var new_html String containing value of the data field after edit
689 var new_html = $this_sibling.find('textarea').val();
691 if($this_sibling.is('.transformed')) {
692 var field_name = getFieldName($this_sibling, disp_mode);
693 $.each(data.transformations, function(key, value) {
694 if(key == field_name) {
695 if($this_sibling.is('.text_plain, .application_octetstream')) {
700 var new_value = $this_sibling.find('textarea').val();
701 new_html = $(value).append(new_value);
711 $test_element = $this_sibling.find('select');
712 if ($test_element.length != 0) {
713 new_value = $test_element.val();
716 $test_element = $this_sibling.find('span.curr_value');
717 if ($test_element.length != 0) {
718 new_value = $test_element.text();
722 if($this_sibling.is('.relation')) {
723 var field_name = getFieldName($this_sibling, disp_mode);
724 $.each(data.relations, function(key, value) {
725 if(key == field_name) {
726 new_html = $(value).append(new_value);
731 if($this_sibling.is('.enum')) {
732 new_html = new_value;
735 $this_sibling.html(new_html);
739 PMA_ajaxShowMessage(data.error);
742 }) // End After editing, clicking again should post data
743 }, 'top.frame_content') // end $(document).ready()
746 * Starting from some th, change the class of all td under it
748 function PMA_changeClassForColumn($this_th, newclass) {
749 // index 0 is the th containing the big T
750 var th_index = $this_th.index();
751 // .eq() is zero-based
753 var $tr_with_data = $this_th.closest('table').find('tbody tr ').has('td.data');
754 $tr_with_data.each(function() {
755 $(this).find('td.data:eq('+th_index+')').toggleClass(newclass);
759 $(document).ready(function() {
761 $('.browse_foreign').live('click', function(e) {
763 window.open(this.href, 'foreigners', 'width=640,height=240,scrollbars=yes,resizable=yes');
765 $anchor.addClass('browse_foreign_clicked');
770 * vertical column highlighting in horizontal mode when hovering over the column header
772 $('.column_heading').live('hover', function() {
773 PMA_changeClassForColumn($(this), 'hover');
777 * vertical column marking in horizontal mode when clicking the column header
779 $('.column_heading').live('click', function() {
780 PMA_changeClassForColumn($(this), 'marked');