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:
parent
a76d00d564
commit
08d6c966cf
|
@ -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')
|
|
@ -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
|
Loading…
Reference in New Issue