Add initial version of filters.
parent
c3e662792d
commit
65347a7d97
|
@ -0,0 +1,159 @@
|
||||||
|
###
|
||||||
|
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||||
|
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
|
||||||
|
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# File: modules/backlog/main.coffee
|
||||||
|
###
|
||||||
|
|
||||||
|
taiga = @.taiga
|
||||||
|
|
||||||
|
mixOf = @.taiga.mixOf
|
||||||
|
toggleText = @.taiga.toggleText
|
||||||
|
scopeDefer = @.taiga.scopeDefer
|
||||||
|
bindOnce = @.taiga.bindOnce
|
||||||
|
groupBy = @.taiga.groupBy
|
||||||
|
debounce = @.taiga.debounce
|
||||||
|
|
||||||
|
|
||||||
|
module = angular.module("taigaBacklog")
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
## Issues Filters Directive
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
BacklogFiltersDirective = ($log, $location) ->
|
||||||
|
template = _.template("""
|
||||||
|
<% _.each(filters, function(f) { %>
|
||||||
|
<% if (f.selected) { %>
|
||||||
|
<a class="single-filter active"
|
||||||
|
data-type="<%= f.type %>"
|
||||||
|
data-id="<%= f.id %>">
|
||||||
|
<span class="name"><%- f.name %></span>
|
||||||
|
<span class="number"><%- f.count %></span>
|
||||||
|
</a>
|
||||||
|
<% } else { %>
|
||||||
|
<a class="single-filter"
|
||||||
|
data-type="<%= f.type %>"
|
||||||
|
data-id="<%= f.id %>">
|
||||||
|
<span class="name"><%- f.name %></span>
|
||||||
|
<span class="number"><%- f.count %></span>
|
||||||
|
</a>
|
||||||
|
<% } %>
|
||||||
|
<% }) %>
|
||||||
|
""")
|
||||||
|
|
||||||
|
templateSelected = _.template("""
|
||||||
|
<% _.each(filters, function(f) { %>
|
||||||
|
<a class="single-filter selected"
|
||||||
|
data-type="<%= f.type %>"
|
||||||
|
data-id="<%= f.id %>">
|
||||||
|
<span class="name"><%- f.name %></span>
|
||||||
|
<span class="icon icon-delete"></span>
|
||||||
|
</a>
|
||||||
|
<% }) %>
|
||||||
|
""")
|
||||||
|
|
||||||
|
selectedFilters = []
|
||||||
|
|
||||||
|
link = ($scope, $el, $attrs) ->
|
||||||
|
$ctrl = $el.closest(".wrapper").controller()
|
||||||
|
|
||||||
|
showFilters = (title) ->
|
||||||
|
$el.find(".filters-cats").hide()
|
||||||
|
$el.find(".filter-list").show()
|
||||||
|
$el.find("h1 a.subfilter").removeClass("hidden")
|
||||||
|
$el.find("h1 a.subfilter span.title").html(title)
|
||||||
|
|
||||||
|
showCategories = ->
|
||||||
|
$el.find(".filters-cats").show()
|
||||||
|
$el.find(".filter-list").hide()
|
||||||
|
$el.find("h1 a.subfilter").addClass("hidden")
|
||||||
|
|
||||||
|
initializeSelectedFilters = (filters) ->
|
||||||
|
for name, values of filters
|
||||||
|
for val in values
|
||||||
|
selectedFilters.push(val) if val.selected
|
||||||
|
|
||||||
|
renderSelectedFilters()
|
||||||
|
|
||||||
|
renderSelectedFilters = ->
|
||||||
|
html = templateSelected({filters:selectedFilters})
|
||||||
|
$el.find(".filters-applied").html(html)
|
||||||
|
|
||||||
|
renderFilters = (filters) ->
|
||||||
|
html = template({filters:filters})
|
||||||
|
$el.find(".filter-list").html(html)
|
||||||
|
|
||||||
|
toggleFilterSelection = (type, id) ->
|
||||||
|
filters = $scope.filters[type]
|
||||||
|
filter = _.find(filters, {id: taiga.toString(id)})
|
||||||
|
filter.selected = (not filter.selected)
|
||||||
|
if filter.selected
|
||||||
|
selectedFilters.push(filter)
|
||||||
|
$scope.$apply ->
|
||||||
|
$ctrl.selectFilter(type, id)
|
||||||
|
$ctrl.filterVisibleUserstories()
|
||||||
|
else
|
||||||
|
selectedFilters = _.reject(selectedFilters, filter)
|
||||||
|
$scope.$apply ->
|
||||||
|
$ctrl.unselectFilter(type, id)
|
||||||
|
$ctrl.filterVisibleUserstories()
|
||||||
|
|
||||||
|
renderSelectedFilters(selectedFilters)
|
||||||
|
renderFilters(_.reject(filters, "selected"))
|
||||||
|
|
||||||
|
# Angular Watchers
|
||||||
|
$scope.$on "filters:loaded", (ctx, filters) ->
|
||||||
|
initializeSelectedFilters(filters)
|
||||||
|
|
||||||
|
# Dom Event Handlers
|
||||||
|
$el.on "click", ".filters-cats > ul > li > a", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
target = angular.element(event.currentTarget)
|
||||||
|
tags = $scope.filters[target.data("type")]
|
||||||
|
|
||||||
|
renderFilters(_.reject(tags, "selected"))
|
||||||
|
showFilters(target.attr("title"))
|
||||||
|
|
||||||
|
$el.on "click", ".filters-inner > h1 > a.title", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
showCategories($el)
|
||||||
|
|
||||||
|
$el.on "click", ".filters-applied a", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
target = angular.element(event.currentTarget)
|
||||||
|
|
||||||
|
id = target.data("id")
|
||||||
|
type = target.data("type")
|
||||||
|
toggleFilterSelection(type, id)
|
||||||
|
|
||||||
|
$el.on "click", ".filter-list .single-filter", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
target = angular.element(event.currentTarget)
|
||||||
|
if target.hasClass("active")
|
||||||
|
target.removeClass("active")
|
||||||
|
# target.css("background-color")
|
||||||
|
else
|
||||||
|
target.addClass("active")
|
||||||
|
|
||||||
|
id = target.data("id")
|
||||||
|
type = target.data("type")
|
||||||
|
toggleFilterSelection(type, id)
|
||||||
|
|
||||||
|
return {link:link}
|
||||||
|
|
||||||
|
module.directive("tgBacklogFilters", ["$log", "$tgLocation", BacklogFiltersDirective])
|
|
@ -33,7 +33,7 @@ module = angular.module("taigaBacklog")
|
||||||
## Backlog Controller
|
## Backlog Controller
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin)
|
class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.FiltersMixin)
|
||||||
@.$inject = [
|
@.$inject = [
|
||||||
"$scope",
|
"$scope",
|
||||||
"$rootScope",
|
"$rootScope",
|
||||||
|
@ -41,10 +41,11 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
"$tgConfirm",
|
"$tgConfirm",
|
||||||
"$tgResources",
|
"$tgResources",
|
||||||
"$routeParams",
|
"$routeParams",
|
||||||
"$q"
|
"$q",
|
||||||
|
"$tgLocation"
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q) ->
|
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location) ->
|
||||||
_.bindAll(@)
|
_.bindAll(@)
|
||||||
|
|
||||||
@scope.sectionName = "Backlog"
|
@scope.sectionName = "Backlog"
|
||||||
|
@ -74,9 +75,11 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
loadUserstories: ->
|
loadUserstories: ->
|
||||||
return @rs.userstories.listUnassigned(@scope.projectId).then (userstories) =>
|
return @rs.userstories.listUnassigned(@scope.projectId).then (userstories) =>
|
||||||
@scope.userstories = userstories
|
@scope.userstories = userstories
|
||||||
@scope.filters = @.generateFilters()
|
|
||||||
|
|
||||||
|
@.generateFilters()
|
||||||
@.filterVisibleUserstories()
|
@.filterVisibleUserstories()
|
||||||
|
|
||||||
|
@rootscope.$broadcast("filters:loaded", @scope.filters)
|
||||||
# The broadcast must be executed when the DOM has been fully reloaded.
|
# The broadcast must be executed when the DOM has been fully reloaded.
|
||||||
# We can't assure when this exactly happens so we need a defer
|
# We can't assure when this exactly happens so we need a defer
|
||||||
scopeDefer @scope, =>
|
scopeDefer @scope, =>
|
||||||
|
@ -111,25 +114,63 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
.then(=> @.loadBacklog())
|
.then(=> @.loadBacklog())
|
||||||
|
|
||||||
filterVisibleUserstories: ->
|
filterVisibleUserstories: ->
|
||||||
|
@scope.visibleUserstories = []
|
||||||
|
|
||||||
|
# Filter by tags
|
||||||
selectedTags = _.filter(@scope.filters.tags, "selected")
|
selectedTags = _.filter(@scope.filters.tags, "selected")
|
||||||
selectedTags = _.map(selectedTags, "name")
|
selectedTags = _.map(selectedTags, "name")
|
||||||
|
|
||||||
@scope.visibleUserstories = []
|
|
||||||
|
|
||||||
if selectedTags.length == 0
|
if selectedTags.length == 0
|
||||||
@scope.visibleUserstories = _.clone(@scope.userstories, false)
|
@scope.visibleUserstories = _.clone(@scope.userstories, false)
|
||||||
else
|
else
|
||||||
@scope.visibleUserstories = _.reject @scope.userstories, (us) =>
|
@scope.visibleUserstories = _.reject @scope.userstories, (us) =>
|
||||||
if _.intersection(selectedTags, us.tags).length == 0
|
if _.intersection(selectedTags, us.tags).length == 0
|
||||||
return true
|
return true
|
||||||
else
|
return false
|
||||||
return false
|
|
||||||
|
# Filter by status
|
||||||
|
selectedStatuses = _.filter(@scope.filters.statuses, "selected")
|
||||||
|
selectedStatuses = _.map(selectedStatuses, "id")
|
||||||
|
|
||||||
|
if selectedStatuses.length > 0
|
||||||
|
@scope.visibleUserstories = _.reject @scope.visibleUserstories, (us) =>
|
||||||
|
res = _.find(selectedStatuses, (x) -> x == taiga.toString(us.status))
|
||||||
|
return not res
|
||||||
|
|
||||||
|
getUrlFilters: ->
|
||||||
|
return _.pick(@location.search(), "statuses", "tags")
|
||||||
|
|
||||||
generateFilters: ->
|
generateFilters: ->
|
||||||
filters = {}
|
searchdata = {}
|
||||||
|
for name, value of @.getUrlFilters()
|
||||||
|
if not searchdata[name]?
|
||||||
|
searchdata[name] = {}
|
||||||
|
|
||||||
|
for val in value.split(",")
|
||||||
|
searchdata[name][val] = true
|
||||||
|
|
||||||
|
isSelected = (type, id) ->
|
||||||
|
if searchdata[type]? and searchdata[type][id]
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
@scope.filters = {}
|
||||||
|
|
||||||
plainTags = _.flatten(_.map(@scope.userstories, "tags"))
|
plainTags = _.flatten(_.map(@scope.userstories, "tags"))
|
||||||
filters.tags = _.map(_.countBy(plainTags), (v, k) -> {name: k, count:v})
|
@scope.filters.tags = _.map _.countBy(plainTags), (v, k) ->
|
||||||
return filters
|
obj = {id:k, type:"tags", name: k, count:v}
|
||||||
|
obj.selected = true if isSelected("tags", obj.id)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
plainStatuses = _.map(@scope.userstories, "status")
|
||||||
|
@scope.filters.statuses = _.map _.countBy(plainStatuses), (v, k) =>
|
||||||
|
obj = {id:k, type:"statuses", name: @scope.usStatusById[k].name, count:v}
|
||||||
|
obj.selected = true if isSelected("statuses", obj.id)
|
||||||
|
console.log "statuses", obj
|
||||||
|
return obj
|
||||||
|
|
||||||
|
console.log @scope.filters.statuses
|
||||||
|
return @scope.filters
|
||||||
|
|
||||||
## Template actions
|
## Template actions
|
||||||
|
|
||||||
|
@ -344,15 +385,6 @@ BacklogDirective = ($repo, $rootscope) ->
|
||||||
toggleText(target.find(".text"), ["Hide Filters", "Show Filters"]) # TODO: i18n
|
toggleText(target.find(".text"), ["Hide Filters", "Show Filters"]) # TODO: i18n
|
||||||
$rootscope.$broadcast("resize")
|
$rootscope.$broadcast("resize")
|
||||||
|
|
||||||
$el.on "click", "section.filters a.single-filter", (event) ->
|
|
||||||
event.preventDefault()
|
|
||||||
target = angular.element(event.currentTarget)
|
|
||||||
targetScope = target.scope()
|
|
||||||
|
|
||||||
$scope.$apply ->
|
|
||||||
targetScope.tag.selected = not (targetScope.tag.selected or false)
|
|
||||||
$ctrl.filterVisibleUserstories()
|
|
||||||
|
|
||||||
link = ($scope, $el, $attrs, $rootscope) ->
|
link = ($scope, $el, $attrs, $rootscope) ->
|
||||||
$ctrl = $el.controller()
|
$ctrl = $el.controller()
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@ block head
|
||||||
block content
|
block content
|
||||||
div.wrapper(tg-backlog, ng-controller="BacklogController as ctrl",
|
div.wrapper(tg-backlog, ng-controller="BacklogController as ctrl",
|
||||||
ng-init="section='backlog'")
|
ng-init="section='backlog'")
|
||||||
sidebar.menu-secondary.extrabar.filters-bar
|
sidebar.menu-secondary.extrabar.filters-bar(tg-backlog-filters)
|
||||||
include views/modules/filters
|
include views/modules/backlog-filters
|
||||||
section.main.backlog
|
section.main.backlog
|
||||||
include views/components/mainTitle
|
include views/components/mainTitle
|
||||||
include views/components/summary
|
include views/components/summary
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
section.filters
|
||||||
|
div.filters-inner
|
||||||
|
h1
|
||||||
|
a.title(href="", title="back to categories") filters
|
||||||
|
a.hidden.subfilter(href="", title="cat-name")
|
||||||
|
span.icon.icon-arrow-right
|
||||||
|
span.title status
|
||||||
|
form
|
||||||
|
fieldset
|
||||||
|
input(type="text", placeholder="Search by subject...", ng-model="filtersSubject")
|
||||||
|
a.icon.icon-search(href="", title="search")
|
||||||
|
|
||||||
|
div.filters-step-cat
|
||||||
|
div.filters-applied
|
||||||
|
div.filters-cats
|
||||||
|
ul
|
||||||
|
li
|
||||||
|
a(href="", title="Status", data-type="statuses")
|
||||||
|
span.title Status
|
||||||
|
span.icon.icon-arrow-right
|
||||||
|
li
|
||||||
|
a(href="", title="Tags", data-type="tags")
|
||||||
|
span.title Tags
|
||||||
|
span.icon.icon-arrow-right
|
||||||
|
|
||||||
|
div.filter-list.hidden
|
Loading…
Reference in New Issue