decaf TreeView.coffee

test plan:
  - existing tests pass
  - substantial comments are preserved

flag=none

refs FOO-3470

Change-Id: I5114704a1e2408789cf2cd6b659589730ffc408d
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/316472
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Omar Soto-Fortuño <omar.soto@instructure.com>
QA-Review: Omar Soto-Fortuño <omar.soto@instructure.com>
Product-Review: Omar Soto-Fortuño <omar.soto@instructure.com>
This commit is contained in:
Aaron Shafovaloff 2023-04-19 17:31:12 -06:00
parent a76d00d564
commit 08d6c966cf
2 changed files with 225 additions and 166 deletions

View File

@ -1,166 +0,0 @@
#
# Copyright (C) 2012 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas 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, version 3 of the License.
#
# Canvas 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/>.
import Backbone from '@canvas/backbone'
import $ from 'jquery'
import _ from 'underscore'
import PaginatedCollectionView from '@canvas/pagination/backbone/views/PaginatedCollectionView'
import TreeItemView from './TreeItemView'
import collectionTemplate from '../../jst/TreeCollection.handlebars'
import htmlEscape from 'html-escape'
export default class TreeView extends Backbone.View
tagName: 'li'
@optionProperty 'nestingLevel'
@optionProperty 'onlyShowSubtrees'
@optionProperty 'onClick'
@optionProperty 'dndOptions'
@optionProperty 'href'
@optionProperty 'focusStyleClass'
@optionProperty 'selectedStyleClass'
@optionProperty 'autoFetch'
@optionProperty 'fetchItAll'
defaults:
nestingLevel: 1
attributes: ->
'role': 'treeitem'
'data-id': @model.id
'aria-expanded': "#{!!@model.isExpanded}"
'aria-level': @nestingLevel
'aria-label': @model.get('custom_name') || @model.get('name') || @model.get('title')
id: @tagId
events:
'click .treeLabel': 'toggle'
'selectItem .treeFile, .treeLabel': 'selectItem'
initialize: ->
@tagId = _.uniqueId 'treenode-'
@render = _.debounce(@render)
@model.on 'all', @render, this
@model.getItems().on 'all', @render, this
@model.getSubtrees().on 'all', @render, this
res = super
@render()
res
render: ->
@renderSelf()
@renderContents()
toggle: (event) ->
# prevent it from bubbling up to parents and from following link
event.preventDefault()
event.stopPropagation()
@model.toggle({onlyShowSubtrees: @onlyShowSubtrees})
@$el.attr(@attributes())
selectItem: (event) ->
$span = $(event.target).find('span')
$span.trigger('click')
title_text: ->
@model.get('custom_name') || @model.get('name') || @model.get('title')
renderSelf: ->
return if @model.isNew()
@$el.attr @attributes()
@$label ||= do =>
@$labelInner = $('<span>').click (event) =>
if (@selectedStyleClass)
$('.' + _this.selectedStyleClass).each((key, element) => $(element).removeClass(@selectedStyleClass))
$(event.target).addClass(@selectedStyleClass)
@onClick?(event, @model)
icon_class = if @model.get('for_submissions') then 'icon-folder-locked' else 'icon-folder'
$label = $("""
<a
class="treeLabel"
role="presentation"
tabindex="-1"
>
<i class="icon-mini-arrow-right"></i>
<i class="#{htmlEscape(icon_class)}"></i>
</a>
""").append(@$labelInner).prependTo(@$el)
if @dndOptions && !@model.get('for_submissions')
toggleActive = (makeActive) ->
return -> $label.toggleClass('activeDragTarget', makeActive)
$label.on
'dragenter dragover': (event) =>
@dndOptions.onItemDragEnterOrOver(event.originalEvent, toggleActive(true))
'dragleave dragend': (event) =>
@dndOptions.onItemDragLeaveOrEnd(event.originalEvent, toggleActive(false))
'drop': (event) =>
@dndOptions.onItemDrop(event.originalEvent, @model, toggleActive(false))
return $label
@$labelInner.text(@title_text())
@$label
.attr('href', @href?(@model) || '#')
.toggleClass('expanded', !!@model.isExpanded)
.toggleClass('loading after', !!@model.isExpanding)
# Lets this work well with file browsers like New Files
if (@selectedStyleClass)
@$label.toggleClass(@selectedStyleClass, window.location.pathname is @href?(@model))
renderContents: ->
if @model.isExpanded
unless @$treeContents
@$treeContents = $("<ul role='group' class='treeContents'/>").appendTo(@$el)
subtreesView = new PaginatedCollectionView(
collection: @model.getSubtrees()
itemView: TreeView
itemViewOptions:
nestingLevel: @nestingLevel+1
onlyShowSubtrees: @onlyShowSubtrees
onClick: @onClick
dndOptions: @dndOptions
href: @href
focusStyleClass: @focusStyleClass
selectedStyleClass: @selectedStyleClass
autoFetch: @autoFetch
fetchItAll: @fetchItAll
tagName: 'li'
className: 'subtrees'
template: collectionTemplate
scrollContainer: @$treeContents.closest('div[role=tabpanel]')
autoFetch: @autoFetch
fetchItAll: @fetchItAll
)
@$treeContents.append(subtreesView.render().el)
unless @onlyShowSubtrees
itemsView = new PaginatedCollectionView(
collection: @model.getItems()
itemView: TreeItemView
itemViewOptions: {nestingLevel: @nestingLevel+1}
tagName: 'li'
className: 'items'
template: collectionTemplate
scrollContainer: @$treeContents.closest('div[role=tabpanel]')
)
@$treeContents.append(itemsView.render().el)
@$('> .treeContents').removeClass('hidden')
else
@$('> .treeContents').addClass('hidden')

