|
@@ -23,7 +23,7 @@ class App.SidebarChecklistShow extends App.Controller
|
|
|
|
|
|
render: ->
|
|
|
|
|
|
- @html App.view('ticket_zoom/sidebar_checklist_show')(
|
|
|
+ @html App.view('ticket_zoom/sidebar_checklist/show')(
|
|
|
checklistTitle: @checklistTitle()
|
|
|
readOnly: @readOnly
|
|
|
)
|
|
@@ -38,28 +38,39 @@ class App.SidebarChecklistShow extends App.Controller
|
|
|
@actionController?.releaseController()
|
|
|
@actionController = new ChecklistReorder(parentVC: @)
|
|
|
|
|
|
- setDisabled: (node, id) ->
|
|
|
- $(node).closest("[data-id='" + id + "']").attr('disabled', true).addClass('u-unclickable u-low-opacity')
|
|
|
+ setDisabled: (node) ->
|
|
|
+ $(node)
|
|
|
+ .closest('tr')
|
|
|
+ .attr('disabled', true)
|
|
|
+ .addClass('u-unclickable u-low-opacity')
|
|
|
+
|
|
|
+ setEnabled: (node) ->
|
|
|
+ $(node)
|
|
|
+ .closest('tr')
|
|
|
+ .attr('disabled', false)
|
|
|
+ .removeClass('u-unclickable u-low-opacity')
|
|
|
+
|
|
|
+ if @actionController instanceof ChecklistReorder
|
|
|
+ @toggleReorder(true)
|
|
|
|
|
|
onAdd: (e) =>
|
|
|
addButton = e.target.closest('button')
|
|
|
|
|
|
$(addButton).attr('disabled', true)
|
|
|
|
|
|
- callbackDone = (data) =>
|
|
|
- @enterEditModeId = data.id
|
|
|
- @renderTable()
|
|
|
- @parentVC.subscribe()
|
|
|
- $(addButton).attr('disabled', false)
|
|
|
-
|
|
|
item = new App.ChecklistItem
|
|
|
item.checklist_id = @checklist.id
|
|
|
item.text = ''
|
|
|
|
|
|
+ self = @
|
|
|
+
|
|
|
item.save(
|
|
|
- done: ->
|
|
|
- App.ChecklistItem.full(@id, callbackDone, force: true)
|
|
|
+ done: (data) ->
|
|
|
+ self.addNewItem(@)
|
|
|
+
|
|
|
+ $(addButton).attr('disabled', false)
|
|
|
fail: (settings, details) =>
|
|
|
+ $(addButton).attr('disabled', false)
|
|
|
@notify(
|
|
|
type: 'error'
|
|
|
msg: App.i18n.translateContent(details.error)
|
|
@@ -67,6 +78,24 @@ class App.SidebarChecklistShow extends App.Controller
|
|
|
@renderTable()
|
|
|
)
|
|
|
|
|
|
+ addNewItem: (item) ->
|
|
|
+ row = $(App.view('ticket_zoom/sidebar_checklist/show_row')(
|
|
|
+ object: item
|
|
|
+ ))
|
|
|
+
|
|
|
+ @checklist.sorted_item_ids.push item.id.toString()
|
|
|
+ @moveOrInsertRow(item.id.toString(), row[0])
|
|
|
+
|
|
|
+ @updateDisplayValue(item)
|
|
|
+
|
|
|
+ row.data('text-value', '')
|
|
|
+
|
|
|
+ cell = row.find('.checklistItemValue')[0]
|
|
|
+
|
|
|
+ @activateItemEditMode(cell, row, item.id)
|
|
|
+
|
|
|
+ @table.find('.checklistShowRowContentCellNoItems').remove()
|
|
|
+
|
|
|
onCheckboxClick: (e) =>
|
|
|
upcomingState = e.currentTarget.checked
|
|
|
id = parseInt(e.currentTarget.value)
|
|
@@ -88,12 +117,19 @@ class App.SidebarChecklistShow extends App.Controller
|
|
|
item = App.ChecklistItem.find(id)
|
|
|
item.checked = upcomingState
|
|
|
|
|
|
- @setDisabled(checkboxElem, id)
|
|
|
+ $(checkboxElem)
|
|
|
+ .closest('tr')
|
|
|
+ .find('.checkbox-replacement-readonly')
|
|
|
+ .html(App.Utils.icon(if upcomingState then 'checkbox-checked-readonly' else 'checkbox-readonly'))
|
|
|
+
|
|
|
+ @setDisabled(checkboxElem)
|
|
|
|
|
|
item.save(
|
|
|
done: =>
|
|
|
- @renderTable()
|
|
|
+ @setEnabled(checkboxElem)
|
|
|
fail: =>
|
|
|
+ @setEnabled(checkboxElem)
|
|
|
+ item.checked = !upcomingState
|
|
|
@renderTable()
|
|
|
)
|
|
|
|
|
@@ -144,6 +180,8 @@ class App.SidebarChecklistShow extends App.Controller
|
|
|
.on('click.dropdown', '[data-table-action=check]', @onCheckOrUncheck)
|
|
|
|
|
|
onTitleChange: (e) =>
|
|
|
+ return if e?.target && $(e.target).closest('th').find('.js-input').length
|
|
|
+
|
|
|
@preventDefaultAndStopPropagation(e)
|
|
|
|
|
|
# Close any open dropdowns
|
|
@@ -154,11 +192,10 @@ class App.SidebarChecklistShow extends App.Controller
|
|
|
else
|
|
|
elem = @el.find('.js-title')[0]
|
|
|
|
|
|
- @actionController?.releaseController()
|
|
|
- @actionController = new ChecklistRenameEdit(el: elem, parentVC: @, originalValue: @checklistTitle())
|
|
|
+ @isRenamingChecklist = new App.SidebarChecklistRename(el: elem, parentVC: @, originalValue: @checklistTitle())
|
|
|
|
|
|
onEntryTextClicked: (e) =>
|
|
|
- return if @actionController instanceof ChecklistItemEdit
|
|
|
+ return if $(e.target).closest('td').find('.js-input').length
|
|
|
|
|
|
# skip on link openings
|
|
|
return if e.target.tagName is 'A'
|
|
@@ -205,16 +242,18 @@ class App.SidebarChecklistShow extends App.Controller
|
|
|
item = App.ChecklistItem.find(id)
|
|
|
|
|
|
deleteCallback = =>
|
|
|
- row.find('.checklistItemValue').css('text-decoration', 'line-through')
|
|
|
+ @setDisabled(e.currentTarget)
|
|
|
|
|
|
- @setDisabled(e.currentTarget, id)
|
|
|
-
|
|
|
item.destroy(
|
|
|
done: =>
|
|
|
- @renderTable()
|
|
|
- @parentVC.subscribe()
|
|
|
- fail: ->
|
|
|
- row.find('.checklistItemValue').css('text-decoration', 'auto')
|
|
|
+ @table
|
|
|
+ .find("tbody tr[data-id='#{id}']")
|
|
|
+ .remove()
|
|
|
+
|
|
|
+ if !@table.find('tbody tr[data-id]').length
|
|
|
+ @renderEmpty()
|
|
|
+ fail: =>
|
|
|
+ @setEnabled(e.currentTarget)
|
|
|
)
|
|
|
|
|
|
# Skip confirmation dialog if the item has no text.
|
|
@@ -229,201 +268,195 @@ class App.SidebarChecklistShow extends App.Controller
|
|
|
)
|
|
|
|
|
|
activateItemEditMode: (cell, row, id) =>
|
|
|
- @actionController?.releaseController()
|
|
|
- @actionController = new ChecklistItemEdit(
|
|
|
+ # @actionController?.releaseController()
|
|
|
+ new App.SidebarChecklistItemEdit(
|
|
|
el: cell
|
|
|
parentVC: @
|
|
|
- originalValue: cell.textContent.trim()
|
|
|
id: id
|
|
|
)
|
|
|
|
|
|
+ renderEmpty: =>
|
|
|
+ @table
|
|
|
+ .find('tbody')
|
|
|
+ .html(App.view('ticket_zoom/sidebar_checklist/show_no_items')())
|
|
|
+
|
|
|
renderTable: ->
|
|
|
- @table.find('tbody').empty()
|
|
|
+ @updateChecklistTitle()
|
|
|
|
|
|
- sorted_items = @checklist.sorted_items()
|
|
|
+ isReorderingInProgress = @actionController instanceof ChecklistReorder
|
|
|
|
|
|
- for object in sorted_items
|
|
|
- if object.ticket_id
|
|
|
- ticket = App.Ticket.find(object.ticket_id)
|
|
|
- ticketAccess = if ticket then ticket.userGroupAccess('read') else false
|
|
|
+ if !isReorderingInProgress
|
|
|
+ @reorderButton.toggleClass('hide', @checklist.sorted_item_ids.length < 2)
|
|
|
|
|
|
- html = App.view('ticket_zoom/sidebar_checklist_show_row')(
|
|
|
- object: object
|
|
|
- ticket: ticket
|
|
|
- ticketAccess: ticketAccess
|
|
|
- readOnly: @readOnly
|
|
|
- )
|
|
|
+ @parentVC.badgeRenderLocal()
|
|
|
|
|
|
- @table.find('tbody').append(html)
|
|
|
+ if !@checklist.sorted_item_ids.length
|
|
|
+ @renderEmpty()
|
|
|
+ return
|
|
|
|
|
|
- if !sorted_items.length
|
|
|
- html = App.view('ticket_zoom/sidebar_checklist_show_no_items')()
|
|
|
- @table.find('tbody').append(html)
|
|
|
+ @table.find('.checklistShowRowContentCellNoItems').remove()
|
|
|
|
|
|
- dndOptions =
|
|
|
- tolerance: 'pointer'
|
|
|
- distance: 15
|
|
|
- opacity: 0.6
|
|
|
- forcePlaceholderSize: true
|
|
|
- items: 'tr'
|
|
|
- @table.find('tbody').sortable(dndOptions)
|
|
|
- @table.find('tbody').sortable('disable')
|
|
|
+ @cleanUpDeleted()
|
|
|
|
|
|
- @reorderButton.toggleClass('hide', !sorted_items.length || sorted_items.length < 2)
|
|
|
+ if!isReorderingInProgress
|
|
|
+ @renderSorting()
|
|
|
|
|
|
- if @enterEditMode
|
|
|
- @enterEditMode = undefined
|
|
|
- @enterEditModeId = @table.find('tbody tr:last-of-type').data('id')
|
|
|
+ @renderDisplayValues()
|
|
|
+ @renderSortable()
|
|
|
+ @renderEditMode()
|
|
|
|
|
|
- if @enterEditModeId
|
|
|
- cell = @table.find("tbody tr[data-id='" + @enterEditModeId + "']").find('.checklistItemValue')[0]
|
|
|
- row = $(cell).closest('tr')
|
|
|
- return if !row.length
|
|
|
- @enterEditModeId = undefined
|
|
|
- @activateItemEditMode(cell, row, row.data('id'))
|
|
|
+ if @actionController instanceof ChecklistReorder
|
|
|
+ @toggleReorder(true)
|
|
|
|
|
|
- @parentVC.badgeRenderLocal()
|
|
|
+ updateChecklistTitle: ->
|
|
|
+ return if @isRenamingChecklist
|
|
|
|
|
|
-class ChecklistItemEdit extends App.Controller
|
|
|
- elements:
|
|
|
- '.js-input': 'input'
|
|
|
- events:
|
|
|
- 'click .js-cancel': 'onCancel'
|
|
|
- 'click .js-confirm': 'onConfirm'
|
|
|
- 'blur .js-input': 'onBlur'
|
|
|
- 'keyup #checklistItemEditText': 'onKeyUp'
|
|
|
+ @table.find('th').html(@checklistTitle())
|
|
|
|
|
|
- constructor: ->
|
|
|
- super
|
|
|
- @render()
|
|
|
+ renderSorting: =>
|
|
|
+ for id, current_index in @checklist.sorted_item_ids
|
|
|
+ @renderSortingSingle(id, current_index)
|
|
|
|
|
|
- releaseController: =>
|
|
|
- super
|
|
|
+ renderSortingSingle: (id, current_index) =>
|
|
|
+ row = @table.find("tbody tr[data-id=#{id}]")
|
|
|
+ object = App.ChecklistItem.find(id)
|
|
|
|
|
|
- @el.text(@originalValue)
|
|
|
- @el.removeClass('edit-widget-active')
|
|
|
- @el.closest('tr').find('.dropdown').removeClass('hide')
|
|
|
+ if row.length and current_index != row.index().toString()
|
|
|
+ @moveOrInsertRow(id, row)
|
|
|
+ return
|
|
|
|
|
|
- if @el.closest('tr').find('.dropdown--actions').hasClass('open')
|
|
|
- @el.closest('tr').find('.js-table-action-menu').dropdown('toggle')
|
|
|
+ row = $(App.view('ticket_zoom/sidebar_checklist/show_row')(
|
|
|
+ object: object
|
|
|
+ ))
|
|
|
|
|
|
- @parentVC.actionController = undefined
|
|
|
+ @updateDisplayValue(object)
|
|
|
|
|
|
- render: =>
|
|
|
- @html App.view('ticket_zoom/sidebar_checklist_item_edit')(value: @object()?.text)
|
|
|
+ @moveOrInsertRow(id, row[0])
|
|
|
|
|
|
- @el.addClass('edit-widget-active')
|
|
|
- @el.closest('tr').find('.dropdown').addClass('hide')
|
|
|
- @input.focus().val('').val(@object()?.text)
|
|
|
+ renderDisplayValues: =>
|
|
|
+ for id in @checklist.sorted_item_ids
|
|
|
+ object = App.ChecklistItem.find(id)
|
|
|
|
|
|
- object: =>
|
|
|
- App.ChecklistItem.find(@id)
|
|
|
+ if !object
|
|
|
+ continue
|
|
|
|
|
|
- setDisabled: (node, id) ->
|
|
|
- $(node).closest("[data-id='" + id + "']").attr('disabled', true).addClass('u-unclickable u-low-opacity')
|
|
|
+ row = @table.find("tbody tr[data-id=#{id}]")
|
|
|
|
|
|
- onBlur: (e) =>
|
|
|
- if $(e.originalEvent.relatedTarget).hasClass('js-cancel')
|
|
|
- @onCancel(e)
|
|
|
- return
|
|
|
+ # Thisi s used for initial filling of an empty display value
|
|
|
+ # This also works if an old checklist item is converted to a ticket reference.
|
|
|
+ # The ticket reference cannot be converted back to a text item so opposite conversion is not needed.
|
|
|
+ if object.ticket_id || row.find('.checklistShowRowDisplayValue').is(':empty')
|
|
|
+ @updateDisplayValue(object)
|
|
|
+ @setEnabled(row)
|
|
|
+ else
|
|
|
+ row
|
|
|
+ .find('input.js-checkbox')
|
|
|
+ .prop('checked', object.checked)
|
|
|
|
|
|
- @onConfirm(e)
|
|
|
+ row
|
|
|
+ .find('.checkbox-replacement-readonly')
|
|
|
+ .html(App.Utils.icon(if object.checked then 'checkbox-checked-readonly' else 'checkbox-readonly'))
|
|
|
|
|
|
- onCancel: (e) =>
|
|
|
- @preventDefaultAndStopPropagation(e)
|
|
|
- @releaseController()
|
|
|
+ @applyTextValue(object, row)
|
|
|
|
|
|
- onConfirm: (e) =>
|
|
|
- @preventDefaultAndStopPropagation(e)
|
|
|
+ # row
|
|
|
+ # .find('li[data-table-action=check],li[data-table-action=edit]')
|
|
|
+ # .toggleClass('hide', !!object.ticket_id)
|
|
|
|
|
|
- newValue = @input.val()
|
|
|
+ applyTextValue: (object, row, displayValue) =>
|
|
|
+ text = if object.text
|
|
|
+ App.Utils.linkify(object.text)
|
|
|
+ else if @readonly
|
|
|
+ '-'
|
|
|
|
|
|
- # Prevent AJAX if user has not changed the value
|
|
|
- if @originalValue == newValue
|
|
|
- @releaseController()
|
|
|
- return
|
|
|
- item = @object()
|
|
|
- item.text = newValue
|
|
|
+ return if text == row.data('text-value')
|
|
|
|
|
|
- @setDisabled(e.target, item.id)
|
|
|
+ row.data('text-value', text)
|
|
|
|
|
|
- item.save(
|
|
|
- done: =>
|
|
|
- @parentVC.renderTable()
|
|
|
- @parentVC.parentVC.subscribe()
|
|
|
- @originalValue = newValue
|
|
|
- fail: (settings, details) =>
|
|
|
- App.ChecklistItem.fetch(id: item.id)
|
|
|
+ return if row.find('.checklistItemEdit').length
|
|
|
|
|
|
- @notify(
|
|
|
- type: 'error'
|
|
|
- msg: App.i18n.translateContent(details.error)
|
|
|
- )
|
|
|
- @releaseController()
|
|
|
+ row.find('.checklistItemValue').html(text)
|
|
|
+ @setEnabled(row)
|
|
|
+
|
|
|
+ updateDisplayValue: (object) =>
|
|
|
+ row = @table.find("tbody tr[data-id=#{object.id}]")
|
|
|
+
|
|
|
+ if object.ticket_id
|
|
|
+ ticket = App.Ticket.find(object.ticket_id)
|
|
|
+ ticketAccess = if ticket then ticket.userGroupAccess('read') else false
|
|
|
+
|
|
|
+ displayValue = App.view('ticket_zoom/sidebar_checklist/show_row_display_value')(
|
|
|
+ object: object
|
|
|
+ ticket: ticket
|
|
|
+ ticketAccess: ticketAccess
|
|
|
+ readOnly: @readOnly
|
|
|
)
|
|
|
|
|
|
- onKeyUp: (e) =>
|
|
|
- switch e.key
|
|
|
- when 'Enter' then @onConfirm(e)
|
|
|
- when 'Escape' then @releaseController()
|
|
|
+ row
|
|
|
+ .find('li[data-table-action=check],li[data-table-action=edit]')
|
|
|
+ .toggleClass('hide', !!object.ticket_id)
|
|
|
|
|
|
-class ChecklistRenameEdit extends App.Controller
|
|
|
- elements:
|
|
|
- '.js-input': 'input'
|
|
|
- events:
|
|
|
- 'click .js-cancel': 'onCancel'
|
|
|
- 'click .js-confirm': 'onConfirm'
|
|
|
- 'blur .js-input': 'onBlur'
|
|
|
- 'keyup #checklistTitleEditText': 'onKeyUp'
|
|
|
+ row
|
|
|
+ .find('.checklistShowRowDisplayValue')
|
|
|
+ .html(displayValue)
|
|
|
|
|
|
- constructor: ->
|
|
|
- super
|
|
|
- @render()
|
|
|
+ @applyTextValue(object, row)
|
|
|
|
|
|
- releaseController: =>
|
|
|
- super
|
|
|
- @el.text(@originalValue)
|
|
|
- @parentVC.actionController = undefined
|
|
|
+ moveOrInsertRow: (id, row) =>
|
|
|
+ target_index = @checklist.sorted_item_ids.indexOf(id)
|
|
|
|
|
|
- setDisabled: (node) ->
|
|
|
- $(node).closest('tr').attr('disabled', true).addClass('u-unclickable u-low-opacity')
|
|
|
+ is_focused = $(row).find('.js-input').is(':focus')
|
|
|
|
|
|
- render: =>
|
|
|
- @html App.view('ticket_zoom/sidebar_checklist_title_edit')(
|
|
|
- value: @object()?.name
|
|
|
- ticketNumber: @parentVC.parentVC.ticket.number
|
|
|
- )
|
|
|
- @input.focus().val('').val(@object()?.name)
|
|
|
+ if is_focused
|
|
|
+ $(row).data('skip-blur', true)
|
|
|
|
|
|
- object: =>
|
|
|
- @parentVC.checklist
|
|
|
+ if target_index == 0
|
|
|
+ @table
|
|
|
+ .find('tbody')
|
|
|
+ .prepend(row)
|
|
|
+ else
|
|
|
+ preceding_id = @checklist.sorted_item_ids[target_index - 1]
|
|
|
|
|
|
- onBlur: (e) =>
|
|
|
- if $(e.originalEvent.relatedTarget).hasClass('js-cancel')
|
|
|
- @onCancel(e)
|
|
|
- return
|
|
|
+ @table
|
|
|
+ .find("tbody tr[data-id=#{preceding_id}]")
|
|
|
+ .after(row)
|
|
|
|
|
|
- @onConfirm(e)
|
|
|
+ if is_focused
|
|
|
+ $(row).find('.js-input').focus()
|
|
|
+ $(row).data('skip-blur', false)
|
|
|
|
|
|
- onCancel: (e) =>
|
|
|
- @preventDefaultAndStopPropagation(e)
|
|
|
- @releaseController()
|
|
|
+ cleanUpDeleted: =>
|
|
|
+ rendered_ids = @table
|
|
|
+ .find('tbody tr')
|
|
|
+ .toArray()
|
|
|
+ .map (el) -> $(el).data('id')?.toString()
|
|
|
|
|
|
- onConfirm: (e) =>
|
|
|
- @preventDefaultAndStopPropagation(e)
|
|
|
- @setDisabled(e.target)
|
|
|
+ for id in _.difference(rendered_ids, @checklist.sorted_item_ids)
|
|
|
+ @table
|
|
|
+ .find("tbody tr[data-id=#{id}]")
|
|
|
+ .remove()
|
|
|
|
|
|
- checklist = @object()
|
|
|
- checklist.name = @input.val()
|
|
|
- checklist.save(
|
|
|
- done: =>
|
|
|
- @parentVC.render()
|
|
|
- )
|
|
|
+ renderEditMode: =>
|
|
|
+ if @enterEditMode
|
|
|
+ @enterEditMode = undefined
|
|
|
+ @enterEditModeId = @table.find('tbody tr:last-of-type').data('id')
|
|
|
|
|
|
- onKeyUp: (e) =>
|
|
|
- switch e.key
|
|
|
- when 'Enter' then @onConfirm(e)
|
|
|
- when 'Escape' then @onCancel()
|
|
|
+ if @enterEditModeId
|
|
|
+ cell = @table.find("tbody tr[data-id='" + @enterEditModeId + "']").find('.checklistItemValue')[0]
|
|
|
+ row = $(cell).closest('tr')
|
|
|
+ return if !row.length
|
|
|
+ @enterEditModeId = undefined
|
|
|
+ @activateItemEditMode(cell, row, row.data('id'))
|
|
|
+
|
|
|
+ renderSortable: =>
|
|
|
+ dndOptions =
|
|
|
+ tolerance: 'pointer'
|
|
|
+ distance: 15
|
|
|
+ opacity: 0.6
|
|
|
+ forcePlaceholderSize: true
|
|
|
+ items: 'tr'
|
|
|
+ @table.find('tbody').sortable(dndOptions)
|
|
|
+ @table.find('tbody').sortable('disable')
|
|
|
|
|
|
class ChecklistReorder extends App.Controller
|
|
|
constructor: ->
|
|
@@ -433,6 +466,7 @@ class ChecklistReorder extends App.Controller
|
|
|
releaseController: =>
|
|
|
@parentVC.toggleReorder(false, 'cancel')
|
|
|
@parentVC.actionController = undefined
|
|
|
+ @parentVC.renderTable()
|
|
|
|
|
|
completed: =>
|
|
|
@parentVC.toggleReorder(false)
|