diff --git a/js/modelHelpers.js b/js/modelHelpers.js new file mode 100644 index 00000000..0b19f81d --- /dev/null +++ b/js/modelHelpers.js @@ -0,0 +1,32 @@ +/** + * Toggle a className in the _classes attribute of any Backbone.Model + * @param model {Backbone.Model} Model with a _classes attribute to modify + * @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 + */ +export function toggleModelClass(model, className, hasClass) { + // split the string of classes into an array of classNames + const currentClassNames = (model.get('_classes') || '').split(' ').filter(Boolean); + // capture all existing classes as a unique list + const classesObject = currentClassNames.reduce((classesObject, className) => ({ ...classesObject, [className]: true }), {}); + // update the list according to arguments + const shouldToggleExisting = (hasClass === null || hasClass === undefined); + // allow multiple classes to be added and removed together + className + .split(' ') + .filter(Boolean) + .forEach(className => { + // toggle class + classesObject[className] = shouldToggleExisting + ? !classesObject[className] + : Boolean(hasClass); + }); + // flatten back into a string of classes + const outputClasses = Object + .entries(classesObject) + .filter(([, hasClass]) => hasClass) + .map(([className]) => className) + .join(' '); + // update the model + model.set('_classes', outputClasses); +} diff --git a/js/models/adaptModel.js b/js/models/adaptModel.js index e97387de..ba7af3fd 100644 --- a/js/models/adaptModel.js +++ b/js/models/adaptModel.js @@ -3,6 +3,7 @@ import data from 'core/js/data'; import ModelEvent from 'core/js/modelEvent'; import LockingModel from 'core/js/models/lockingModel'; import logging from 'core/js/logging'; +import { toggleModelClass } from '../modelHelpers'; export default class AdaptModel extends LockingModel { @@ -116,6 +117,16 @@ 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 + */ + toggleClass(className, hasClass) { + toggleModelClass(this, className, hasClass); + return this; + } + setupModel() { if (this.hasManagedChildren) { this.setupChildListeners(); diff --git a/js/models/itemModel.js b/js/models/itemModel.js index dca9c549..035facd7 100644 --- a/js/models/itemModel.js +++ b/js/models/itemModel.js @@ -1,15 +1,27 @@ import LockingModel from 'core/js/models/lockingModel'; +import { toggleModelClass } from '../modelHelpers'; export default class ItemModel extends LockingModel { defaults() { return { + _classes: '', _isActive: false, _isVisited: false, _score: 0 }; } + /** + * 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 + */ + toggleClass(className, hasClass) { + toggleModelClass(this, className, hasClass); + return this; + } + reset() { this.set({ _isActive: false, _isVisited: false }); }