diff --git a/js/collections/adaptCollection.js b/js/collections/adaptCollection.js index deb9a96e..d53a7f7d 100644 --- a/js/collections/adaptCollection.js +++ b/js/collections/adaptCollection.js @@ -1,5 +1,19 @@ +/** + * @file AdaptCollection - Base Backbone collection for Adapt data + * @module core/js/collections/adaptCollection + * @description Base collection class for Adapt Framework data sets. Fires + * `adaptCollection:dataLoaded` on its first reset, signalling downstream + * modules that the collection's data is available. + */ import Adapt from 'core/js/adapt'; +/** + * @class AdaptCollection + * @classdesc Base Backbone collection used throughout the Adapt Framework. + * Fires `adaptCollection:dataLoaded` on its first reset, allowing dependent + * modules to react when the collection's data is available. + * @extends {Backbone.Collection} + */ export default class AdaptCollection extends Backbone.Collection { initialize(models, options) { diff --git a/js/collections/adaptSubsetCollection.js b/js/collections/adaptSubsetCollection.js index 92c395c1..6c61a85d 100644 --- a/js/collections/adaptSubsetCollection.js +++ b/js/collections/adaptSubsetCollection.js @@ -1,5 +1,21 @@ +/** + * @file AdaptSubsetCollection - Filtered subset of a parent AdaptCollection + * @module core/js/collections/adaptSubsetCollection + * @description A filtered view of a parent {@link module:core/js/collections/adaptCollection}. + * Automatically re-filters its contents whenever the parent collection resets, + * keeping only models that are instances of this collection's model type. + */ import AdaptCollection from 'core/js/collections/adaptCollection'; +/** + * @class AdaptSubsetCollection + * @classdesc Maintains a live, filtered subset of a parent + * {@link module:core/js/collections/adaptCollection|AdaptCollection}. When the parent + * resets, `loadSubset` rebuilds the subset by retaining only models that are + * instances of `this.model`. Also builds a `_byAdaptID` lookup map for fast + * retrieval by `_id`. + * @extends {AdaptCollection} + */ export default class AdaptSubsetCollection extends AdaptCollection { initialize(models, options) { @@ -8,6 +24,11 @@ export default class AdaptSubsetCollection extends AdaptCollection { this.listenTo(this.parent, 'reset', this.loadSubset); } + /** + * Rebuilds the subset from the parent collection, keeping only models that + * are instances of `this.model`. Also indexes the subset by `_id` in + * `this._byAdaptID` for fast lookup. + */ loadSubset() { this.set(this.parent.filter(model => model instanceof this.model)); this._byAdaptID = this.groupBy('_id'); diff --git a/js/models/adaptModel.js b/js/models/adaptModel.js index ba7af3fd..978fc9e5 100644 --- a/js/models/adaptModel.js +++ b/js/models/adaptModel.js @@ -1,3 +1,29 @@ +/** + * @file AdaptModel - Base data model for all Adapt content + * @module core/js/models/adaptModel + * @description Base Backbone model for every content item in the Adapt Framework + * (course, contentobject, article, block, component). Extends + * {@link module:core/js/models/lockingModel|LockingModel} to provide cooperative + * locking, and adds hierarchy traversal, trackable state, completion checking, + * and accessibility label support. + * + * **Key Responsibilities:** + * - Hierarchy traversal: `getParent`, `getChildren`, `getAncestorModels`, `getSiblings`, `findDescendantModels` + * - Completion checking: `checkCompletionStatus`, `checkInteractionCompletionStatus` + * - Trackable state: `getTrackableState`, `setTrackableState`, `triggerTrackableState` + * - Type group queries: `isTypeGroup`, `getTypeGroups` + * - Relative navigation: `findRelativeModel` + * + * **State Properties:** + * - `_isComplete` {boolean} - Model has been completed + * - `_isInteractionComplete` {boolean} - Interaction portion is complete + * - `_isReady` {boolean} - All rendered children are ready + * - `_isRendered` {boolean} - View has been rendered + * - `_isAvailable` {boolean} - Model is included in the course + * - `_isVisible` {boolean} - Model is shown in the UI + * - `_isLocked` {boolean} - Model is locked (lockable) + * - `_isOptional` {boolean} - Model does not count towards parent completion + */ import Adapt from 'core/js/adapt'; import data from 'core/js/data'; import ModelEvent from 'core/js/modelEvent'; @@ -5,8 +31,23 @@ import LockingModel from 'core/js/models/lockingModel'; import logging from 'core/js/logging'; import { toggleModelClass } from '../modelHelpers'; +/** + * @class AdaptModel + * @classdesc Base data model for all Adapt content items. Provides hierarchy + * traversal, completion checking, trackable state management, and type group + * querying. Subclassed by course, contentobject, article, block, and component + * models. + * @extends {LockingModel} + */ export default class AdaptModel extends LockingModel { + /** + * Serializes the model attributes to a plain JSON object. + * Performs a deep clone and removes the `_children` and `_parent` references, + * which are Backbone collections/models and not valid JSON. + * @override + * @returns {Object} Deep-cloned plain object of model attributes + */ toJSON() { // Perform shallow clone const json = { ...this.attributes }; @@ -17,6 +58,14 @@ export default class AdaptModel extends LockingModel { return $.extend(true, {}, json); } + /** + * Overrides Backbone `get` to emit deprecation warnings when accessing + * `_children` or `_parent` directly. + * Use {@link getChildren} and {@link getParent} instead. + * @override + * @param {string} name - Attribute name + * @returns {*} Attribute value + */ get(name) { switch (name) { case '_parent': @@ -57,8 +106,11 @@ export default class AdaptModel extends LockingModel { } /** - * Fetch an array representing the relative location of the model to the nearest _trackingId - * @returns {Array} + * Returns the relative location of this model to the nearest `_trackingId` ancestor + * as a two-element tuple `[trackingId, indexOffset]`. `indexOffset` is ≥ 0 when this + * model is the tracking-id model or one of its flattened descendants, and negative + * when it is an ancestor. + * @returns {[number, number]|undefined} */ get trackingPosition() { const firstDescendant = this.getAllDescendantModels(false).concat([this])[0]; @@ -79,9 +131,12 @@ export default class AdaptModel extends LockingModel { } /** - * The AAT always sets the value of `_isResetOnRevisit` to a String - * which is fine for the 'soft' and 'hard' values - but 'false' needs - * converting to Boolean - see #2825 + * Pre-processes raw data before setting it on the model. + * Converts the string `'false'` value of `_isResetOnRevisit` to a Boolean, + * working around an AAT serialization issue (see #2825). + * @override + * @param {Object} data - Raw model data + * @returns {Object} Processed data */ parse(data) { if (data._isResetOnRevisit === 'false') { @@ -90,6 +145,11 @@ export default class AdaptModel extends LockingModel { return data; } + /** + * Returns the list of attribute names that should be persisted to the LMS. + * Override in subclasses to extend the trackable attribute set. + * @returns {Array} Trackable attribute names + */ trackable() { return [ '_id', @@ -99,6 +159,11 @@ export default class AdaptModel extends LockingModel { ]; } + /** + * Returns the expected constructor types for each trackable attribute, + * in the same order as {@link trackable}. + * @returns {Array} Constructor functions (e.g. `String`, `Boolean`) + */ trackableType() { return [ String, @@ -108,6 +173,11 @@ export default class AdaptModel extends LockingModel { ]; } + /** + * Returns the list of events that should bubble up through the model hierarchy. + * Override in subclasses to extend the bubbling event set. + * @returns {Array} Backbone event names to bubble + */ bubblingEvents() { return [ 'change:_isComplete', @@ -118,9 +188,10 @@ export default class AdaptModel extends LockingModel { } /** - * Toggle a className in the _classes attribute - * @param className {string} Name or names of class to add/remove to _classes attribute, space separated list - * @param hasClass {boolean|null|undefined} true to add a class, false to remove, null or undefined to toggle + * Toggles a class name in the `_classes` model attribute. + * @param {string} className - Class name(s) to add/remove, space-separated + * @param {boolean|null} [hasClass] - `true` to add, `false` to remove, omit to toggle + * @returns {AdaptModel} Returns this for chaining */ toggleClass(className, hasClass) { toggleModelClass(this, className, hasClass); @@ -205,6 +276,11 @@ export default class AdaptModel extends LockingModel { init() {} + /** + * Returns a plain object containing only the trackable attributes and their + * current values, as declared by {@link trackable}. + * @returns {Object} Trackable attribute snapshot + */ getTrackableState() { const trackable = this.resultExtend('trackable', []); @@ -217,6 +293,12 @@ export default class AdaptModel extends LockingModel { } + /** + * Applies a previously captured trackable state snapshot to this model. + * Only attributes declared by {@link trackable} are applied. + * @param {Object} state - Attribute snapshot, typically from `getTrackableState` + * @returns {AdaptModel} Returns this for chaining + */ setTrackableState(state) { const trackable = this.resultExtend('trackable', []); @@ -232,6 +314,11 @@ export default class AdaptModel extends LockingModel { } + /** + * Fires the `state:change` event on Adapt with this model and its current + * trackable state snapshot. Debounced in practice via `setupTrackables`. + * @fires state:change + */ triggerTrackableState() { Adapt.trigger('state:change', this, this.getTrackableState()); @@ -239,9 +326,12 @@ export default class AdaptModel extends LockingModel { } /** - * @param {string} [type] 'hard' resets _isComplete and _isInteractionComplete, 'soft' resets _isInteractionComplete only. - * @param {boolean} [canReset] Defaults to this.get('_canReset') - * @returns {boolean} + * Resets the model's completion state. A hard reset clears both `_isComplete` + * and `_isInteractionComplete`; a soft reset clears `_isInteractionComplete` only. + * Returns `false` and does nothing if `canReset` is `false` or `type` is unrecognised. + * @param {string} [type='hard'] Reset type: `'hard'` or `'soft'` + * @param {boolean} [canReset=this.get('_canReset')] Override the model's reset guard + * @returns {boolean} `true` if the reset was applied, `false` otherwise */ reset(type = 'hard', canReset = this.get('_canReset')) { if (!canReset) return false; @@ -288,6 +378,11 @@ export default class AdaptModel extends LockingModel { this.set('_isReady', true); } + /** + * Checks child models and sets `_isVisited` to `true` if any child has been + * visited, completed, or interaction-completed. + * @returns {boolean} Whether any child is considered visited + */ checkVisitedStatus() { const children = this.getAvailableChildModels(); const isVisited = children.some(child => child.get('_isVisited') || child.get('_isComplete') || child.get('_isInteractionComplete')); @@ -300,6 +395,10 @@ export default class AdaptModel extends LockingModel { this.set('_isVisited', true); } + /** + * Sets this model as complete, interaction-complete, and visited. + * Has no effect if the model is not visible. + */ setCompletionStatus() { if (!this.get('_isVisible')) return; @@ -310,12 +409,20 @@ export default class AdaptModel extends LockingModel { }); } + /** + * Schedules a deferred check of `_isComplete` across child models. + * Defers to allow other `change:_isComplete` handlers to fire first. + */ checkCompletionStatus() { // defer to allow other change:_isComplete handlers to fire before cascading to parent Adapt.checkingCompletion(); _.defer(this.checkCompletionStatusFor.bind(this), '_isComplete'); } + /** + * Schedules a deferred check of `_isInteractionComplete` across child models. + * Defers to allow other `change:_isInteractionComplete` handlers to fire first. + */ checkInteractionCompletionStatus() { // defer to allow other change:_isInteractionComplete handlers to fire before cascading to parent Adapt.checkingCompletion(); @@ -407,10 +514,10 @@ export default class AdaptModel extends LockingModel { } /** - * Searches the model's ancestors to find the first instance of the specified ancestor type - * @param {string} [ancestorType] Valid values are 'course', 'pages', 'contentObjects', 'articles' or 'blocks'. - * If left blank, the immediate ancestor (if there is one) is returned - * @return {object} Reference to the model of the first ancestor of the specified type that's found - or `undefined` if none found + * Searches the model's ancestors to find the first instance of the specified type. + * If `ancestorType` is omitted, returns the immediate parent. + * @param {string} [ancestorType] Type group to search for — e.g. `'course'`, `'contentobject'`, `'article'`, `'block'` + * @returns {AdaptModel|undefined} First matching ancestor model, or `undefined` if none found */ findAncestor(ancestorType) { const parent = this.getParent(); @@ -422,14 +529,13 @@ export default class AdaptModel extends LockingModel { } /** - * Returns all the descendant models of a specific type - * @param {string} descendants Valid values are 'contentobject', 'page', 'menu', 'article', 'block', 'component', 'question' - * @param {object} options an object that defines the search type and the properties/values to search on. Currently only the `where` search type (equivalent to `Backbone.Collection.where()`) is supported. - * @param {object} options.where - * @return {array} + * Returns all descendant models of a specific type, optionally filtered by attribute values. + * @param {string} descendants Type group to search for — e.g. `'contentobject'`, `'article'`, `'block'`, `'component'`, `'question'` + * @param {Object} [options] Filter options + * @param {Object} [options.where] Attribute/value pairs all matched descendants must satisfy (equivalent to `Backbone.Collection.where()`) + * @returns {Array} * @example - * //find all available, non-optional components - * this.findDescendantModels('component', { where: { _isAvailable: true, _isOptional: false }}); + * this.findDescendantModels('component', { where: { _isAvailable: true, _isOptional: false } }); */ findDescendantModels(descendants, options) { const allDescendantsModels = this.getAllDescendantModels(); @@ -467,8 +573,8 @@ export default class AdaptModel extends LockingModel { * [ a1, b1, c1, c2, b2, c3, c4, a2, b3, c5, c6 ] * * This is useful when sequential operations are performed on the menu/page/article/block/component hierarchy. - * @param {boolean} [isParentFirst] - * @return {array} + * @param {boolean} [isParentFirst=false] When `true`, each parent is returned before its children + * @returns {Array} */ getAllDescendantModels(isParentFirst) { @@ -516,10 +622,10 @@ export default class AdaptModel extends LockingModel { * @see Adapt.parseRelativeString for a description of relativeStrings * @param {string} relativeString * @param {object} options Search configuration settings - * @param {boolean} options.limitParentId Constrain to a parent - * @param {function} options.filter Model filter - * @param {boolean} options.loop Allow offsets and insets to loop around to the beginning - * @return {array} + * @param {string} [options.limitParentId] Constrain the search to descendants of the model with this `_id` + * @param {Function} [options.filter] Additional filter function applied to candidate models + * @param {boolean} [options.loop=false] Allow offsets to loop around when they exceed the available count + * @returns {AdaptModel|undefined} */ findRelativeModel(relativeString, options = {}) { if (!relativeString) return this; @@ -622,10 +728,17 @@ export default class AdaptModel extends LockingModel { return foundModel; } + // Override in subclasses to return `false` for leaf nodes (e.g. components). get hasManagedChildren() { return true; } + /** + * Returns the collection of direct child models, building and caching it on + * first access. Block models with two children are re-ordered to ensure + * the `left`-layout component appears first. + * @returns {Backbone.Collection} Child model collection + */ getChildren() { if (this._childrenCollection) { return this._childrenCollection; @@ -656,6 +769,10 @@ export default class AdaptModel extends LockingModel { return this._childrenCollection; } + /** + * Sets the children collection and updates the deprecated `_children` attribute. + * @param {Backbone.Collection} children - New children collection + */ setChildren(children) { this._childrenCollection = children; // Setup deprecated reference @@ -668,6 +785,11 @@ export default class AdaptModel extends LockingModel { }); } + /** + * Returns the parent model, looked up by `_parentId` from the data module. + * Caches the result on first access. + * @returns {AdaptModel|undefined} + */ getParent() { if (this._parentModel) { return this._parentModel; @@ -685,6 +807,11 @@ export default class AdaptModel extends LockingModel { return this._parentModel; } + /** + * Sets the parent model reference and updates `_parentId` and the deprecated + * `_parent` attribute accordingly. + * @param {AdaptModel} parent - Parent model + */ setParent(parent) { this._parentModel = parent; this.set('_parentId', this._parentModel.get('_id')); @@ -692,6 +819,11 @@ export default class AdaptModel extends LockingModel { this.set('_parent', this._parentModel); } + /** + * Returns an ordered array of ancestor models, from immediate parent to the root. + * @param {boolean} [shouldIncludeChild=false] - Include this model at the start of the array + * @returns {Array|null} Ancestor models, or `null` if none + */ getAncestorModels(shouldIncludeChild) { const parents = []; let context = this; @@ -706,6 +838,11 @@ export default class AdaptModel extends LockingModel { return parents.length ? parents : null; } + /** + * Returns a collection of sibling models that share the same `_parentId`. + * @param {boolean} [passSiblingsAndIncludeSelf=false] - When `true`, includes this model in the result + * @returns {Backbone.Collection} Sibling collection + */ getSiblings(passSiblingsAndIncludeSelf) { const id = this.get('_id'); const parentId = this.get('_parentId'); @@ -740,9 +877,10 @@ export default class AdaptModel extends LockingModel { } /** - * @param {string} key - * @param {any} value - * @param {Object} options + * Sets attributes on this model and recursively on all descendant models. + * @param {string|Object} key - Attribute name or hash of key/value pairs + * @param {*} [value] - Attribute value (when key is a string) + * @param {Object} [options] - Backbone set options */ setOnChildren(...args) { diff --git a/js/models/lockingModel.js b/js/models/lockingModel.js index 866e916f..ae0f1e2e 100644 --- a/js/models/lockingModel.js +++ b/js/models/lockingModel.js @@ -1,3 +1,31 @@ +/** + * @file LockingModel - Backbone model with cooperative attribute locking + * @module core/js/models/lockingModel + * @description Extends Backbone.Model with a cooperative locking system that allows + * multiple plugins to independently lock boolean attributes. An attribute stays + * in its locked state as long as any one plugin holds a lock on it, and returns + * to the unlocked state only when all locks are released. + * + * **Architecture:** + * - Overrides `set()` to intercept locking attribute changes + * - Tracks per-plugin lock state in `_lockedAttributesValues` + * - Subclasses declare locking attributes by overriding `lockedAttributes()` + * + * **Usage:** + * Subclasses define which attributes are lockable by overriding `lockedAttributes()`. + * Plugins call `model.set(attrName, lockingValue, { pluginName: 'myPlugin' })` to + * add or remove their lock. The attribute resolves to its unlocked value only + * when no plugins hold a lock. + */ + +/** + * @class LockingModel + * @classdesc Backbone model with cooperative boolean attribute locking. Allows + * multiple plugins to independently lock attributes; the attribute stays in its + * locked state until all plugins release their lock. Subclasses declare lockable + * attributes via `lockedAttributes()`. + * @extends {Backbone.Model} + */ export default class LockingModel extends Backbone.Model { lockedAttributes() { @@ -10,6 +38,18 @@ export default class LockingModel extends Backbone.Model { // }; } + /** + * Overrides Backbone `set` to intercept locking attribute changes. + * Non-locking attributes pass through to `super.set` unchanged. For locking + * attributes, the per-plugin lock state is updated before the final value + * is resolved and applied. + * @override + * @param {string|Object} key - Attribute name or hash of key/value pairs + * @param {*} [val] - Attribute value (when key is a string) + * @param {Object} [options] - Backbone set options + * @param {string} [options.pluginName] - Plugin identifier required when changing a locked attribute + * @returns {LockingModel} Returns this for chaining + */ set(...args) { if (typeof args[0] !== 'object') { const [name, value] = args.splice(0, 2); @@ -70,12 +110,23 @@ export default class LockingModel extends Backbone.Model { } + /** + * Registers an attribute as a locking attribute with a given default locked value. + * Has no effect if the attribute is already registered. + * @param {string} attrName - Attribute name to register + * @param {boolean} defaultLockValue - The value the attribute takes when locked + */ setLocking(attrName, defaultLockValue) { if (this.isLocking(attrName)) return; if (!this._lockedAttributes) this._lockedAttributes = {}; this._lockedAttributes[attrName] = defaultLockValue; } + /** + * Unregisters a locking attribute, removing all associated lock state. + * Has no effect if the attribute is not currently registered. + * @param {string} attrName - Attribute name to unregister + */ unsetLocking(attrName) { if (!this.isLocking(attrName)) return; if (!this._lockedAttributes) return; @@ -87,6 +138,13 @@ export default class LockingModel extends Backbone.Model { } } + /** + * Returns whether the model uses locking, or whether a specific attribute is + * registered as a locking attribute. + * @param {string} [attrName] - Attribute to check. If omitted, returns whether + * the model has any locking attributes at all. + * @returns {boolean} + */ isLocking(attrName) { const isCheckingGeneralLockingState = (attrName === undefined); let hasDerivedLockedAttributes = Object.prototype.hasOwnProperty.call(this, '_lockedAttributes'); @@ -118,6 +176,14 @@ export default class LockingModel extends Backbone.Model { return true; } + /** + * Returns whether a locking attribute currently has any active locks. + * @param {string} attrName - The locking attribute to check + * @param {Object} [options] + * @param {boolean} [options.skipcheck] - Skip the `isLocking` guard check + * @returns {boolean|undefined} `true` if locked, `false` if unlocked, + * `undefined` if the attribute is not a locking attribute + */ isLocked(attrName, options) { const shouldSkipCheck = options?.skipcheck; if (!shouldSkipCheck) { @@ -128,6 +194,15 @@ export default class LockingModel extends Backbone.Model { return this.getLockCount(attrName) > 0; } + /** + * Returns the total number of active locks on a locking attribute, or the + * lock count for a specific plugin. + * @param {string} attrName - The locking attribute to query + * @param {Object} [options] + * @param {string} [options.pluginName] - Return only this plugin's lock count (0 or 1) + * @param {boolean} [options.skipcheck] - Skip the `isLocking` guard check + * @returns {number|undefined} Total lock count, or `undefined` if not a locking attribute + */ getLockCount(attrName, options) { const shouldSkipCheck = options?.skipcheck; if (!shouldSkipCheck) { @@ -146,6 +221,15 @@ export default class LockingModel extends Backbone.Model { return lockingAttributeValuesSum; } + /** + * Sets or clears a specific plugin's lock on a locking attribute. + * @param {string} attrName - The locking attribute to modify + * @param {boolean} value - `true` to add the lock, `false` to remove it + * @param {Object} options + * @param {string} options.pluginName - Plugin identifier holding or releasing the lock + * @param {boolean} [options.skipcheck] - Skip the `isLocking` guard check + * @returns {LockingModel} Returns this for chaining + */ setLockState(attrName, value, options) { const shouldSkipCheck = options?.skipcheck; if (!shouldSkipCheck) { diff --git a/js/views/adaptView.js b/js/views/adaptView.js index a91e2f50..2b43a85a 100644 --- a/js/views/adaptView.js +++ b/js/views/adaptView.js @@ -1,3 +1,30 @@ +/** + * @file AdaptView - Base Backbone view for all Adapt content + * @module core/js/views/adaptView + * @description Base view class for every rendered content item in the Adapt Framework. + * Handles both Handlebars and JSX template rendering, child view lifecycle + * management, on-screen animation, visibility toggling, and completion/ready + * state delegation to the model. + * + * **Key Responsibilities:** + * - Template rendering (Handlebars and React/JSX) + * - Child view creation and hierarchical addition (`addChildren`, `addChildView`) + * - View lifecycle events: `preRemove`, `remove`, `postRemove` + * - On-screen animation via `_onScreen` model config + * - Visibility and display toggling + * - Priority label calculation + * + * **Public Events Triggered:** + * - `{type}View:preRender view:preRender` - Before template is rendered + * - `{type}View:render view:render` - During render + * - `{type}View:postRender view:postRender` - After render and children added + * - `{type}View:addChild view:addChild` - Before a child view is created + * - `{type}View:childAdded view:childAdded` - After a child view is appended + * - `{type}View:preRemove view:preRemove` - Before removal begins + * - `{type}View:remove view:remove` - During removal + * - `{type}View:postRemove view:postRemove` - After removal completes + * - `{type}View:animationStart view:animationStart` - On-screen animation triggered + */ import Adapt from 'core/js/adapt'; import wait from 'core/js/wait'; import components from 'core/js/components'; @@ -8,6 +35,14 @@ import ReactDOM from 'react-dom'; import location from 'core/js/location'; import logging from 'core/js/logging'; import PRIORITY_LABEL_SUPPORTED_TYPE from 'core/js/enums/priorityLabelSupportedType'; +/** + * @class AdaptView + * @classdesc Base Backbone view for all Adapt content items. Manages template + * rendering (Handlebars and JSX), child view hierarchy, on-screen animations, + * and visibility state. Subclassed by menu, page, article, block, and component + * views. + * @extends {Backbone.View} + */ class AdaptView extends Backbone.View { attributes() { @@ -57,6 +92,17 @@ class AdaptView extends Backbone.View { await this.addChildren(); } + /** + * Renders the Handlebars or JSX template into the root element and fires + * lifecycle events. Defers `postRender` to allow the browser to paint first. + * @fires {type}View:preRender + * @fires view:preRender + * @fires {type}View:render + * @fires view:render + * @fires {type}View:postRender + * @fires view:postRender + * @returns {AdaptView} Returns this for chaining + */ render() { const type = this.constructor.type; Adapt.trigger(`${type}View:preRender view:preRender`, this); @@ -84,8 +130,8 @@ class AdaptView extends Backbone.View { } /** - * Re-render a react template - * @param {string} eventName=null Backbone change event name + * Re-renders the JSX template in response to model or device changes. + * @param {string} [eventName=null] Backbone change event name; bubbling events are ignored */ changed(eventName = null) { if (this._jsxIgnoreChanges !== 0) return; @@ -108,10 +154,19 @@ class AdaptView extends Backbone.View { ReactDOM.render(