diff --git a/include/class.orm.php b/include/class.orm.php index 2f553e470282773e881b685c63ca36c6bcf247d5..9714a58c6b5e6b6894db3ce8d059be87c45d5a5b 100644 --- a/include/class.orm.php +++ b/include/class.orm.php @@ -917,6 +917,9 @@ class QuerySet implements IteratorAggregate, ArrayAccess, Serializable, Countabl const LOCK_EXCLUSIVE = 1; const LOCK_SHARED = 2; + const ASC = 'ASC'; + const DESC = 'DESC'; + var $compiler = 'MySqlCompiler'; var $iterator = 'ModelInstanceManager'; @@ -976,10 +979,21 @@ class QuerySet implements IteratorAggregate, ArrayAccess, Serializable, Countabl $this->defer[$f] = true; return $this; } + function order_by($order, $direction=false) { + $args = func_get_args(); + if (in_array($direction, array(self::ASC, self::DESC))) { + $args = array($args[0]); + } + else + $direction = false; - function order_by($order) { - $this->ordering = array_merge($this->ordering, - is_array($order) ? $order : func_get_args()); + $new = is_array($order) ? $order : $args; + if ($direction) { + foreach ($new as $i=>$x) { + $new[$i] = array($x, $direction); + } + } + $this->ordering = array_merge($this->ordering, $new); return $this; } function getSortFields() { @@ -2370,6 +2384,9 @@ class MySqlCompiler extends SqlCompiler { $orders = array(); foreach ($columns as $sort) { $dir = 'ASC'; + if (is_array($sort)) { + list($sort, $dir) = $sort; + } if ($sort instanceof SqlFunction) { $field = $sort->toSql($this, $model); } diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php index 0978acfdd3444a135ab03a5c7cf0613b589b7b80..063feee30cd8e662fe4e1b480c65db634c7bc6c1 100644 --- a/include/staff/tickets.inc.php +++ b/include/staff/tickets.inc.php @@ -20,8 +20,8 @@ $sort_options = array( 'priority,updated' => __('Priority + Most Recently Updated'), 'updated' => __('Most Recently Updated'), 'priority,created' => __('Priority + Most Recently Created'), - 'due' => __('Due Soon'), - 'priority,due' => __('Priority + Due Soon'), + 'due' => __('Due Date'), + 'priority,due' => __('Priority + Due Date'), 'number' => __('Ticket Number'), 'answered' => __('Most Recently Answered'), 'closed' => __('Most Recently Closed'), @@ -196,73 +196,78 @@ $tickets = $pageNav->paginate($tickets); $queue_sort_key = sprintf(':Q%s:%s:sort', ObjectModel::OBJECT_TYPE_TICKET, $queue_name); if (isset($_GET['sort'])) { - $_SESSION[$queue_sort_key] = $_GET['sort']; + $_SESSION[$queue_sort_key] = array($_GET['sort'], $_GET['dir']); } elseif (!isset($_SESSION[$queue_sort_key])) { - $_SESSION[$queue_sort_key] = $queue_sort_options[0]; + $_SESSION[$queue_sort_key] = array($queue_sort_options[0], 0); } -switch ($_SESSION[$queue_sort_key]) { +list($sort_cols, $sort_dir) = $_SESSION[$queue_sort_key]; +$orm_dir = $sort_dir ? QuerySet::ASC : QuerySet::DESC; +$orm_dir_r = $sort_dir ? QuerySet::DESC : QuerySet::ASC; +switch ($sort_cols) { case 'number': $tickets->extra(array( - 'order_by'=>array(SqlExpression::times(new SqlField('number'), 1)) + 'order_by'=>array( + array(SqlExpression::times(new SqlField('number'), 1), $orm_dir) + ) )); break; case 'priority,created': - $tickets->order_by('cdata__:priority__priority_urgency'); + $tickets->order_by(($sort_dir ? '-' : '') . 'cdata__:priority__priority_urgency'); // Fall through to columns for `created` case 'created': $date_header = __('Date Created'); $date_col = 'created'; $tickets->values('created'); - $tickets->order_by('-created'); + $tickets->order_by($sort_dir ? 'created' : '-created'); break; case 'priority,due': - $tickets->order_by('cdata__:priority__priority_urgency'); + $tickets->order_by('cdata__:priority__priority_urgency', $orm_dir_r); // Fall through to add in due date filter case 'due': $date_header = __('Due Date'); $date_col = 'est_duedate'; $tickets->values('est_duedate'); - $tickets->order_by(SqlFunction::COALESCE(new SqlField('est_duedate'), 'zzz')); + $tickets->order_by(SqlFunction::COALESCE(new SqlField('est_duedate'), 'zzz'), $orm_dir_r); break; case 'closed': $date_header = __('Date Closed'); $date_col = 'closed'; $tickets->values('closed'); - $tickets->order_by('-closed'); + $tickets->order_by('closed', $orm_dir); break; case 'answered': $date_header = __('Last Response'); $date_col = 'thread__lastresponse'; $date_fallback = '<em class="faded">'.__('unanswered').'</em>'; - $tickets->order_by('-thread__lastresponse'); + $tickets->order_by('thread__lastresponse', $orm_dir); $tickets->values('thread__lastresponse'); break; case 'hot': - $tickets->order_by('-thread_count'); + $tickets->order_by('thread_count', $orm_dir); $tickets->annotate(array( 'thread_count' => SqlAggregate::COUNT('thread__entries'), )); break; case 'relevance': - $tickets->order_by(new SqlCode('relevance')); + $tickets->order_by(new SqlCode('relevance'), $orm_dir); break; default: case 'priority,updated': - $tickets->order_by('cdata__:priority__priority_urgency'); + $tickets->order_by('cdata__:priority__priority_urgency', $orm_dir_r); // Fall through for columns defined for `updated` case 'updated': $date_header = __('Last Updated'); $date_col = 'lastupdate'; - $tickets->order_by('-lastupdate'); + $tickets->order_by('lastupdate', $orm_dir); break; } @@ -312,23 +317,33 @@ $_SESSION[':Q:tickets'] = $orig_tickets; <div id='basic_search'> <div class="pull-right" style="height:25px"> <span class="valign-helper"></span> - <span class="action-button muted" data-dropdown="#sort-dropdown"> + <span class="action-button muted" data-dropdown="#sort-dropdown" data-toggle="tooltip" title="<?php echo $sort_options[$sort_cols]; ?>"> <i class="icon-caret-down pull-right"></i> - <span><i class="icon-sort-by-attributes-alt"></i> <?php echo __('Sort');?></span> + <span><i class="icon-sort-by-attributes-alt <?php if ($sort_dir) echo 'icon-flip-vertical'; ?>"></i> <?php echo __('Sort');?></span> </span> <div id="sort-dropdown" class="action-dropdown anchor-right" - onclick="javascript: $.pjax({ - url:'?' + addSearchParam('sort', $(event.target).data('mode')), + onclick="javascript: + var query = addSearchParam({'sort': $(event.target).data('mode'), 'dir': $(event.target).data('dir')}); + $.pjax({ + url: '?' + query, timeout: 2000, container: '#pjax-container'});"> <ul class="bleed-left"> <?php foreach ($queue_sort_options as $mode) { $desc = $sort_options[$mode]; -$selected = $mode == $_SESSION[$queue_sort_key]; ?> - <li <?php if ($selected) echo 'class="active"'; ?>> - <a href="#" data-mode="<?php echo $mode; ?>"><i class="icon-fixed-width <?php - if ($selected) echo 'icon-hand-right'; - ?>"></i> <?php echo Format::htmlchars($desc); ?></a> +$icon = ''; +$dir = '0'; +$selected = $sort_cols == $mode; ?> + <li <?php +if ($selected) { + echo 'class="active"'; + $dir = ($sort_dir == '1') ? '0' : '1'; // Flip the direction + $icon = ($sort_dir == '1') ? 'icon-hand-up' : 'icon-hand-down'; +} +?>> + <a href="#" data-mode="<?php echo $mode; ?>" data-dir="<?php echo $dir; ?>"> + <i class="icon-fixed-width <?php echo $icon; ?>" + ></i> <?php echo Format::htmlchars($desc); ?></a> </li> <?php } ?> </div> @@ -584,6 +599,7 @@ $(function() { } return false; }); + $('[data-toggle=tooltip]').tooltip(); }); </script> diff --git a/scp/js/scp.js b/scp/js/scp.js index 58ced5d337bb21546eecce6c47824f9a312f217c..07135b2ba6066a465dd23fe58e6e0ea980f01102 100644 --- a/scp/js/scp.js +++ b/scp/js/scp.js @@ -1158,23 +1158,16 @@ function __(s) { } // Thanks, http://stackoverflow.com/a/487049 -function addSearchParam(key, value) { - key = encodeURI(key); value = encodeURI(value); - +function addSearchParam(data) { var kvp = document.location.search.substr(1).split('&'); - var i=kvp.length; var x; + var i=kvp.length, x, params = {}; while (i--) { x = kvp[i].split('='); - if (x[0]==key) { - x[1] = value; - kvp[i] = x.join('='); - break; - } + params[decodeURIComponent(x[0])] = decodeURIComponent(x[1]); } - if(i<0) {kvp[kvp.length] = [key,value].join('=');} //this will reload the page, it's likely better to store this until finished - return kvp.join('&'); + return $.param($.extend(params, data)); } // Periodically adjust relative times