View File

@ -0,0 +1,225 @@
/*
* Copyright (C) 2023 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas 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, version 3 of the License.
*
* Canvas 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/>.
*/
/* eslint-disable no-void */
import {extend} from '@canvas/backbone/utils'
import Backbone from '@canvas/backbone'
import $ from 'jquery'
import _ from 'underscore'
import PaginatedCollectionView from '@canvas/pagination/backbone/views/PaginatedCollectionView'
import TreeItemView from './TreeItemView'
import collectionTemplate from '../../jst/TreeCollection.handlebars'
import htmlEscape from 'html-escape'
extend(TreeView, Backbone.View)
function TreeView() {
return TreeView.__super__.constructor.apply(this, arguments)
}
TreeView.prototype.tagName = 'li'
TreeView.optionProperty('nestingLevel')
TreeView.optionProperty('onlyShowSubtrees')
TreeView.optionProperty('onClick')
TreeView.optionProperty('dndOptions')
TreeView.optionProperty('href')
TreeView.optionProperty('focusStyleClass')
TreeView.optionProperty('selectedStyleClass')
TreeView.optionProperty('autoFetch')
TreeView.optionProperty('fetchItAll')
TreeView.prototype.defaults = {
nestingLevel: 1,
}
TreeView.prototype.attributes = function () {
return {
role: 'treeitem',
'data-id': this.model.id,
'aria-expanded': '' + !!this.model.isExpanded,
'aria-level': this.nestingLevel,
'aria-label':
this.model.get('custom_name') || this.model.get('name') || this.model.get('title'),
id: this.tagId,
}
}
TreeView.prototype.events = {
'click .treeLabel': 'toggle',
'selectItem .treeFile, .treeLabel': 'selectItem',
}
TreeView.prototype.initialize = function () {
this.tagId = _.uniqueId('treenode-')
this.render = _.debounce(this.render)
this.model.on('all', this.render, this)
this.model.getItems().on('all', this.render, this)
this.model.getSubtrees().on('all', this.render, this)
const res = TreeView.__super__.initialize.apply(this, arguments)
this.render()
return res
}
TreeView.prototype.render = function () {
this.renderSelf()
return this.renderContents()
}
TreeView.prototype.toggle = function (event) {
// prevent it from bubbling up to parents and from following link
event.preventDefault()
event.stopPropagation()
this.model.toggle({
onlyShowSubtrees: this.onlyShowSubtrees,
})
return this.$el.attr(this.attributes())
}
TreeView.prototype.selectItem = function (event) {
const $span = $(event.target).find('span')
return $span.trigger('click')
}
TreeView.prototype.title_text = function () {
return this.model.get('custom_name') || this.model.get('name') || this.model.get('title')
}
TreeView.prototype.renderSelf = function () {
if (this.model.isNew()) {
return
}
this.$el.attr(this.attributes())
this.$label ||
(this.$label = (function (_this) {
return function () {
_this.$labelInner = $('<span>').click(function (event) {
// Lets this work well with file browsers like New Files
if (_this.selectedStyleClass) {
$('.' + _this.selectedStyleClass).each(function (key, element) {
return $(element).removeClass(_this.selectedStyleClass)
})
$(event.target).addClass(_this.selectedStyleClass)
}
return typeof _this.onClick === 'function' ? _this.onClick(event, _this.model) : void 0
})
const icon_class = _this.model.get('for_submissions') ? 'icon-folder-locked' : 'icon-folder'
const $label = $(
'<a\n class="treeLabel"\n role="presentation"\n tabindex="-1"\n>\n <i class="icon-mini-arrow-right"></i>\n <i class="' +
htmlEscape(icon_class) +
'"></i>\n</a>'
)
.append(_this.$labelInner)
.prependTo(_this.$el)
if (_this.dndOptions && !_this.model.get('for_submissions')) {
const toggleActive = function (makeActive) {
return function () {
return $label.toggleClass('activeDragTarget', makeActive)
}
}
$label.on({
'dragenter dragover': function (event) {
return _this.dndOptions.onItemDragEnterOrOver(event.originalEvent, toggleActive(true))
},
'dragleave dragend': function (event) {
return _this.dndOptions.onItemDragLeaveOrEnd(event.originalEvent, toggleActive(false))
},
drop(event) {
return _this.dndOptions.onItemDrop(
event.originalEvent,
_this.model,
toggleActive(false)
)
},
})
}
return $label
}
})(this)())
this.$labelInner.text(this.title_text())
this.$label
.attr('href', (typeof this.href === 'function' ? this.href(this.model) : void 0) || '#')
.toggleClass('expanded', !!this.model.isExpanded)
.toggleClass('loading after', !!this.model.isExpanding)
if (this.selectedStyleClass) {
return this.$label.toggleClass(
this.selectedStyleClass,
window.location.pathname ===
(typeof this.href === 'function' ? this.href(this.model) : void 0)
)
}
}
TreeView.prototype.renderContents = function () {
let itemsView, subtreesView
if (this.model.isExpanded) {
if (!this.$treeContents) {
this.$treeContents = $("<ul role='group' class='treeContents'/>").appendTo(this.$el)
subtreesView = new PaginatedCollectionView({
collection: this.model.getSubtrees(),
itemView: TreeView,
itemViewOptions: {
nestingLevel: this.nestingLevel + 1,
onlyShowSubtrees: this.onlyShowSubtrees,
onClick: this.onClick,
dndOptions: this.dndOptions,
href: this.href,
focusStyleClass: this.focusStyleClass,
selectedStyleClass: this.selectedStyleClass,
autoFetch: this.autoFetch,
fetchItAll: this.fetchItAll,
},
tagName: 'li',
className: 'subtrees',
template: collectionTemplate,
scrollContainer: this.$treeContents.closest('div[role=tabpanel]'),
autoFetch: this.autoFetch,
fetchItAll: this.fetchItAll,
})
this.$treeContents.append(subtreesView.render().el)
if (!this.onlyShowSubtrees) {
itemsView = new PaginatedCollectionView({
collection: this.model.getItems(),
itemView: TreeItemView,
itemViewOptions: {
nestingLevel: this.nestingLevel + 1,
},
tagName: 'li',
className: 'items',
template: collectionTemplate,
scrollContainer: this.$treeContents.closest('div[role=tabpanel]'),
})
this.$treeContents.append(itemsView.render().el)
}
}
return this.$('> .treeContents').removeClass('hidden')
} else {
return this.$('> .treeContents').addClass('hidden')
}
}
export default TreeView