Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic sorting and filtering #174

Merged
merged 12 commits into from Nov 9, 2013
62 changes: 42 additions & 20 deletions src/App/Tracker/Model/IssuesModel.php
Expand Up @@ -29,7 +29,7 @@ class IssuesModel extends AbstractTrackerListModel
* @var string
* @since 1.0
*/
protected $context = 'com_tracker.issues';
protected $context = 'tracker.issues';

/**
* Method to get a DatabaseQuery object for retrieving the data set from a database.
Expand Down Expand Up @@ -57,7 +57,7 @@ protected function getListQuery()
$query->where($db->quoteName('a.project_id') . ' = ' . (int) $filter);
}

$filter = $this->state->get('list.filter');
$filter = $this->state->get('filter.search');

if ($filter)
{
Expand All @@ -68,11 +68,18 @@ protected function getListQuery()
$query->where('(' . $db->quoteName('a.title') . ' LIKE ' . $filter . ' OR ' . $db->quoteName('a.description') . ' LIKE ' . $filter . ')');
}

$status = $this->state->get('filter.status');
$filter = $this->state->get('filter.status');

if ($status)
if ($filter)
{
$query->where($db->quoteName('a.status') . ' = ' . (int) $filter);
}

$filter = $this->state->get('filter.priority');

if ($filter)
{
$query->where($db->quoteName('a.status') . ' = ' . (int) $status);
$query->where($db->quoteName('a.priority') . ' = ' . (int) $filter);
}

// TODO: Implement filtering and join to other tables as added
Expand Down Expand Up @@ -102,7 +109,7 @@ protected function getStoreId($id = '')
// Add the list state to the store id.
$id .= ':' . $this->state->get('filter.priority');
$id .= ':' . $this->state->get('filter.status');
$id .= ':' . $this->state->get('list.filter');
$id .= ':' . $this->state->get('filter.search');

return parent::getStoreId($id);
}
Expand All @@ -119,31 +126,46 @@ protected function loadState()
/* @type \JTracker\Application $application */
$application = Container::retrieve('app');

$project = $application->getProject();
$projectId = $application->getProject()->project_id;

$this->state = new Registry;

$input = $application->input;

$this->state->set('filter.project', $project->project_id);

$this->state->set('list.ordering', $input->get('filter_order', 'a.issue_number'));
$this->state->set('filter.project', $projectId);

$listOrder = $input->get('filter_order_Dir', 'DESC');
$sort = $application->getUserStateFromRequest('project_' . $projectId . '.filter.sort', 'filter-sort', 0, 'uint');

if (!in_array(strtoupper($listOrder), array('ASC', 'DESC', '')))
switch ($sort)
{
$listOrder = 'ASC';
case 1:
$this->state->set('list.ordering', 'a.issue_number');
$this->state->set('list.direction', 'ASC');
break;

case 2:
$this->state->set('list.ordering', 'a.modified_date');
$this->state->set('list.direction', 'DESC');
break;

case 3:
$this->state->set('list.ordering', 'a.modified_date');
$this->state->set('list.direction', 'ASC');
break;

default:
$this->state->set('list.ordering', 'a.issue_number');
$this->state->set('list.direction', 'DESC');
}

$this->state->set('list.direction', $listOrder);
$this->state->set('filter.sort', $sort);

$this->state->set('filter.priority', $input->getUint('priority', 3));
$priority = $application->getUserStateFromRequest('project_' . $projectId . '.filter.priority', 'filter-priority', 0, 'uint');
$this->state->set('filter.priority', $priority);

$this->state->set('filter.status', $input->getUint('filter-status'));
$status = $application->getUserStateFromRequest('project_' . $projectId . '.filter.status', 'filter-status', 1, 'uint');
$this->state->set('filter.status', $status);

// Optional filter text
$this->state->set('list.filter', $input->getString('filter-search'));
$search = $application->getUserStateFromRequest('project_' . $projectId . '.filter.search', 'filter-search', '', 'string');
$this->state->set('filter.search', $search);

// List state information.
parent::loadState();
Expand Down
7 changes: 7 additions & 0 deletions src/JTracker/Application.php
Expand Up @@ -291,6 +291,13 @@ public function getSession()
{
$this->newSession = new Session;
$this->newSession->start();

$registry = $this->newSession->get('registry');

if (is_null($registry))
{
$this->newSession->set('registry', new Registry('session'));
}
}

