From 08d6c966cfb58a2febc89d3d9d2284ef58016f67 Mon Sep 17 00:00:00 2001 From: Aaron Shafovaloff Date: Wed, 19 Apr 2023 17:31:12 -0600 Subject: [PATCH] decaf TreeView.coffee MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Reviewed-by: Omar Soto-Fortuño QA-Review: Omar Soto-Fortuño Product-Review: Omar Soto-Fortuño --- .../backbone/views/TreeView.coffee | 166 ------------- .../backbone/views/TreeView.js | 225 ++++++++++++++++++ 2 files changed, 225 insertions(+), 166 deletions(-) delete mode 100644 ui/shared/tree-browser-view/backbone/views/TreeView.coffee create mode 100644 ui/shared/tree-browser-view/backbone/views/TreeView.js diff --git a/ui/shared/tree-browser-view/backbone/views/TreeView.coffee b/ui/shared/tree-browser-view/backbone/views/TreeView.coffee deleted file mode 100644 index c673dfd70da..00000000000 --- a/ui/shared/tree-browser-view/backbone/views/TreeView.coffee +++ /dev/null @@ -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 . - -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 = $('').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 = $(""" - - - - - """).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 = $("
    ").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') diff --git a/ui/shared/tree-browser-view/backbone/views/TreeView.js b/ui/shared/tree-browser-view/backbone/views/TreeView.js new file mode 100644 index 00000000000..5cd08d0879c --- /dev/null +++ b/ui/shared/tree-browser-view/backbone/views/TreeView.js @@ -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 . + */ + +/* 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 = $('').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 = $( + '\n \n \n' + ) + .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 = $("
      ").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