summaryrefslogtreecommitdiff
path: root/javascript/videojs/src/js/mixins/stateful.js
diff options
context:
space:
mode:
Diffstat (limited to 'javascript/videojs/src/js/mixins/stateful.js')
-rw-r--r--javascript/videojs/src/js/mixins/stateful.js120
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;