return $this->newSession;
Expand Down
1 change: 1 addition & 0 deletions src/JTracker/Model/AbstractTrackerListModel.php
Expand Up @@ -237,6 +237,7 @@ protected function loadState()
// If the context is set, assume that stateful lists are used.
if ($this->context)
{
/* @type \JTracker\Application $app */
$app = Container::retrieve('app');

$limit = $app->getUserStateFromRequest('global.list.limit', 'limit', $app->get('system.list_limit', 20), 'uint');
Expand Down
32 changes: 32 additions & 0 deletions templates/fields.twig
@@ -0,0 +1,32 @@
{# Copyright (C) 2013 - 2013 Open Source Matters, Inc. All rights reserved. #}
{# GNU General Public License version 2 or later; see LICENSE.txt#}

{% macro input(name, value, type, id, class) %}
<input type="{{ type|default('text') }}" name="{{ name }}" id="{{ id|default(name) }}" value="{{ value|e }}" class="{{ class }}" />
{% endmacro %}

{% macro textarea(name, value, class) %}
<textarea name="{{ name }}" id="{{ id|default(name) }}" class="{{ class }}">{{ value|e }}</textarea>
{% endmacro %}

{% macro label(id, content, class) %}
<label for="{{ id }}" class="{{ class }}">{{ content|e }}</label>
{% endmacro %}

{% macro checkbox(name, value, id, class) %}
<input type="checkbox" name="{{ name }}" id="{{ id|default(name) }}" class="{{ class }}" value="1"{% if value %} checked="checked"{% endif %} />
{% endmacro %}

{% macro select(name, values, value, id, class) %}
<select name="{{ name }}" id="{{ id|default(name) }}" class="{{ class }}">
{% for key, name in values %}
<option value="{{ key }}"{% if value == key %} selected="selected"{% endif %}>{{ name }}</option>
{% endfor %}
</select>
{% endmacro %}

{% macro radio(name, values, value, id, class) %}
{% for key, label in values %}
<label{% if value == key %} class="selected"{% endif %} ><input type="radio" name="{{ name }}" id="{{ id|default(name) ~ key }}" value="{{ key }}"{% if value == key %} checked="checked"{% endif %} /> {{ label }}</label>
{% endfor %}
{% endmacro %}
113 changes: 80 additions & 33 deletions templates/tracker/issues.index.twig
Expand Up @@ -32,23 +32,39 @@
{% endblock %}

{% block content %}
<form action="{{ uri.base.path }}tracker/{{ project.alias }}" method="post" name="adminForm" id="adminForm" class="form-inline form-search">
{% import "tracker.filters.twig" as filters %}

<form action="{{ uri.base.path }}tracker/{{ project.alias }}" method="post" name="issuesForm" id="issuesForm" class="form-inline form-search">
<div class="filters btn-toolbar clearfix">
<div class="filter-search btn-group pull-left input-append">
<label class="filter-search-lbl element-invisible"
for="filter-search">{{ translate('Filter the list by summary or description.') }}</label>
<input type="text" class="search-query input-xlarge" name="filter-search" id="filter-search"
value="{{ state.get('list.filter') }}" onchange="document.adminForm.submit();"
value="{{ state.get('filter.search') }}" onchange="document.issuesForm.submit();"
title="{{ translate('Filter the list by summary or description.') }}"
placeholder="{{ translate('Filter the list by summary or description.') }}"/>
<button class="btn tip hasTooltip" type="submit" title="{{ translate('Submit search') }}">
<span class="icon-search"></span></button>
</div>
<div class="btn-group pull-left">
<button class="btn tip hasTooltip" type="button"
onclick="jQuery('#filter-search').val('');document.adminForm.submit();"
onclick="jQuery('#filter-search').val('');document.issuesForm.submit();"
title="{{ translate('Clear search') }}"><span class="icon-remove"></span></button>
</div>
<div class="pull-right btn-group">
{{ filters.priority(state.get('filter.priority'), 'filter-priority', 'input-small') }}
</div>
<div class="pull-right btn-group">
{{ filters.status(state.get('filter.status'), 'filter-status', 'input-medium') }}
</div>
<div class="pull-right btn-group">
{{ filters.sort(state.get('filter.sort'), 'filter-sort', 'input-medium') }}
</div>
<div class="btn-group pull-right" data-toggle="buttons-checkbox" id="filter-oc-status-div">
<button type="button" class="btn {% if state.get('filter.status') == 1 %} active btn-success {% endif %}" value="1">{{ translate('Open') }}</button>
<button type="button" class="btn {% if state.get('filter.status') == 10 %} active btn-danger {% endif %}" value="10">{{ translate('Closed') }}</button>
<input type="hidden" name="filter-oc-status" id="filter-oc-status" value="{{ state.get('filter.status') }}" />
</div>
</div>

{% if pagination.pagesTotal > 1 %}
Expand All @@ -64,7 +80,7 @@
<th width="5%">{{ translate('Priority') }}</th>
<th width="10%">{{ translate('Status') }}</th>
<th width="10%" class="hidden-phone">{{ translate('Date Opened') }}</th>
<th width="10%" class="hidden-phone">{{ translate('Date Closed') }}</th>
<th width="10%" class="date-closed hidden-phone">{{ translate('Date Closed') }}</th>
<th width="10%" class="hidden-phone">{{ translate('Last Modified') }}</th>
</tr>
</thead>
Expand Down Expand Up @@ -99,23 +115,23 @@
<a href="{{ uri.base.path }}tracker/{{ project.alias }}/{{ item.issue_number }}">
{{ item.title|e }}
</a>
{{ item.labels|labels|raw }}
{% if project.gh_user and project.gh_project %}
<br/>
{{ translate('GitHub ID') }}
<a href="https://github.com/{{ project.gh_user }}/{{ project.gh_project }}/issues/{{ item.issue_number }}"
target="_blank">
{{ item.issue_number }}
</a>
{% endif %}
{% if item.foreign_number %}
<br/>
{{ translate('Foreign ID') }}
<a href="http://joomlacode.org/gf/project/joomla/tracker/?action=TrackerItemEdit&tracker_item_id={{ item.foreign_number }}"
target="_blank">
{{ item.foreign_number }}
</a>
{% endif %}
{{ item.labels|labels|raw }}
{% if project.gh_user and project.gh_project %}
<br/>
{{ translate('GitHub ID') }}
<a href="https://github.com/{{ project.gh_user }}/{{ project.gh_project }}/issues/{{ item.issue_number }}"
target="_blank">
{{ item.issue_number }}
</a>
{% endif %}
{% if item.foreign_number %}
<br/>
{{ translate('Foreign ID') }}
<a href="http://joomlacode.org/gf/project/joomla/tracker/?action=TrackerItemEdit&tracker_item_id={{ item.foreign_number }}"
target="_blank">
{{ item.foreign_number }}
</a>
{% endif %}
</div>
</td>
<td class="center">
Expand All @@ -129,7 +145,7 @@
<td class="nowrap small hidden-phone">
{{ item.opened_date|date('Y-m-d') }}
</td>
<td class="nowrap small hidden-phone">
<td class="date-closed nowrap small hidden-phone">
{% if item.closed_status %}
{{ item.closed_date|date('Y-m-d') }}
{% endif %}
Expand Down Expand Up @@ -178,24 +194,55 @@

{{ parent() }}

<script src="{{ uri.base.path }}vendor/bootstrap/js/bootstrap-tooltip.js"></script>
<script src="{{ uri.base.path }}vendor/bootstrap/js/bootstrap-popover.js"></script>
<script src="{{ uri.base.path }}vendor/blueimp-tmpl/js/tmpl{{ jdebug ? "" : ".min" }}.js"></script>
<script src="{{ uri.base.path }}jtracker/blueimp-tmpl/js/jtracker-tmpl{{ jdebug ? "" : ".min" }}.js"></script>

<script type="text/javascript">

$('*[data-issue]').click(
function () {
var el = $(this);
$.get(el.data('issue'), function(r) {
el.popover({
content: tmpl("tplIssueInfo", r.data),
html: true
}).popover('show');
});
el.unbind('click');
return false;
}
function () {
var el = $(this);
$.get(el.data('issue'), function(r) {
el.popover({
content: tmpl("tplIssueInfo", r.data),
html: true
}).popover('show');
});
el.unbind('click');
return false;
}
);

var form = $('#issuesForm');

$('[id^=filter]').change(
function() {
form.submit();
}
);

$('#filter-status').change(
function() {
var val = $(this).val();
$('#filter-oc-status').val(val);
$('#filter-oc-status-div button').val(val);
}
);

$('#filter-oc-status-div button').click(
function () {
var val = $(this).val();
$('#filter-oc-status').val(val);
$('#filter-status').val(val);
form.submit();
}
);

if ($('#filter-status').val() == 1) {
$('.date-closed').hide();
};
</script>

{% endblock %}
45 changes: 45 additions & 0 deletions templates/tracker/tracker.filters.twig
@@ -0,0 +1,45 @@
{# Copyright (C) 2013 - 2013 Open Source Matters, Inc. All rights reserved. #}
{# GNU General Public License version 2 or later; see LICENSE.txt#}

{% macro status(value, id, class) %}
{% import "fields.twig" as fields %}

{% set values = {
0: translate('status'),
1: 'opened',
2: 'confirmed',
3: 'pending',
4: 'fixed',
5: 'rtc',
6: 'review',
7: 'info',
8: 'platform',
9: 'no_reply',
10: 'closed',
11: 'expected',
12: 'known'
} %}

{{ fields.select('filter-status', values, value, id, class) }}
{% endmacro %}

{% macro priority(value, id, class) %}
{% import "fields.twig" as fields %}

{% set values = {0: translate('priority'), 1: 1, 2: 2, 3: 3, 4: 4, 5: 5 } %}

{{ fields.select('filter-priority', values, value, id, class) }}
{% endmacro %}

{% macro sort(value, id, class) %}
{% import "fields.twig" as fields %}

{% set values = {
0: translate('Newest'),
1: translate('Oldest'),
2: translate('Recently updated'),
3: translate('Least recently updated')
} %}

{{ fields.select('filter-sort', values, value, id, class) }}
{% endmacro %}
2 changes: 1 addition & 1 deletion www/jtracker/pagination/css/B_black.css
Expand Up @@ -7,7 +7,7 @@
border-radius:3px;
-moz-border-radius:3px;
-webkit-border-radius:3px;
padding:6px 9px 6px 9px;
padding:4px 9px 4px 9px;
}

ul.trackerPagination li
Expand Down