diff --git a/app/coffee/modules/backlog/filters.coffee b/app/coffee/modules/backlog/filters.coffee new file mode 100644 index 00000000..e1d34315 --- /dev/null +++ b/app/coffee/modules/backlog/filters.coffee @@ -0,0 +1,159 @@ +### +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino Garcia +# Copyright (C) 2014 David Barragán Merino +# +# 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 . +# +# 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) { %> + + <%- f.name %> + <%- f.count %> + + <% } else { %> + + <%- f.name %> + <%- f.count %> + + <% } %> + <% }) %> + """) + + templateSelected = _.template(""" + <% _.each(filters, function(f) { %> + + <%- f.name %> + + + <% }) %> + """) + + 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]) diff --git a/app/coffee/modules/backlog/main.coffee b/app/coffee/modules/backlog/main.coffee index 76e2b7a3..59ca14d1 100644 --- a/app/coffee/modules/backlog/main.coffee +++ b/app/coffee/modules/backlog/main.coffee @@ -33,7 +33,7 @@ module = angular.module("taigaBacklog") ## Backlog Controller ############################################################################# -class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin) +class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.FiltersMixin) @.$inject = [ "$scope", "$rootScope", @@ -41,10 +41,11 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin) "$tgConfirm", "$tgResources", "$routeParams", - "$q" + "$q", + "$tgLocation" ] - constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q) -> + constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location) -> _.bindAll(@) @scope.sectionName = "Backlog" @@ -74,9 +75,11 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin) loadUserstories: -> return @rs.userstories.listUnassigned(@scope.projectId).then (userstories) => @scope.userstories = userstories - @scope.filters = @.generateFilters() + @.generateFilters() @.filterVisibleUserstories() + + @rootscope.$broadcast("filters:loaded", @scope.filters) # 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 scopeDefer @scope, => @@ -111,25 +114,63 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin) .then(=> @.loadBacklog()) filterVisibleUserstories: -> + @scope.visibleUserstories = [] + + # Filter by tags selectedTags = _.filter(@scope.filters.tags, "selected") selectedTags = _.map(selectedTags, "name") - @scope.visibleUserstories = [] - if selectedTags.length == 0 @scope.visibleUserstories = _.clone(@scope.userstories, false) else @scope.visibleUserstories = _.reject @scope.userstories, (us) => if _.intersection(selectedTags, us.tags).length == 0 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: -> - 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")) - filters.tags = _.map(_.countBy(plainTags), (v, k) -> {name: k, count:v}) - return filters + @scope.filters.tags = _.map _.countBy(plainTags), (v, k) -> + 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 @@ -344,15 +385,6 @@ BacklogDirective = ($repo, $rootscope) -> toggleText(target.find(".text"), ["Hide Filters", "Show Filters"]) # TODO: i18n $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) -> $ctrl = $el.controller() diff --git a/app/partials/backlog.jade b/app/partials/backlog.jade index 972c2fd4..e22885bd 100644 --- a/app/partials/backlog.jade +++ b/app/partials/backlog.jade @@ -6,8 +6,8 @@ block head block content div.wrapper(tg-backlog, ng-controller="BacklogController as ctrl", ng-init="section='backlog'") - sidebar.menu-secondary.extrabar.filters-bar - include views/modules/filters + sidebar.menu-secondary.extrabar.filters-bar(tg-backlog-filters) + include views/modules/backlog-filters section.main.backlog include views/components/mainTitle include views/components/summary diff --git a/app/partials/views/modules/backlog-filters.jade b/app/partials/views/modules/backlog-filters.jade new file mode 100644 index 00000000..deddaf9b --- /dev/null +++ b/app/partials/views/modules/backlog-filters.jade @@ -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