Search List component

stable
Daniel García 2018-07-20 10:58:01 +02:00 committed by Alex Hermida
parent 7b3bdfbf97
commit 572ba7a8b9
12 changed files with 224 additions and 140 deletions

View File

@ -840,40 +840,43 @@ $confirm, $q, attachmentsService, $template, $compile) ->
}
}
$scope.$on "genericform:new", (ctx, data) ->
getSchema(data)
$scope.$on "genericform:new", (ctx, params) ->
getSchema(params)
$scope.mode = 'new'
$scope.getOrCreate = false
mount(data)
mount(params)
$scope.$on "genericform:new-or-existing", (ctx, data) ->
getSchema(data)
$scope.$on "genericform:new-or-existing", (ctx, params) ->
getSchema(params)
$scope.mode = 'add-existing'
$scope.getOrCreate = true
$scope.existingFilterText = ''
$scope.existingItems = {}
$scope.existingOptions = data.existingOptions
mount(data)
$scope.existingItems = []
$rs[schema.model].listInAllProjects({ project: $scope.project.id }, true).then (data) ->
$scope.existingItems = data
mount(params)
$scope.$on "genericform:edit", (ctx, data) ->
getSchema(data)
$scope.$on "genericform:edit", (ctx, params) ->
getSchema(params)
$scope.mode = 'edit'
$scope.getOrCreate = false
mount(data)
mount(params)
getSchema = (params) ->
_.map params, (value, key) ->
$scope[key] = value
getSchema = (data) ->
$scope.objType = data.objType
if !$scope.objType || !schemas[$scope.objType]
return $log.error("Invalid objType `#{$scope.objType}` for `genericform` event")
schema = schemas[$scope.objType]
mount = (data) ->
mount = (params) ->
$scope.objName = schema.objName
if $scope.mode == 'edit'
$scope.obj = data.obj
$scope.attachments = Immutable.fromJS(data.attachments)
$scope.obj = params.obj
$scope.attachments = Immutable.fromJS(params.attachments)
else
$scope.obj = $model.make_model(schema.model, schema.initialData(data))
$scope.obj = $model.make_model(schema.model, schema.initialData(params))
$scope.attachments = Immutable.List()
_.map schema.data($scope.project), (value, key) ->
@ -939,35 +942,13 @@ $confirm, $q, attachmentsService, $template, $compile) ->
return attachmentsService.delete($scope.objType, attachment.id)
return $q.all(promises)
addExisting = (ref) ->
addExisting = (item) ->
currentLoading = $loading().target($el.find(".add-existing-button")).start()
selectedItem = $scope.existingItems[parseInt(ref)]
selectedItem.setAttr($scope.existingOptions.targetField, $scope.existingOptions.targetValue)
$repo.save(selectedItem, true).then (data) ->
item.setAttr($scope.relatedField, $scope.relatedObjectId)
$repo.save(item, true).then (data) ->
currentLoading.finish()
lightboxService.close($el)
$rootScope.$broadcast("#{$scope.objType}form:add:success", selectedItem)
$scope.getTargetTitle = (item) ->
index = item[$scope.existingOptions.targetField]
return $scope.existingOptions.targetsById[index]?.name
$scope.existingFilterChanged = (value) ->
if value?
$rs[schema.model].listInAllProjects(
{ project: $scope.project.id, q: value }, true
).then (data) ->
$scope.existingItems = {}
_.map(data, (itemModel) ->
itemModel.html = itemModel.subject
targetTitle = $scope.getTargetTitle(itemModel)
if targetTitle
itemModel.html = "#{itemModel.html} (#{targetTitle})"
itemModel.class = 'strong'
$scope.existingItems[itemModel.ref] = itemModel
)
$rootScope.$broadcast("#{$scope.objType}form:add:success", item)
$scope.addExisting = (selectedItem) ->
event.preventDefault()
@ -1079,16 +1060,6 @@ $confirm, $q, attachmentsService, $template, $compile) ->
$scope.selectedStatus = _.find $scope.statusList, (item) -> item.id == id
$scope.obj.is_closed = $scope.selectedStatus.is_closed
render = () ->
# templatePath = "common/lightbox/lightbox-create-edit/lb-create-edit-#{$scope.objType}.html"
# template = $template.get(templatePath, true)
_.map schema.data($scope.project), (value, key) ->
$scope[key] = value
# html = $compile(template($scope))($scope)
# $el.html(html)
return {
link: link
templateUrl: "common/lightbox/lightbox-create-edit/lb-create-edit.html"

View File

@ -110,7 +110,10 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
@scope.$on "assign-sprint-to-issue:success", (ctx, milestoneId) =>
@rootscope.$broadcast("object:updated")
@scope.issue.milestone = milestoneId
@.loadSprint()
if milestoneId
@.loadSprint()
else
@scope.sprint = null
initializeOnDeleteGoToUrl: ->
ctx = {project: @scope.project.slug}
@ -713,6 +716,7 @@ AssignSprintToIssueButtonDirective = ($rootScope, $rs, $repo, $loading, $transla
issue = $model.$modelValue
$rs.sprints.list($scope.projectId, null).then (data) ->
$scope.milestones = data.milestones
$scope.selectedSprintId = issue.milestone
avaliableMilestones = angular.copy($scope.milestones)
lightboxService.open($el.find(".lightbox-assign-sprint-to-issue"))
@ -729,15 +733,13 @@ AssignSprintToIssueButtonDirective = ($rootScope, $rs, $repo, $loading, $transla
existsMilestone(filterText, milestone.name)
)
$scope.saveIssueToSprint = (selectedSprintId) ->
currentLoading = $loading().target($el.find(".e2e-select-related-sprint-button")).start()
issue.setAttr('milestone', selectedSprintId)
$scope.saveIssueToSprint = (selectedSprint, event) ->
currentLoading = $loading().target($(event.currentTarget)).start()
issue.setAttr('milestone', selectedSprint.id)
$repo.save(issue, true).then (data) ->
currentLoading.finish()
lightboxService.close($el.find(".lightbox-assign-sprint-to-issue"))
$scope.$broadcast("assign-sprint-to-issue:success", selectedSprintId)
$scope.$broadcast("assign-sprint-to-issue:success", selectedSprint.id)
return {
link: link

View File

@ -385,7 +385,6 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
@scope.usStatusList = _.sortBy(project.us_statuses, "order")
@scope.usStatusById = groupBy(project.us_statuses, (e) -> e.id)
@scope.issueStatusById = groupBy(project.issue_statuses, (e) -> e.id)
@scope.milestonesById = groupBy(project.milestones, (e) -> e.id)
@scope.$emit('project:loaded', project)
@ -623,15 +622,12 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
switch type
when "standard" then @rootscope.$broadcast("genericform:new-or-existing",
{
'objType': 'issue',
'project': @scope.project,
'sprintId': @scope.sprintId,
'existingOptions': {
targetField: 'milestone',
targetValue: @scope.sprintId,
targetsById: @scope.milestonesById,
title: "#{@translate.instant("COMMON.FIELDS.SPRINT")} #{@scope.sprint.name}",
}
objType: 'issue',
project: @scope.project,
sprintId: @scope.sprintId,
relatedField: 'milestone',
relatedObjectId: @scope.sprintId,
title: "#{@translate.instant("COMMON.FIELDS.SPRINT")} #{@scope.sprint.name}",
})
when "standard" then @rootscope.$broadcast("taskform:new", @scope.sprintId, us?.id)
when "bulk" then @rootscope.$broadcast("issueform:bulk", @scope.projectId, @scope.sprintId)

View File

@ -1434,7 +1434,7 @@
"LINK_TASKBOARD": "Taskboard",
"TITLE_LINK_TASKBOARD": "Go to the taskboard",
"FILTER_SPRINTS": "Filter Sprints",
"CHOOSE_SPRINT": "What's the user Sprint?",
"CHOOSE_SPRINT": "What's the Sprint?",
"FIELDS": {
"PRIORITY": "Priority",
"SEVERITY": "Severity",

View File

@ -0,0 +1,3 @@
- var hash = "#";
div.title #{hash}{{ item.ref }} {{ item.subject }}
div.info(ng-if="item.milestone") {{ milestonesById[item.milestone].name }}

View File

@ -0,0 +1,2 @@
div.title {{ item.name }}
div.info {{ item.estimated_start }} - {{ item.estimated_finish }}

View File

@ -0,0 +1,91 @@
###
# Copyright (C) 2014-2018 Taiga Agile LLC <taiga@taiga.io>
#
# 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: search-list.directive.coffee
###
module = angular.module("taigaComponents")
normalizeString = @.taiga.normalizeString
groupBy = @.taiga.groupBy
searchListDirective = ($translate) ->
link = (scope, el, attrs, model) ->
scope.templateUrl = "components/search-list/search-list-#{scope.itemType}-choice.html"
scope.currentSelected = null
filtering = false
avaliableItems = []
itemsById = {}
if scope.itemType == 'issue'
scope.milestonesById = groupBy(scope.project.milestones, (e) -> e.id)
el.on "click", ".choice", (event) ->
choiceId = parseInt($(event.currentTarget).data("choice-id"))
value = if attrs.ngModel?.id != choiceId then itemsById[choiceId] else null
model.$setViewValue(value)
scope.currentSelected = value
scope.$apply()
isContainedIn = (needle, haystack) ->
return _.includes(parseString(haystack), parseString(needle))
parseString = (value) ->
if typeof value != 'string'
value = value.toString()
return normalizeString(value.toUpperCase())
el.on "blur", "#items-filter", (event) ->
filtering = false
el.on "focus", "#items-filter", (event) ->
filtering = true
scope.filterItems = (searchText) ->
scope.items = avaliableItems.filter((item) ->
itemAttrs = item.getAttrs()
if Array.isArray(scope.filterBy)
_.some(scope.filterBy, (attr) -> isContainedIn(searchText, itemAttrs[attr]))
else
isContainedIn(searchText, itemAttrs[scope.filterBy])
)
if scope.value
scope.value = _.find(scope.items, scope.currentSelected)
scope.$watch 'items', (items) ->
if !filtering && items?.length
if scope.resetOnChange
scope.currentSelected = null
model.$setViewValue(null)
avaliableItems = angular.copy(items)
itemsById = groupBy(avaliableItems, (x) -> x.id)
return {
link: link,
templateUrl: "components/search-list/search-list.html",
require: "ngModel",
scope: {
label: '@',
placeholder: '@',
project: '=',
filterBy: '=',
items: '=',
itemType: '@',
resetOnChange: "="
}
}
module.directive('tgSearchList', ['$translate', searchListDirective])

View File

@ -0,0 +1,23 @@
fieldset.search-list
label(
for="items-filter"
) {{ label }}
input.items-filter(
id="items-filter"
type="text"
placeholder="{{ placeholder }}"
ng-model="searchText"
ng-change="filterItems(searchText)"
)
ul
li.choice(
ng-repeat="item in items track by item.id"
ng-class="{ 'selected': item.id == currentSelected.id }"
data-choice-id="{{ item.id }}"
)
ng-include(src="templateUrl")
p.no-stories-found(
ng-show="!items.length"
) {{ noItemsText }}

View File

@ -0,0 +1,36 @@
.search-list {
ul {
background: $mass-white;
border: 1px solid $gray-light;
height: 200px;
margin: .25em 0 0;
max-height: 200px;
overflow-y: auto;
}
li {
cursor: pointer;
padding: .25em .5em;
&.selected {
background: $yellow-green;
color: $white;
.info {
color: $white;
}
}
ng-include {
display: flex;
width: 100%;
.title {
flex-grow: 1;
text-align: left;
}
.info {
color: $gray-lighter;
text-align: right;
}
}
.title span {
margin-right: .5em;
}
}
}

View File

@ -31,35 +31,17 @@ form(ng-if="lightboxOpen")
div(ng-if="mode == 'add-existing'")
.existing-item-wrapper
label(for="existing-filter") {{ 'LIGHTBOX.CREATE_EDIT.CHOOSE_EXISTING' | translate: { objName: objName } }}
input.filter(
id="existing-filter"
name="existing-filter"
type="text"
ng-model="existingFilterText"
ng-model-options="{ debounce: 200 }"
ng-change="existingFilterChanged(existingFilterText)"
tg-search-list(
label="{{ 'LIGHTBOX.CREATE_EDIT.CHOOSE_EXISTING' | translate: { objName: objName } }}"
placeholder="{{ 'ISSUES.FILTER_SPRINTS' | translate }}"
items="existingItems"
ng-model="selectedItem"
filter-by="['ref', 'subject']"
project="project"
item-type="{{ objType }}"
reset-on-change="true"
)
.existing-item(ng-show="existingItems")
select.userstory.e2e-userstories-select(
size="5"
ng-model="selectedItem"
data-required="true"
)
- var hash = "#";
option.hidden(value="")
option(
ng-repeat="(ref, obj) in existingItems"
ng-class="obj.class"
value="{{ ::ref }}"
) #{hash}{{ ref }} {{ obj.html }}
p.no-stories-found(
ng-show="existingFilterText && !existingItems"
translate="EPIC.NO_USERSTORIES_FOUND"
) {{ 'LIGHTBOX.CREATE_EDIT.NO_ITEMS_FOUND' | translate }}
button.button-green.add-existing-button(
ng-click="addExisting(selectedItem)"
ng-disabled="!selectedItem"

View File

@ -11,45 +11,19 @@ a.assign-issue-button.button-gray.is-editable(
tg-lightbox-close
div.lightbox-assign-related-sprint
h2 "{{ 'ISSUES.ACTION_ASSIGN_SPRINT' | translate }}"
fieldset.existing-sprint
label(
translate="ISSUES.CHOOSE_SPRINT"
for="sprint-filter"
)
input.sprint-filter.e2e-filter-sprints-input(
id="sprint-filter"
type="text"
placeholder="{{'ISSUES.FILTER_SPRINTS' | translate}}"
ng-model="filterText"
ng-change="filterMilestones(filterText)"
)
form.existing-user-story-form
select.userstory.e2e-userstories-select(
size="5"
data-required="true"
ng-model="selectedSprint"
)
- var hash = "#";
option.hidden(
value=""
)
option(
ng-repeat="milestone in milestones | toMutable track by milestone.id"
value="{{ ::milestone.id }}"
) #{hash}{{::milestone.name}}
p.no-stories-found(
ng-show="!milestones.length"
translate="EPIC.NO_USERSTORIES_FOUND"
)
button.button-green.e2e-select-related-sprint-button(
href=""
ng-click="saveIssueToSprint(selectedSprint)"
ng-disabled="!selectedSprint"
tg-loading="vm.loading"
translate="COMMON.SAVE"
)
h2.title {{ 'ISSUES.ACTION_ASSIGN_SPRINT' | translate }}
tg-search-list(
label="{{ 'ISSUES.CHOOSE_SPRINT' | translate }}"
placeholder="{{ 'ISSUES.FILTER_SPRINTS' | translate }}"
items="milestones"
ng-model="selectedSprint"
filter-by="['name', 'estimated_start', 'estimated_finish']"
project="project"
item-type="sprint"
)
button.button-green.select-option(
href=""
ng-click="saveIssueToSprint(selectedSprint, $event)"
tg-loading="vm.loading"
translate="COMMON.SAVE"
)

View File

@ -856,6 +856,10 @@
.ticket-detail-settings .lightbox-assign-sprint-to-issue {
.lightbox-assign-related-sprint {
width: 700px;
}
svg {
fill: initial;
max-height: initial;