diff options
Diffstat (limited to 'javascript/videojs/src/js/mixins/stateful.js')
| -rw-r--r-- | javascript/videojs/src/js/mixins/stateful.js | 120 |
1 files changed, 120 insertions, 0 deletions
diff --git a/javascript/videojs/src/js/mixins/stateful.js b/javascript/videojs/src/js/mixins/stateful.js new file mode 100644 index 0000000..0fd72d0 --- /dev/null +++ b/javascript/videojs/src/js/mixins/stateful.js @@ -0,0 +1,120 @@ +/** + * @file mixins/stateful.js + * @module stateful + */ +import {isEvented} from './evented'; +import * as Obj from '../utils/obj'; + +/** + * Contains methods that provide statefulness to an object which is passed + * to {@link module:stateful}. + * + * @mixin StatefulMixin + */ +const StatefulMixin = { + + /** + * A hash containing arbitrary keys and values representing the state of + * the object. + * + * @type {Object} + */ + state: {}, + + /** + * Set the state of an object by mutating its + * {@link module:stateful~StatefulMixin.state|state} object in place. + * + * @fires module:stateful~StatefulMixin#statechanged + * @param {Object|Function} stateUpdates + * A new set of properties to shallow-merge into the plugin state. + * Can be a plain object or a function returning a plain object. + * + * @return {Object|undefined} + * An object containing changes that occurred. If no changes + * occurred, returns `undefined`. + */ + setState(stateUpdates) { + + // Support providing the `stateUpdates` state as a function. + if (typeof stateUpdates === 'function') { + stateUpdates = stateUpdates(); + } + + let changes; + + Obj.each(stateUpdates, (value, key) => { + + // Record the change if the value is different from what's in the + // current state. + if (this.state[key] !== value) { + changes = changes || {}; + changes[key] = { + from: this.state[key], + to: value + }; + } + + this.state[key] = value; + }); + + // Only trigger "statechange" if there were changes AND we have a trigger + // function. This allows us to not require that the target object be an + // evented object. + if (changes && isEvented(this)) { + + /** + * An event triggered on an object that is both + * {@link module:stateful|stateful} and {@link module:evented|evented} + * indicating that its state has changed. + * + * @event module:stateful~StatefulMixin#statechanged + * @type {Object} + * @property {Object} changes + * A hash containing the properties that were changed and + * the values they were changed `from` and `to`. + */ + this.trigger({ + changes, + type: 'statechanged' + }); + } + + return changes; + } +}; + +/** + * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target + * object. + * + * If the target object is {@link module:evented|evented} and has a + * `handleStateChanged` method, that method will be automatically bound to the + * `statechanged` event on itself. + * + * @param {Object} target + * The object to be made stateful. + * + * @param {Object} [defaultState] + * A default set of properties to populate the newly-stateful object's + * `state` property. + * + * @return {Object} + * Returns the `target`. + */ +function stateful(target, defaultState) { + Object.assign(target, StatefulMixin); + + // This happens after the mixing-in because we need to replace the `state` + // added in that step. + target.state = Object.assign({}, target.state, defaultState); + + // Auto-bind the `handleStateChanged` method of the target object if it exists. + if (typeof target.handleStateChanged === 'function' && isEvented(target)) { + target.on('statechanged', target.handleStateChanged); + } + + return target; +} + +export default stateful; |
