Multicolumn sorting feature request(1507)

This patch completely implements the feature requested and it modifies 4
existing files and adds one extra Javascript file. The existing system for
one column sorting has been refactored to be used for multicolumn sort.

Files messages.php and makegrid.js where modified to add the tool tip.

File sql.php was modified to add the import statement for the newly added JS
file.

The newly added columndelete.js is responsible for shift deleting the columns.
It gets the url of the link that was shift clicked and removes the name of the
column which we want to remove from the order by clause.

Finally, most of the crux of this feature request is implemented through
changes to the DisplayResult.class.php. 4 methods of this class have been
refactored to accomodate the required change. getsortparams() which process
the order by clause text remains the same, just the output parameters have
been turned into a array for each order by clause column. isInSort() has been
modified to check multiple order by clauses. The smart ordering feature in
to form a new function makeURL, which makes the url for multiple sorted table's
header's button's targets.

Signed-off-by: Aditya Sastry <ganeshaditya1@gmail.com>
This commit is contained in:
Aditya Sastry 2014-03-19 22:10:32 +05:30
parent 69e2d84b51
commit a05ab9640b
6 changed files with 278 additions and 121 deletions

68
js/columndelete.js Normal file
View File

@ -0,0 +1,68 @@
/* vim: set expandtab sw=4 ts=4 sts=4: */
/**
* @fileoverview Implements the shiftkey + click remove column
* from order by clause funcationality
* @name columndelete
*
* @requires jQuery
*/
function captureURL(url)
{
var URL = {};
url = '' + url;
// Exclude the url part till HTTP
url = url.substr(url.search("sql.php"), url.length);
// The url part between ORDER BY and &session_max_rows needs to be replaced.
URL['head'] = url.substr(0, url.indexOf('ORDER+BY') + 9);
URL['tail'] = url.substr(url.indexOf("&session_max_rows"), url.length);
return URL;
}
/**
* This function is for navigating to the generated URL
*
* @param object target HTMLAnchor element
* @param object parent HTMLDom Object
*/
function redirect(target, parent)
{
var URL = captureURL(target);
var begin = target.indexOf('ORDER+BY') + 8;
var end = target.indexOf('&session_max_rows');
// get the names of the columns involved
var between_part = target.substr(begin, end-begin);
var columns = between_part.split('%2C+');
// If the given column is not part of the order clause exit from this function
var index = parent.find('small').length ? parent.find('small').text() : '';
if (index == ''){
return;
}
// Remove the current clicked column
columns.splice(index-1, 1);
// If all the columns have been removed dont submit a query with nothing
// After order by clause.
if (columns.length == 0){
var head = URL['head'];
head = head.slice(0,head.indexOf('ORDER+BY'));
URL['head'] = head;
}
var middle_part = columns.join('%2C+');
url = URL['head'] + middle_part + URL['tail'];
window.location.replace(url);
}
AJAX.registerOnload('keyhandler.js', function () {
$("th.draggable.column_heading.pointer.marker a").live('click', function (event) {
if (event.shiftKey) {
event.preventDefault();
redirect($(this).attr("href"), $(this).parent());
}
});
});
AJAX.registerTeardown('keyhandler.js', function () {
$("th.draggable.column_heading.pointer.marker a").die('click');
});

View File

