From e4e426e30762542accb3d817fd360bf7ff2f7f40 Mon Sep 17 00:00:00 2001 From: Brad Simpson Date: Mon, 16 Jan 2023 03:54:42 -0700 Subject: [PATCH] Fix: Convert template to JSX (fixes #89) (#90) --- .github/pull_request_template.md | 2 +- README.md | 12 +-- js/ResourcesHelpers.js | 42 ----------- js/ResourcesView.js | 41 ++--------- js/adapt-contrib-resources.js | 1 - templates/resources.hbs | 78 -------------------- templates/resources.jsx | 109 ++++++++++++++++++++++++++++ templates/resourcesFilterButton.jsx | 37 ++++++++++ templates/resourcesItem.jsx | 63 ++++++++++++++++ 9 files changed, 222 insertions(+), 163 deletions(-) delete mode 100644 js/ResourcesHelpers.js delete mode 100644 templates/resources.hbs create mode 100644 templates/resources.jsx create mode 100644 templates/resourcesFilterButton.jsx create mode 100644 templates/resourcesItem.jsx diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 750a692..f641119 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -8,7 +8,7 @@ * A sentence describing each fix ### Update -* A sentence describing each udpate +* A sentence describing each update ### New * A sentence describing each new feature diff --git a/README.md b/README.md index ed10676..3bdcc9b 100644 --- a/README.md +++ b/README.md @@ -81,9 +81,9 @@ The attributes listed below are used in *course.json* to configure **Resources** No known limitations. ---------------------------- -**Version number:** 5.0.1 adapt learning logo -**Framework versions:** 5.19.1+ -**Author / maintainer:** Adapt Core Team with [contributors](https://github.com/adaptlearning/adapt-contrib-resources/graphs/contributors) -**Accessibility support:** WAI AA -**RTL support:** Yes -**Cross-platform coverage:** Chrome, Chrome for Android, Firefox (ESR + latest version), Edge, IE11, Safari 12+13 for macOS/iOS/iPadOS, Opera +**Version number:** 5.3.2 Adapt Learning logo +**Framework versions:** 5.19.1+ +**Author / maintainer:** Adapt Core Team with [contributors](https://github.com/adaptlearning/adapt-contrib-resources/graphs/contributors) +**Accessibility support:** WAI AA +**RTL support:** Yes +**Cross-platform coverage:** Chrome, Chrome for Android, Firefox (ESR + latest version), Edge, IE11, Safari 12+13 for macOS/iOS/iPadOS, Opera diff --git a/js/ResourcesHelpers.js b/js/ResourcesHelpers.js deleted file mode 100644 index 9b1c8b3..0000000 --- a/js/ResourcesHelpers.js +++ /dev/null @@ -1,42 +0,0 @@ -import Handlebars from 'handlebars'; -import device from 'core/js/device'; - -const helpers = { - - resources_has_type(resources, type, block) { - const hasType = resources.some(_.matcher({ _type: type })); - return hasType ? block.fn(this) : block.inverse(this); - }, - - resources_has_multiple_types(resources, block) { - if (resources.length === 1) return block.inverse(this); - - const allSameType = resources.every(_.matcher({ _type: resources[0]._type })); - return allSameType ? block.inverse(this) : block.fn(this); - }, - - resources_get_column_count(resources) { - return _.uniq(_.pluck(resources, '_type')).length + 1;// add 1 for the 'All' button column - }, - - /** - * IE doesn't support the 'download' attribute - * https://github.com/adaptlearning/adapt_framework/issues/1559 - * and iOS just opens links with that attribute in the same window - * https://github.com/adaptlearning/adapt_framework/issues/1852 - */ - resources_force_download(resource, block) { - if (device.browser === 'internet explorer' || device.OS === 'ios') { - return block.inverse(this); - } - - return (resource._forceDownload || resource.filename) ? block.fn(this) : block.inverse(this); - } - -}; - -for (const name in helpers) { - Handlebars.registerHelper(name, helpers[name]); -} - -export default helpers; diff --git a/js/ResourcesView.js b/js/ResourcesView.js index 1ceecce..3c04cf0 100644 --- a/js/ResourcesView.js +++ b/js/ResourcesView.js @@ -1,5 +1,7 @@ import Adapt from 'core/js/adapt'; -import a11y from 'core/js/a11y'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import { templates } from 'core/js/reactHelpers'; export default class ResourcesView extends Backbone.View { @@ -12,17 +14,12 @@ export default class ResourcesView extends Backbone.View { this.render(); } - events() { - return { - 'click .js-resources-filter-btn-click': 'onFilterClicked' - }; - } - render() { - this.$el.html(Handlebars.templates.resources({ + const data = { model: this.model.toJSON(), resources: this.model.get('_resources') - })); + }; + ReactDOM.render(, this.el); _.defer(() => { this.listenTo(Adapt, 'drawer:closed', this.remove); @@ -31,30 +28,4 @@ export default class ResourcesView extends Backbone.View { return this; } - onFilterClicked(e) { - if (e && e.preventDefault) e.preventDefault(); - - const $resources = this.$('#resources'); - const $clickedButton = this.$(e.currentTarget); - const clickedTabId = $clickedButton.attr('id'); - - this.$('.js-resources-filter-btn-click').removeClass('is-selected').attr('aria-selected', false); - - $resources.attr('aria-labelledby', clickedTabId); - $clickedButton.attr('aria-selected', true); - - let items; - const filter = $clickedButton.addClass('is-selected').attr('data-filter'); - if (filter === 'all') { - items = this.$('.js-resources-item').removeClass('u-display-none'); - } else { - this.$('.js-resources-item') - .removeClass('u-display-none').not('.is-' + filter) - .addClass('u-display-none'); - items = this.$('.js-resources-item.is-' + filter); - } - - if (items.length < 0) return; - a11y.focusFirst($(items[0])); - } } diff --git a/js/adapt-contrib-resources.js b/js/adapt-contrib-resources.js index fedfeab..a5db4eb 100644 --- a/js/adapt-contrib-resources.js +++ b/js/adapt-contrib-resources.js @@ -1,7 +1,6 @@ import Adapt from 'core/js/adapt'; import drawer from 'core/js/drawer'; import ResourcesView from './ResourcesView'; -import './ResourcesHelpers'; class Resources extends Backbone.Controller { diff --git a/templates/resources.hbs b/templates/resources.hbs deleted file mode 100644 index 78c386b..0000000 --- a/templates/resources.hbs +++ /dev/null @@ -1,78 +0,0 @@ -{{! make the _globals object in course.json available to this template}} -{{import_globals}} - -
- - {{#resources_has_multiple_types resources}} -
-
- -
- - - - {{#resources_has_type resources 'document'}} - - {{/resources_has_type}} - - {{#resources_has_type resources 'media'}} - - {{/resources_has_type}} - - {{#resources_has_type resources 'link'}} - - {{/resources_has_type}} - -
-
- {{/resources_has_multiple_types}} - - - -
diff --git a/templates/resources.jsx b/templates/resources.jsx new file mode 100644 index 0000000..77cb1df --- /dev/null +++ b/templates/resources.jsx @@ -0,0 +1,109 @@ +import React, { useState, useEffect } from 'react'; +import Adapt from 'core/js/adapt'; +import a11y from 'core/js/a11y'; +import { classes, templates } from 'core/js/reactHelpers'; + +export default function Resources (props) { + const { + resources + } = props; + + const _globals = Adapt.course.get('_globals'); + const resourceTypes = ['all', 'document', 'media', 'link']; // must contain 'all' + + function resourcesHasMultipleTypes(resources) { + if (resources.length === 1) return false; + + const allSameType = resources.every(_.matcher({ _type: resources[0]._type })); + return !allSameType; + } + + function resourcesGetColumnCount(resources) { + return _.uniq(_.pluck(resources, '_type')).length + 1; // add 1 for the 'All' button column + } + + const [selectedFilter, setSelectedFilter] = useState('all'); + const [selectedId, setSelectedId] = useState('resources__show-all'); + const [focusFlag, setFocusFlag] = useState(false); + + useEffect(() => { + if (focusFlag) { + let $items; + if (selectedFilter === 'all') { + $items = $('.resources__item'); + } else { + $items = $('.resources__item.is-' + selectedFilter); + } + + if ($items.length < 0) return; + + a11y.focusFirst($items); + + setFocusFlag(false); + } + }, [focusFlag]); + + const onFilterClicked = e => { + if (e && e.preventDefault) e.preventDefault(); + + const $clickedButton = this.$(e.currentTarget); + const filter = $clickedButton.data('filter'); + const id = $clickedButton.attr('id'); + + setSelectedFilter(filter); + setSelectedId(id); + setFocusFlag(true); + }; + + return ( +
+ + + + {resourcesHasMultipleTypes(resources) && +
+
+ +
+ + {resourceTypes.map((type, index) => + + )} + +
+
+ } + +
+ +
+ + {resources.map(({ title, description, _link, _type, _isGlobal, filename, _forceDownload }, index) => + + )}; + +
+ +
+ +
+ + ); +} diff --git a/templates/resourcesFilterButton.jsx b/templates/resourcesFilterButton.jsx new file mode 100644 index 0000000..346bfc8 --- /dev/null +++ b/templates/resourcesFilterButton.jsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { classes } from 'core/js/reactHelpers'; + +export default function ResourcesFilterButton (props) { + const { + model, + onClick, + resources, + selected, + _filter + } = props; + + const buttonText = model._filterButtons[_filter]; + const ariaLabel = model._filterAria[`${_filter}Aria`]; + + function resourcesHasType(resources, type) { + return resources.some(_.matcher({ _type: type })); + } + + if (!resourcesHasType(resources, _filter) && _filter !== 'all') return null; + + return ( +