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 if ($img_object.length != 0) {
73 var img_src = $img_object.attr('src').replace(/b_edit/,'b_inline_edit');
74 $img_object.attr('src', img_src);
78 .addClass('inline_edit_anchor')
79 .find('a').attr('href', '#')
81 .text(' ' + PMA_messages['strInlineEdit'])
82 .prepend($img_object);
84 $cloned_tr.insertAfter($this_tr);
87 $('#rowsDeleteForm').find('tbody').find('th').each(function() {
88 var $this_th = $(this);
89 if ($this_th.attr('rowspan') == 4) {
90 $this_th.attr('rowspan', '5');
96 $('.edit_row_anchor').each(function() {
98 var $this_td = $(this);
99 $this_td.removeClass('edit_row_anchor');
101 var $cloned_anchor = $this_td.clone();
103 var $img_object = $cloned_anchor.find('img').attr('title', PMA_messages['strInlineEdit']);
104 if ($img_object.length != 0) {
105 var img_src = $img_object.attr('src').replace(/b_edit/,'b_inline_edit');
106 $img_object.attr('src', img_src);
108 .find('a').attr('href', '#')
110 .text(' ' + PMA_messages['strInlineEdit']);
114 .prepend($img_object);
116 // the link was too big so <input type="image"> is there
117 $img_object = $cloned_anchor.find('input:image').attr('title', PMA_messages['strInlineEdit']);
118 var img_src = $img_object.attr('src').replace(/b_edit/,'b_inline_edit');
119 $img_object.attr('src', img_src);
121 .find('.clickprevimage')
122 .text(' ' + PMA_messages['strInlineEdit']);
126 .addClass('inline_edit_anchor');
128 $this_td.after($cloned_anchor);
131 $('#rowsDeleteForm').find('thead, tbody').find('th').each(function() {
132 var $this_th = $(this);
133 if ($this_th.attr('colspan') == 4) {
134 $this_th.attr('colspan', '5');
145 * @description <p>Ajax scripts for sql and browse pages</p>
147 * Actions ajaxified here:
149 * <li>Retrieve results of an SQL query</li>
150 * <li>Paginate the results table</li>
151 * <li>Sort the results table</li>
152 * <li>Change table according to display options</li>
153 * <li>Inline editing of data</li>
156 * @name document.ready
159 $(document).ready(function() {
162 * Set a parameter for all Ajax queries made on this page. Don't let the
163 * web server serve cached pages
170 * current value of the direction in which the table is displayed
175 var disp_mode = $("#top_direction_dropdown").val();
178 * Update value of {@link jQuery.disp_mode} everytime the direction dropdown changes value
180 * @name direction_dropdown_change
182 $("#top_direction_dropdown, #bottom_direction_dropdown").live('change', function(event) {
183 disp_mode = $(this).val();
187 * Attach the {@link appendInlineAnchor} function to a custom event, which
188 * will be triggered manually everytime the table of results is reloaded
191 $("#sqlqueryresults").live('appendAnchor',function() {
192 appendInlineAnchor();
196 * Trigger the appendAnchor event to prepare the first table for inline edit
197 * (see $GLOBALS['cfg']['AjaxEnable'])
199 * @name sqlqueryresults_trigger
201 $("#sqlqueryresults.ajax").trigger('appendAnchor');
204 * Append the "Show/Hide query box" message to the query input form
207 * @name appendToggleSpan
209 // do not add this link more than once
210 if (! $('#sqlqueryform').find('a').is('#togglequerybox')) {
211 $('<a id="togglequerybox"></a>')
212 .html(PMA_messages['strHideQueryBox'])
213 .appendTo("#sqlqueryform")
214 // initially hidden because at this point, nothing else
215 // appears under the link
218 // Attach the toggling of the query box visibility to a click
219 $("#togglequerybox").bind('click', function() {
221 $link.siblings().slideToggle("fast");
222 if ($link.text() == PMA_messages['strHideQueryBox']) {
223 $link.text(PMA_messages['strShowQueryBox']);
224 // cheap trick to add a spacer between the menu tabs
225 // and "Show query box"; feel free to improve!
226 $('#togglequerybox_spacer').remove();
227 $link.before('<br id="togglequerybox_spacer" />');
229 $link.text(PMA_messages['strHideQueryBox']);
231 // avoid default click action
237 * Ajax Event handler for 'SQL Query Submit'
239 * @see PMA_ajaxShowMessage()
240 * @see $cfg['AjaxEnable']
242 * @name sqlqueryform_submit
244 $("#sqlqueryform.ajax").live('submit', function(event) {
245 event.preventDefault();
246 // remove any div containing a previous error message
247 $('.error').remove();
250 PMA_ajaxShowMessage();
252 if (! $form.find('input:hidden').is('#ajax_request_hidden')) {
253 $form.append('<input type="hidden" id="ajax_request_hidden" name="ajax_request" value="true" />');
256 $.post($(this).attr('action'), $(this).serialize() , function(data) {
257 if(data.success == true) {
258 // fade out previous messages, if any
259 $('.success').fadeOut();
260 $('.sqlquery_message').fadeOut();
261 // show a message that stays on screen
262 $('#sqlqueryform').before(data.message);
263 // and display the query
264 $('<div class="sqlquery_message"></div>')
265 .html(data.sql_query)
266 .insertBefore('#sqlqueryform');
267 // unnecessary div that came from data.sql_query
268 $('.notice').remove();
269 $('#sqlqueryresults').show();
270 // this happens if a USE command was typed
271 if (typeof data.reload != 'undefined') {
272 $form.find('input[name=db]').val(data.db);
273 // need to regenerate the whole upper part
274 $form.find('input[name=ajax_request]').remove();
275 $form.append('<input type="hidden" name="reload" value="true" />');
276 $.post('db_sql.php', $form.serialize(), function(data) {
277 $('body').html(data);
278 }); // end inner post
281 else if (data.success == false ) {
282 // show an error message that stays on screen
283 $('#sqlqueryform').before(data.error);
284 $('#sqlqueryresults').hide();
287 // real results are returned
288 $('#sqlqueryresults').show();
289 $("#sqlqueryresults").html(data);
290 $("#sqlqueryresults").trigger('appendAnchor');
291 $('#togglequerybox').show();
292 if($("#togglequerybox").siblings(":visible").length > 0) {
293 $("#togglequerybox").trigger('click');
298 }) // end SQL Query submit
301 * Ajax Event handlers for Paginating the results table
305 * Paginate when we click any of the navigation buttons
306 * (only if the element has the ajax class, see $cfg['AjaxEnable'])
308 * @name paginate_nav_button_click
309 * @uses PMA_ajaxShowMessage()
310 * @see $cfg['AjaxEnable']
312 $("input[name=navig].ajax").live('click', function(event) {
314 event.preventDefault();
316 PMA_ajaxShowMessage();
319 * @var $the_form Object referring to the form element that paginates the results table
321 var $the_form = $(this).parent("form");
323 $the_form.append('<input type="hidden" name="ajax_request" value="true" />');
325 $.post($the_form.attr('action'), $the_form.serialize(), function(data) {
326 $("#sqlqueryresults").html(data);
327 $("#sqlqueryresults").trigger('appendAnchor');
330 })// end Paginate results table
333 * Paginate results with Page Selector dropdown
335 * @name paginate_dropdown_change
336 * @see $cfg['AjaxEnable']
338 $("#pageselector").live('change', function(event) {
339 var $the_form = $(this).parent("form");
341 if ($(this).hasClass('ajax')) {
342 event.preventDefault();
344 PMA_ajaxShowMessage();
346 $.post($the_form.attr('action'), $the_form.serialize() + '&ajax_request=true', function(data) {
347 $("#sqlqueryresults").html(data);
348 $("#sqlqueryresults").trigger('appendAnchor');
355 })// end Paginate results with Page Selector
358 * Ajax Event handler for sorting the results table
360 * @name table_results_sort_click
361 * @see $cfg['AjaxEnable']
363 $("#table_results.ajax").find("a[title=Sort]").live('click', function(event) {
364 event.preventDefault();
366 PMA_ajaxShowMessage();
370 $.get($anchor.attr('href'), $anchor.serialize() + '&ajax_request=true', function(data) {
371 $("#sqlqueryresults")
373 .trigger('appendAnchor');
375 })//end Sort results table
378 * Ajax Event handler for the display options
380 * @name displayOptionsForm_submit
381 * @see $cfg['AjaxEnable']
383 $("#displayOptionsForm.ajax").live('submit', function(event) {
384 event.preventDefault();
388 $.post($form.attr('action'), $form.serialize() + '&ajax_request=true' , function(data) {
389 $("#sqlqueryresults")
391 .trigger('appendAnchor');
395 //end displayOptionsForm handler
398 * Ajax Event handlers for Inline Editing
402 * On click, replace the fields of current row with an input/textarea
404 * @name inline_edit_start
405 * @see PMA_ajaxShowMessage()
406 * @see getFieldName()
408 $(".inline_edit_anchor").live('click', function(event) {
410 event.preventDefault();
412 $(this).removeClass('inline_edit_anchor').addClass('inline_edit_active');
414 // Initialize some variables
415 if(disp_mode == 'vertical') {
417 * @var this_row_index Index of the current <td> in the parent <tr>
418 * Current <td> is the inline edit anchor.
420 var this_row_index = $(this).index();
422 * @var $input_siblings Object referring to all inline editable events from same row
424 var $input_siblings = $(this).parents('tbody').find('tr').find('.inline_edit:nth('+this_row_index+')');
426 * @var where_clause String containing the WHERE clause to select this row
428 var where_clause = $(this).parents('tbody').find('tr').find('.where_clause:nth('+this_row_index+')').val();
432 var this_row_index = $(this).parent().index();
433 var $input_siblings = $(this).parent('tr').find('.inline_edit');
434 var where_clause = $(this).parent('tr').find('.where_clause').val();
437 $input_siblings.each(function() {
440 * @var data_value Current value of this field
442 var data_value = $(this).html();
444 // We need to retrieve the value from the server for truncated/relation fields
445 // Find the field name
448 * @var this_field Object referring to this field (<td>)
450 var $this_field = $(this);
452 * @var field_name String containing the name of this field.
453 * @see getFieldName()
455 var field_name = getFieldName($this_field, disp_mode);
457 * @var relation_curr_value String current value of the field (for fields that are foreign keyed).
459 var relation_curr_value = $this_field.find('a').text();
461 * @var enum_curr_value String current value of the field (for fields that are of type enum).
463 var enum_curr_value = $this_field.text();
465 if($this_field.is(':not(.not_null)')){
466 // add a checkbox to mark null for all the field that are nullable.
467 $this_field.html('<div class="null_div">Null :<input type="checkbox" class="checkbox_null_'+ field_name + '_' + this_row_index +'"></div>');
468 // check the 'checkbox_null_<field_name>_<row_index>' if the corresponding value is null
469 if($this_field.is('.null')) {
470 $('.checkbox_null_' + field_name + '_' + this_row_index).attr('checked', true);
473 // if the select/editor is changed un-check the 'checkbox_null_<field_name>_<row_index>'.
474 if ($this_field.is('.enum, .set')) {
475 $this_field.find('select').live('change', function(e) {
476 $('.checkbox_null_' + field_name + '_' + this_row_index).attr('checked', false);
478 } else if ($this_field.is('.relation')) {
479 $this_field.find('select').live('change', function(e) {
480 $('.checkbox_null_' + field_name + '_' + this_row_index).attr('checked', false);
482 $this_field.find('.browse_foreign').live('click', function(e) {
483 $('.checkbox_null_' + field_name + '_' + this_row_index).attr('checked', false);
486 $this_field.find('textarea').live('keypress', function(e) {
487 $('.checkbox_null_' + field_name + '_' + this_row_index).attr('checked', false);
491 // if 'chechbox_null_<field_name>_<row_index>' is clicked empty the corresponding select/editor.
492 $('.checkbox_null_' + field_name + '_' + this_row_index).bind('click', function(e) {
493 if ($this_field.is('.enum, .set')) {
494 $this_field.find('select').attr('value', '');
495 } else if ($this_field.is('.relation')) {
496 // if the dropdown is there to select the foreign value
497 if ($this_field.find('select').length > 0) {
498 $this_field.find('select').attr('value', '');
499 // if foriegn value is selected by browsing foreing values
501 $this_field.find('span.curr_value').empty();
504 $this_field.find('textarea').val('');
509 $this_field.html('<div class="null_div"></div>');
512 // In each input sibling, wrap the current value in a textarea
513 // and store the current value in a hidden span
514 if($this_field.is(':not(.truncated, .transformed, .relation, .enum, .null)')) {
515 // handle non-truncated, non-transformed, non-relation values
516 // We don't need to get any more data, just wrap the value
517 $this_field.append('<textarea>'+data_value+'</textarea>');
518 $this_field.append('<span class="original_data">'+data_value+'</span>');
519 $(".original_data").hide();
521 else if($this_field.is('.truncated, .transformed')) {
523 //handle truncated/transformed values values
526 * @var sql_query String containing the SQL query used to retrieve value of truncated/transformed data
528 var sql_query = 'SELECT ' + field_name + ' FROM ' + window.parent.table + ' WHERE ' + where_clause;
530 // Make the Ajax call and get the data, wrap it and insert it
532 'token' : window.parent.token,
533 'db' : window.parent.db,
534 'ajax_request' : true,
535 'sql_query' : sql_query,
538 if(data.success == true) {
539 $this_field.append('<textarea>'+data.value+'</textarea>');
540 $this_field.append('<span class="original_data">'+data_value+'</span>');
541 $(".original_data").hide();
544 PMA_ajaxShowMessage(data.error);
548 else if($this_field.is('.relation')) {
553 * @var post_params Object containing parameters for the POST request
556 'ajax_request' : true,
557 'get_relational_values' : true,
558 'db' : window.parent.db,
559 'table' : window.parent.table,
560 'column' : field_name,
561 'token' : window.parent.token,
562 'curr_value' : relation_curr_value
565 $.post('sql.php', post_params, function(data) {
566 $this_field.append(data.dropdown);
567 $this_field.append('<span class="original_data">'+data_value+'</span>');
568 $(".original_data").hide();
571 else if($this_field.is('.enum')) {
576 * @var post_params Object containing parameters for the POST request
579 'ajax_request' : true,
580 'get_enum_values' : true,
581 'db' : window.parent.db,
582 'table' : window.parent.table,
583 'column' : field_name,
584 'token' : window.parent.token,
585 'curr_value' : enum_curr_value
588 $.post('sql.php', post_params, function(data) {
589 $this_field.append(data.dropdown);
590 $this_field.append('<span class="original_data">'+data_value+'</span>');
591 $(".original_data").hide();
594 else if($this_field.is('.null')) {
596 $this_field.append('<textarea></textarea>');
597 $this_field.append('<span class="original_data">NULL</span>');
598 $(".original_data").hide();
601 }) // End On click, replace the current field with an input/textarea
604 * After editing, clicking again should post data
607 * @name inline_edit_save
608 * @see PMA_ajaxShowMessage()
609 * @see getFieldName()
611 $(".inline_edit_active").live('click', function(event) {
613 event.preventDefault();
616 * @var $this_td Object referring to the td containing the
617 * "Inline Edit" link that was clicked to save the row that is
621 var $this_td = $(this);
623 var $test_element = ''; // to test the presence of a element
625 // Initialize variables
626 if(disp_mode == 'vertical') {
628 * @var this_td_index Index of the current <td> in the parent <tr>
629 * Current <td> is the inline edit anchor.
631 var this_td_index = $this_td.index();
633 * @var $input_siblings Object referring to all inline editable events from same row
635 var $input_siblings = $this_td.parents('tbody').find('tr').find('.inline_edit:nth('+this_td_index+')');
637 * @var where_clause String containing the WHERE clause to select this row
639 var where_clause = $this_td.parents('tbody').find('tr').find('.where_clause:nth('+this_td_index+')').val();
642 var $input_siblings = $this_td.parent('tr').find('.inline_edit');
643 var where_clause = $this_td.parent('tr').find('.where_clause').val();
647 * @var nonunique Boolean, whether this row is unique or not
649 if($this_td.is('.nonunique')) {
656 // Collect values of all fields to submit, we don't know which changed
658 * @var relation_fields Array containing the name/value pairs of relational fields
660 var relation_fields = {};
662 * @var transform_fields Array containing the name/value pairs for transformed fields
664 var transform_fields = {};
666 * @var transformation_fields Boolean, if there are any transformed fields in this row
668 var transformation_fields = false;
671 * @var sql_query String containing the SQL query to update this row
673 var sql_query = 'UPDATE ' + window.parent.table + ' SET ';
675 $input_siblings.each(function() {
678 * @var this_field Object referring to this field (<td>)
680 var $this_field = $(this);
682 * @var field_name String containing the name of this field.
683 * @see getFieldName()
685 var field_name = getFieldName($this_field, disp_mode);
688 * @var this_field_params Array temporary storage for the name/value of current field
690 var this_field_params = {};
692 if($this_field.is('.transformed')) {
693 transformation_fields = true;
696 * @var is_null String capturing whether 'checkbox_null_<field_name>_<row_index>' is checked.
698 var is_null = $this_field.find('input:checkbox').is(':checked');
702 sql_query += ' ' + field_name + "=NULL , ";
704 if($this_field.is(":not(.relation, .enum)")) {
705 this_field_params[field_name] = $this_field.find('textarea').val();
706 if($this_field.is('.transformed')) {
707 $.extend(transform_fields, this_field_params);
711 // results from a drop-down
712 $test_element = $this_field.find('select');
713 if ($test_element.length != 0) {
714 this_field_params[field_name] = $test_element.val();
717 // results from Browse foreign value
718 $test_element = $this_field.find('span.curr_value');
719 if ($test_element.length != 0) {
720 this_field_params[field_name] = $test_element.text();
723 if($this_field.is('.relation')) {
724 $.extend(relation_fields, this_field_params);
728 sql_query += ' ' + field_name + "='" + this_field_params[field_name].replace(/'/g, "''") + "' , ";
732 //Remove the last ',' appended in the above loop
733 sql_query = sql_query.replace(/,\s$/, '');
734 sql_query += ' WHERE ' + PMA_urldecode(where_clause);
737 * @var rel_fields_list String, url encoded representation of {@link relations_fields}
739 var rel_fields_list = $.param(relation_fields);
742 * @var transform_fields_list String, url encoded representation of {@link transform_fields}
744 var transform_fields_list = $.param(transform_fields);
746 // Make the Ajax post after setting all parameters
748 * @var post_params Object containing parameters for the POST request
750 var post_params = {'ajax_request' : true,
751 'sql_query' : sql_query,
752 'disp_direction' : disp_mode,
753 'token' : window.parent.token,
754 'db' : window.parent.db,
755 'table' : window.parent.table,
756 'clause_is_unique' : nonunique,
757 'where_clause' : where_clause,
758 'rel_fields_list' : rel_fields_list,
759 'do_transformations' : transformation_fields,
760 'transform_fields_list' : transform_fields_list,
762 'submit_type' : 'save'
765 $.post('tbl_replace.php', post_params, function(data) {
766 if(data.success == true) {
767 PMA_ajaxShowMessage(data.message);
768 $this_td.removeClass('inline_edit_active').addClass('inline_edit_anchor');
770 $input_siblings.each(function() {
771 // Inline edit post has been successful.
772 $this_sibling = $(this);
774 var is_null = $this_sibling.find('input:checkbox').is(':checked');
776 $this_sibling.html('NULL');
777 $this_sibling.addClass('null');
779 $this_sibling.removeClass('null');
780 if($this_sibling.is(':not(.relation, .enum)')) {
782 * @var new_html String containing value of the data field after edit
784 var new_html = $this_sibling.find('textarea').val();
786 if($this_sibling.is('.transformed')) {
787 var field_name = getFieldName($this_sibling, disp_mode);
788 $.each(data.transformations, function(key, value) {
789 if(key == field_name) {
790 if($this_sibling.is('.text_plain, .application_octetstream')) {
795 var new_value = $this_sibling.find('textarea').val();
796 new_html = $(value).append(new_value);
806 $test_element = $this_sibling.find('select');
807 if ($test_element.length != 0) {
808 new_value = $test_element.val();
811 $test_element = $this_sibling.find('span.curr_value');
812 if ($test_element.length != 0) {
813 new_value = $test_element.text();
816 if($this_sibling.is('.relation')) {
817 var field_name = getFieldName($this_sibling, disp_mode);
818 $.each(data.relations, function(key, value) {
819 if(key == field_name) {
820 new_html = $(value).append(new_value);
825 if($this_sibling.is('.enum')) {
826 new_html = new_value;
829 $this_sibling.html(new_html);
834 PMA_ajaxShowMessage(data.error);
837 }) // End After editing, clicking again should post data
838 }, 'top.frame_content') // end $(document).ready()
841 * Starting from some th, change the class of all td under it
843 function PMA_changeClassForColumn($this_th, newclass) {
844 // index 0 is the th containing the big T
845 var th_index = $this_th.index();
846 // .eq() is zero-based
848 var $tr_with_data = $this_th.closest('table').find('tbody tr ').has('td.data');
849 $tr_with_data.each(function() {
850 $(this).find('td.data:eq('+th_index+')').toggleClass(newclass);
854 $(document).ready(function() {
856 $('.browse_foreign').live('click', function(e) {
858 window.open(this.href, 'foreigners', 'width=640,height=240,scrollbars=yes,resizable=yes');
860 $anchor.addClass('browse_foreign_clicked');
865 * vertical column highlighting in horizontal mode when hovering over the column header
867 $('.column_heading').live('hover', function() {
868 PMA_changeClassForColumn($(this), 'hover');
872 * vertical column marking in horizontal mode when clicking the column header
874 $('.column_heading').live('click', function() {
875 PMA_changeClassForColumn($(this), 'marked');