@ -437,6 +437,10 @@ function PMA_makegrid(t, enableResize, enableReorder, enableVisib, enableGridEdi
if (g.showSortHint && g.sortHint) {
text += text.length > 0 ? '<br />' : '';
text += g.sortHint;
}
if (g.showRemColHint && g.strRemColHint) {
text += text.length > 0 ? '<br />' : '';
text += g.strRemColHint;
}
if (g.showMarkHint && g.markHint &&
!g.showSortHint // we do not show mark hint, when sort hint is shown
@ -1784,6 +1788,7 @@ function PMA_makegrid(t, enableResize, enableReorder, enableVisib, enableGridEdi
// assign the hints
g.sortHint = PMA_messages.strSortHint;
g.strRemColHint = PMA_messages.strRemColHint;
g.markHint = PMA_messages.strColMarkHint;
g.copyHint = PMA_messages.strColNameCopyHint;
@ -1837,12 +1842,14 @@ function PMA_makegrid(t, enableResize, enableReorder, enableVisib, enableGridEdi
$(t).find('th.draggable a')
.mouseenter(function (e) {
g.showSortHint = true;
g.showRemColHint = true;
$(t).find("th.draggable").tooltip("option", {
content: g.updateHint()
});
})
.mouseleave(function (e) {
g.showSortHint = false;
g.showSortHint = false;
g.showRemColHint = false;
$(t).find("th.draggable").tooltip("option", {
content: g.updateHint()
});

View File

@ -335,6 +335,7 @@ $js_messages['strCellEditHint'] = __('Press escape to cancel editing.');
$js_messages['strSaveCellWarning'] = __('You have edited some data and they have not been saved. Are you sure you want to leave this page before saving the data?');
$js_messages['strColOrderHint'] = __('Drag to reorder.');
$js_messages['strSortHint'] = __('Click to sort.');
$js_messages['strRemColHint'] = __('Hold shift and click to remove column from order by clause.');
$js_messages['strColMarkHint'] = __('Click to mark/unmark.');
$js_messages['strColNameCopyHint'] = __('Double-click to copy column name.');
$js_messages['strColVisibHint'] = __(

View File

@ -1140,7 +1140,7 @@ class PMA_DisplayResults
$sort_expression_nodirection, $i, $unsorted_sql_query,
$session_max_rows, $direction, $comments,
$sort_direction, $directionCondition, $col_visib,
$col_visib[$j], $condition_field
$col_visib[$j]
);
$table_headers_html .= $sorted_header_html;
@ -1816,14 +1816,14 @@ class PMA_DisplayResults
* Prepare parameters and html for sorted table header fields
*
* @param array $fields_meta set of field properties
* @param string $sort_expression sort expression
* @param string $sort_expression_nodirection sort expression without direction
* @param array $sort_expression sort expression
* @param array $sort_expression_nodirection sort expression without direction
* @param integer $column_index the index of the column
* @param string $unsorted_sql_query the unsorted sql query
* @param integer $session_max_rows maximum rows resulted by sql
* @param string $direction the display direction
* @param array $direction the display direction
* @param string $comments comment for row
* @param string $sort_direction sort direction
* @param array $sort_direction sort direction
* @param boolean $directionCondition display direction horizontal
* or horizontalflipped
* @param boolean $col_visib column is visible(false)
@ -1837,12 +1837,12 @@ class PMA_DisplayResults
* @access private
*
* @see _getTableHeaders()
*/
*/
private function _getOrderLinkAndSortedHeaderHtml(
$fields_meta, $sort_expression, $sort_expression_nodirection,
$column_index, $unsorted_sql_query, $session_max_rows, $direction,
$comments, $sort_direction, $directionCondition, $col_visib,
$col_visib_j, $condition_field
$col_visib_j, $condition_field, $is_last_field
) {
$sorted_header_html = '';
@ -1860,50 +1860,14 @@ class PMA_DisplayResults
) . '.'
: '';
// Checks if the current column is used to sort the
// results
// the orgname member does not exist for all MySQL versions
// but if found, it's the one on which to sort
$name_to_use_in_sort = $fields_meta->name;
$is_orgname = false;
if (isset($fields_meta->orgname)
&& strlen($fields_meta->orgname)
) {
$name_to_use_in_sort = $fields_meta->orgname;
$is_orgname = true;
}
// $name_to_use_in_sort might contain a space due to
// formatting of function expressions like "COUNT(name )"
// so we remove the space in this situation
$name_to_use_in_sort = str_replace(' )', ')', $name_to_use_in_sort);
$is_in_sort = $this->_isInSorted(
$sort_expression, $sort_expression_nodirection,
$sort_tbl, $name_to_use_in_sort
);
// Check the field name for a bracket.
// If it contains one, it's probably a function column
// like 'COUNT(`field`)'
// It still might be a column name of a view. See bug #3383711
// Check is_orgname.
if ((strpos($name_to_use_in_sort, '(') !== false) && ! $is_orgname) {
$sort_order = "\n" . 'ORDER BY ' . $name_to_use_in_sort . ' ';
} else {
$sort_order = "\n" . 'ORDER BY ' . $sort_tbl
. PMA_Util::backquote(
$name_to_use_in_sort
) . ' ';
}
unset($name_to_use_in_sort);
unset($is_orgname);
// Do define the sorting URL
list($sort_order, $order_img) = $this->_getSortingUrlParams(
$is_in_sort, $sort_direction, $fields_meta,
$sort_order, $column_index
// Generates the orderby clause part of the query which is part
// of URL
list($sort_order, $order_img) = $this->makeUrl(
$sort_expression, $sort_expression_nodirection,
$sort_tbl, $name_to_use_in_sort, $sort_direction, $fields_meta,
$column_index
);
if (preg_match(
@ -1931,23 +1895,124 @@ class PMA_DisplayResults
$fields_meta, $order_url
);
if ($directionCondition) {
$sorted_header_html .= $this->_getDraggableClassForSortableColumns(
$col_visib, $col_visib_j, $condition_field, $direction,
$fields_meta, $order_link, $comments
);
}
$sorted_header_html .= $this->_getDraggableClassForSortableColumns(
$col_visib, $col_visib_j, $condition_field, $direction,
$fields_meta, $order_link, $comments
);
return array($order_link, $sorted_header_html);
} // end of the '_getOrderLinkAndSortedHeaderHtml()' function
/**
* Prepare parameters and html for sorted table header fields
*
* @param array $fields_meta set of field properties
* @param array $sort_expression sort expression
* @param array $sort_expression_nodirection sort expression without direction
* @param array $sort_direction sort direction
* @param string $name_to_use_in_sort The current column under consideration
* @param string $sort_tbl The name of the table to which the
* current column belongs to
* @param integer $column_index The index number to the current column
* @return array 2 element array - $order_link, $sorted_header_html
*
* @access private
*
* @see _getTableHeaders()
*/
private function makeURL(
$sort_expression, $sort_expression_nodirection,
$sort_tbl, $name_to_use_in_sort, $sort_direction, $fields_meta,
$column_index
){
$sort_order = "";
// Check if the current column is in the order by clause
$is_in_sort = $this->_isInSorted(
$sort_expression, $sort_expression_nodirection,
$sort_tbl, $name_to_use_in_sort
);
$current_name = $name_to_use_in_sort;
if ($sort_expression_nodirection[0] == '' || !$is_in_sort){
$special_index = $sort_expression_nodirection[0] == '' ? 0 : count($sort_expression_nodirection);
$sort_expression_nodirection[$special_index] =
PMA_Util::backquote(
$current_name
);
$sort_direction[$special_index] = (preg_match(
'@time|date@i',
$fields_meta->type
)) ? self::DESCENDING_SORT_DIR : self::ASCENDING_SORT_DIR;
}
$sort_expression_nodirection = array_filter($sort_expression_nodirection);
foreach ($sort_expression_nodirection as $index=>$expression){
// check if this is the first clause, if it is then we have to add "order by"
$is_first_clause = ($index == 0);
$name_to_use_in_sort = $expression;
$sort_tbl_new = $sort_tbl;
// Test to detect if the column name is a standard name
// Standard name has the table name prefixed to the column name
$is_standard_name = false;
if (strpos($name_to_use_in_sort, '.') !== false){
$matches = explode('.', $name_to_use_in_sort);
// Matches[0] has the table name
// Matches[1] has the column name
$name_to_use_in_sort = $matches[1];
$sort_tbl_new = $matches[0];
$is_standard_name = true;
}
// $name_to_use_in_sort might contain a space due to
// formatting of function expressions like "COUNT(name )"
// so we remove the space in this situation
$name_to_use_in_sort = str_replace(' )', ')', $name_to_use_in_sort);
$name_to_use_in_sort = str_replace('`','',$name_to_use_in_sort);
// If this the first column name in the order by clause add
// order by clause to the column name
$query_head = $is_first_clause ? "\nORDER BY " : "";
$tbl = $is_standard_name ? $sort_tbl_new : $sort_tbl;
// Again a check to see if the given column is a aggregate column
if (strpos($name_to_use_in_sort, '(') !== false) {
$sort_order .= $query_head . $name_to_use_in_sort . ' ' ;
} else {
$sort_order .= $query_head . $sort_tbl_new . "."
. PMA_Util::backquote(
$name_to_use_in_sort
) . ' ' ;
}
// For a special case where the code generates two dots between
// column name and table name.
$sort_order = preg_replace("/\.\./", ".", $sort_order);
// Incase the current column name is in the order by clause
// We need to generate the arrow button and related html
if($current_name == $name_to_use_in_sort && $is_in_sort){
list($sort_order, $order_img) = $this->_getSortingUrlParams(
$is_in_sort, $sort_direction, $fields_meta,
$sort_order, $column_index, $sort_tbl_new, $index
);
}
else{
$sort_order .= strtoupper($sort_direction[$index]);
}
// Separte columns by a comma
$sort_order .= ", ";
unset($name_to_use_in_sort);
}
// remove the comma from the last column name in the newly
// constructed clause
$sort_order = substr($sort_order,0,strlen($sort_order)-2);
return array($sort_order, $order_img);
}
/**
* Check whether the column is sorted
*
* @param string $sort_expression sort expression
* @param string $sort_expression_nodirection sort expression without direction
* @param array $sort_expression sort expression
* @param array $sort_expression_nodirection sort expression without direction
* @param string $sort_tbl the table name
* @param string $name_to_use_in_sort the sorting column name
*
@ -1961,8 +2026,23 @@ class PMA_DisplayResults
$sort_expression, $sort_expression_nodirection, $sort_tbl,
$name_to_use_in_sort
) {
if (empty($sort_expression)) {
$index_in_expression = 0;
foreach ($sort_expression_nodirection as $index => $clause) {
if (strpos($clause,'.') !== false) {
$fragments = explode('.', $clause);
$clause2 = $fragments[0] . "." . str_replace('`', ``, $fragments[1]);
}
else {
$clause2 = $sort_tbl . str_replace('`', ``, $clause);
}
if ($clause2 === $sort_tbl . $name_to_use_in_sort) {
$index_in_expression = $index;
break;
}
}
if (empty($sort_expression[$index_in_expression])) {
$is_in_sort = false;
} else {
// Field name may be preceded by a space, or any number
@ -1975,13 +2055,13 @@ class PMA_DisplayResults
// SELECT p.*, FROM_UNIXTIME(p.temps) FROM mytable AS p
// (and try clicking on each column's header twice)
if (! empty($sort_tbl)
&& strpos($sort_expression_nodirection, $sort_tbl) === false
&& strpos($sort_expression_nodirection, '(') === false
&& strpos($sort_expression_nodirection[$index_in_expression], $sort_tbl) === false
&& strpos($sort_expression_nodirection[$index_in_expression], '(') === false
) {
$new_sort_expression_nodirection = $sort_tbl
. $sort_expression_nodirection;
. $sort_expression_nodirection[$index_in_expression];
} else {
$new_sort_expression_nodirection = $sort_expression_nodirection;
$new_sort_expression_nodirection = $sort_expression_nodirection[$index_in_expression];
}
//Back quotes are removed in next comparison, so remove them from value
@ -1992,7 +2072,7 @@ class PMA_DisplayResults
$sort_name = str_replace('`', '', $sort_tbl) . $name_to_use_in_sort;
if ($sort_name == str_replace('`', '', $new_sort_expression_nodirection)
|| $sort_name == str_replace('`', '', $sort_expression_nodirection)
|| $sort_name == str_replace('`', '', $sort_expression_nodirection[$index_in_expression])
) {
$is_in_sort = true;
}
@ -2007,9 +2087,9 @@ class PMA_DisplayResults
* Get sort url paramaeters - sort order and order image
*
* @param boolean $is_in_sort the column sorted or not
* @param string $sort_direction the sort direction
* @param array $sort_direction the sort direction
* @param array $fields_meta set of field properties
* @param string $sort_order the sorting order
* @param array $sort_order the sorting order
* @param integer $column_index the index of the column
*
* @return array 2 element array - $sort_order, $order_img
@ -2019,26 +2099,13 @@ class PMA_DisplayResults
* @see _getTableHeaders()
*/
private function _getSortingUrlParams(
$is_in_sort, $sort_direction, $fields_meta, $sort_order, $column_index
) {
if (! $is_in_sort) {
// patch #455484 ("Smart" order)
$GLOBALS['cfg']['Order'] = strtoupper($GLOBALS['cfg']['Order']);
if ($GLOBALS['cfg']['Order'] === self::SMART_SORT_ORDER) {
$sort_order .= (preg_match(
'@time|date@i',
$fields_meta->type
)) ? self::DESCENDING_SORT_DIR : self::ASCENDING_SORT_DIR;
} else {
$sort_order .= $GLOBALS['cfg']['Order'];
}
$order_img = '';
} elseif ($sort_direction == self::DESCENDING_SORT_DIR) {
$is_in_sort, $sort_direction, $fields_meta, $sort_order, $column_index, $sort_tbl, $index
) {
$index2 = $index + 1;
if (strtoupper(trim($sort_direction[$index])) == self::DESCENDING_SORT_DIR) {
$sort_number = "<small>".$index2."</small>";
$sort_order .= ' ASC';
$order_img = ' ' . PMA_Util::getImage(
's_desc.png', __('Descending'),
@ -2048,20 +2115,22 @@ class PMA_DisplayResults
$order_img .= ' ' . PMA_Util::getImage(
's_asc.png', __('Ascending'),
array('class' => "soimg$column_index hide", 'title' => '')
);
) . $sort_number;
} else {
$sort_number = "<small>".$index2."</small>";
$sort_order .= ' DESC';
$order_img = ' ' . PMA_Util::getImage(
's_asc.png', __('Ascending'),
array('class' => "soimg$column_index", 'title' => '')
);
$order_img .= ' ' . PMA_Util::getImage(
$order_img .= ' ' . PMA_Util::getImage(
's_desc.png', __('Descending'),
array('class' => "soimg$column_index hide", 'title' => '')
);
) . $sort_number;
}
return array($sort_order, $order_img);
@ -2088,8 +2157,7 @@ class PMA_DisplayResults
$order_img, $col_index, $direction, $fields_meta, $order_url
) {
$order_link_params = array();
$order_link_params = array();
if (isset($order_img) && ($order_img != '')) {
if (strstr($order_img, 'asc')) {
$order_link_params['onmouseover'] = "$('.soimg$col_index').toggle()";
@ -4567,16 +4635,16 @@ class PMA_DisplayResults
// 1.3 Find the sort expression
// we need $sort_expression and $sort_expression_nodirection
// even if there are many table references
list($sort_expression, $sort_expression_nodirection, $sort_direction)
list($sort_expression, $sort_expression_nodirection, $sort_direction,$number_of_columns)
= $this->_getSortParams($analyzed_sql[0]['order_by_clause']);
// 1.4 Prepares display of first and last value of the sorted column
$sorted_column_message = $this->_getSortedColumnMessage(
$dt_result, $sort_expression_nodirection
);
for ( $i = 0; $i < $number_of_columns; $i++ ){
$sorted_column_message = $this->_getSortedColumnMessage(
$dt_result, $sort_expression_nodirection[$i]
);
}
// 2. ----- Prepare to display the top of the page -----
@ -4759,35 +4827,39 @@ class PMA_DisplayResults
* @see getTable()
*/
private function _getSortParams($order_by_clause)
{
{
if (! empty($order_by_clause)) {
if (! empty($order_by_clause)) {
// Each order by clause is assumed to be delimited by a comma
// A typical order by clause would be order by column1 asc, column2 desc
// The following line counts the number of columns in order by clause
$matches = explode(',', $order_by_clause);
// Iterate over each column in order by clause
foreach ($matches as $index=>$order_by_clause2) {
$sort_expression = trim(
str_replace(' ', ' ', $order_by_clause)
);
/**
* Get rid of ASC|DESC
*/
preg_match(
'@(.*)([[:space:]]*(ASC|DESC))@si', $sort_expression, $matches
);
$sort_expression_nodirection = isset($matches[1])
? trim($matches[1])
: $sort_expression;
$sort_direction = isset($matches[2]) ? trim($matches[2]) : '';
unset($matches);
$sort_expression[$index] = trim(
str_replace(' ', ' ', $order_by_clause2)
);
/**
* Get rid of ASC|DESC
*/
preg_match(
'@(.*)([[:space:]]*(ASC|DESC))@si', $sort_expression[$index], $matches
);
$sort_expression_nodirection[$index] = isset($matches[1])
? trim($matches[1])
: $sort_expression[$index];
$sort_direction[$index] = isset($matches[2]) ? trim($matches[2]) : '';
}
} else {
$sort_expression = $sort_expression_nodirection = $sort_direction = '';
$sort_expression[0] = $sort_expression_nodirection[0] = $sort_direction[0] = '';
}
return array($sort_expression, $sort_expression_nodirection,
$sort_direction
);
} // end of the '_getSortParams()' function

View File

@ -26,6 +26,7 @@ $scripts->addFile('jquery/jquery-ui-timepicker-addon.js');
$scripts->addFile('tbl_change.js');
$scripts->addFile('indexes.js');
$scripts->addFile('gis_data_editor.js');
$scripts->addFile('columndelete.js');
/**
* Set ajax_reload in the response if it was already set

View File

@ -723,13 +723,21 @@ class PMA_DisplayResults_Test extends PHPUnit_Framework_TestCase
public function dataProviderForGetSortParams()
{
return array(
array('', array('', '', '')),
array('', array(array(''), array(''), array(''))),
array(
'`a_sales`.`customer_id` ASC',
array(
'`a_sales`.`customer_id` ASC',
'`a_sales`.`customer_id`',
'ASC'
array('`a_sales`.`customer_id` ASC'),
array('`a_sales`.`customer_id`'),
array('ASC')
)
),
array(
'`a_sales`.`customer_id` ASC, `b_sales`.`customer_id` DESC',
array(
array('`a_sales`.`customer_id` ASC', '`b_sales`.`customer_id` DESC'),
array('`a_sales`.`customer_id`', '`b_sales`.`customer_id`'),
array('ASC', 'DESC')
)
),
);