From 1a0b2812cecf0bea88ab23f51a51c7dd3ee05d38 Mon Sep 17 00:00:00 2001 From: Brandon Casey Date: Fri, 2 Dec 2016 15:07:19 -0500 Subject: [PATCH] docs(jsdoc): Update the jsdoc comments to modern syntax - Part 1 (#3694) Add jsdoc npm script. Update JSDoc comments for the following files: * src/js/big-play-button.js * src/js/button.js * src/js/clickable-component.js * src/js/close-button.js * src/js/component.js * src/js/error-display.js * src/js/event-target.js --- .jsdoc.json | 6 + package.json | 4 +- src/js/big-play-button.js | 32 +- src/js/button.js | 58 +- src/js/clickable-component.js | 124 ++-- src/js/close-button.js | 49 +- src/js/component.js | 1181 ++++++++++++++++++++++----------- src/js/error-display.js | 28 +- src/js/event-target.js | 195 +++++- 9 files changed, 1187 insertions(+), 490 deletions(-) create mode 100644 .jsdoc.json diff --git a/.jsdoc.json b/.jsdoc.json new file mode 100644 index 0000000000..bc61eb6352 --- /dev/null +++ b/.jsdoc.json @@ -0,0 +1,6 @@ +{ + "plugins": ["plugins/markdown"], + "markdown": { + "tags": ["example"] + } +} diff --git a/package.json b/package.json index a8d8db311d..290165e721 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "lint": "vjsstandard", "start": "grunt watchAll", "test": "grunt test", - "toc": "doctoc" + "toc": "doctoc", + "docs": "jsdoc -r src/js -d docs/api -c .jsdoc.json" }, "repository": { "type": "git", @@ -87,6 +88,7 @@ "grunt-version": "~0.3.0", "grunt-videojs-languages": "0.0.4", "grunt-zip": "0.10.2", + "jsdoc": "^3.4.2", "karma": "^1.2.0", "karma-browserify": "^5.1.0", "karma-browserstack-launcher": "^1.0.1", diff --git a/src/js/big-play-button.js b/src/js/big-play-button.js index b581d33b25..4f81abd62a 100644 --- a/src/js/big-play-button.js +++ b/src/js/big-play-button.js @@ -5,37 +5,45 @@ import Button from './button.js'; import Component from './component.js'; /** - * Initial play button. Shows before the video has played. The hiding of the - * big play button is done via CSS and player states. + * The initial play button that shows before the video has played. The hiding of the + * `BigPlayButton` get done via CSS and `Player` states. * - * @param {Object} player Main Player - * @param {Object=} options Object of option names and values * @extends Button - * @class BigPlayButton */ class BigPlayButton extends Button { /** - * Allow sub components to stack CSS class names + * Builds the default DOM `className`. * - * @return {String} The constructed class name - * @method buildCSSClass + * @return {string} + * The DOM `className` for this object. Always returns 'vjs-big-play-button'. */ buildCSSClass() { return 'vjs-big-play-button'; } /** - * Handles click for play + * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent} + * for more detailed information on what a click can be. * - * @method handleClick + * @param {EventTarget~Event} event + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click */ - handleClick() { + handleClick(event) { this.player_.play(); } - } +/** + * The text that should display over the `BigPlayButton`s controls. Added to for localization. + * + * @type {string} + * @private + */ BigPlayButton.prototype.controlText_ = 'Play Video'; Component.registerComponent('BigPlayButton', BigPlayButton); diff --git a/src/js/button.js b/src/js/button.js index 7dae7baeca..5f03d2f044 100644 --- a/src/js/button.js +++ b/src/js/button.js @@ -7,23 +7,26 @@ import log from './utils/log.js'; import assign from 'object.assign'; /** - * Base class for all buttons + * Base class for all buttons. * - * @param {Object} player Main Player - * @param {Object=} options Object of option names and values * @extends ClickableComponent - * @class Button */ class Button extends ClickableComponent { /** - * Create the component's DOM element + * Create the `Button`s DOM element. + * + * @param {string} [tag=button] + * Element's node type. e.g. 'button' + * + * @param {Object} [props={}] + * An object of properties that should be set on the element. + * + * @param {Object} [attributes={}] + * An object of attributes that should be set on the element. * - * @param {String=} type Element's node type. e.g. 'div' - * @param {Object=} props An object of properties that should be set on the element - * @param {Object=} attributes An object of attributes that should be set on the element * @return {Element} - * @method createEl + * The element that gets created. */ createEl(tag = 'button', props = {}, attributes = {}) { props = assign({ @@ -62,13 +65,20 @@ class Button extends ClickableComponent { } /** - * Adds a child component inside this button + * Add a child `Component` inside of this `Button`. + * + * @param {string|Component} child + * The name or instance of a child to add. * - * @param {String|Component} child The class name or instance of a child to add - * @param {Object=} options Options, including options to be passed to children of the child. - * @return {Component} The child component (created by this process if a string was used) - * @deprecated - * @method addChild + * @param {Object} [options={}] + * The key/value store of options that will get passed to children of + * the child. + * + * @return {Component} + * The `Component` that gets added as a child. When using a string the + * `Component` will get created by this process. + * + * @deprecated since version 5 */ addChild(child, options = {}) { const className = this.constructor.name; @@ -80,10 +90,11 @@ class Button extends ClickableComponent { } /** - * Enable the button element + * Enable the `Button` element so that it can be activated or clicked. Use this with + * {@link Button#disable}. * * @return {Component} - * @method enable + * Returns itself; method is chainable. */ enable() { super.enable(); @@ -91,10 +102,11 @@ class Button extends ClickableComponent { } /** - * Disable the button element + * Enable the `Button` element so that it cannot be activated or clicked. Use this with + * {@link Button#enable}. * * @return {Component} - * @method disable + * Returns itself; method is chainable. */ disable() { super.disable(); @@ -102,9 +114,13 @@ class Button extends ClickableComponent { } /** - * Handle KeyPress (document level) - Extend with specific functionality for button + * This gets called when a `Button` has focus and `keydown` is triggered via a key + * press. + * + * @param {EventTarget~Event} event + * The event that caused this function to get called. * - * @method handleKeyPress + * @listens keydown */ handleKeyPress(event) { diff --git a/src/js/clickable-component.js b/src/js/clickable-component.js index 41e3ef37ec..9d1db5a36f 100644 --- a/src/js/clickable-component.js +++ b/src/js/clickable-component.js @@ -10,15 +10,22 @@ import document from 'global/document'; import assign from 'object.assign'; /** - * Clickable Component which is clickable or keyboard actionable, but is not a native HTML button + * Clickable Component which is clickable or keyboard actionable, + * but is not a native HTML button. * - * @param {Object} player Main Player - * @param {Object=} options Object of option names and values * @extends Component - * @class ClickableComponent */ class ClickableComponent extends Component { + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ constructor(player, options) { super(player, options); @@ -28,13 +35,19 @@ class ClickableComponent extends Component { } /** - * Create the component's DOM element + * Create the `Component`s DOM element. + * + * @param {string} [tag=div] + * The element's node type. + * + * @param {Object} [props={}] + * An object of properties that should be set on the element. + * + * @param {Object} [attributes={}] + * An object of attributes that should be set on the element. * - * @param {String=} type Element's node type. e.g. 'div' - * @param {Object=} props An object of properties that should be set on the element - * @param {Object=} attributes An object of attributes that should be set on the element * @return {Element} - * @method createEl + * The element that gets created. */ createEl(tag = 'div', props = {}, attributes = {}) { props = assign({ @@ -64,11 +77,13 @@ class ClickableComponent extends Component { } /** - * create control text + * Create a control text element on this `Component` + * + * @param {Element} [el] + * Parent element for the control text. * - * @param {Element} el Parent element for the control text * @return {Element} - * @method controlText + * The control text element that gets created. */ createControlTextEl(el) { this.controlTextEl_ = Dom.createEl('span', { @@ -85,12 +100,17 @@ class ClickableComponent extends Component { } /** - * Controls text - both request and localize + * Get or set the localize text to use for the controls on the `Component`. + * + * @param {string} [text] + * Control text for element. * - * @param {String} text Text for element - * @param {Element=} el Element to set the title on - * @return {String} - * @method controlText + * @param {Element} [el=this.el()] + * Element to set the title on. + * + * @return {string|ClickableComponent} + * - The control text when getting + * - Returns itself when setting; method can be chained. */ controlText(text, el = this.el()) { if (!text) { @@ -107,20 +127,20 @@ class ClickableComponent extends Component { } /** - * Allows sub components to stack CSS class names + * Builds the default DOM `className`. * - * @return {String} - * @method buildCSSClass + * @return {string} + * The DOM `className` for this object. */ buildCSSClass() { return `vjs-control vjs-button ${super.buildCSSClass()}`; } /** - * Enable the component element + * Enable this `Component`s element. * - * @return {Component} - * @method enable + * @return {ClickableComponent} + * Returns itself; method can be chained. */ enable() { this.removeClass('vjs-disabled'); @@ -136,10 +156,10 @@ class ClickableComponent extends Component { } /** - * Disable the component element + * Disable this `Component`s element. * - * @return {Component} - * @method disable + * @return {ClickableComponent} + * Returns itself; method can be chained. */ disable() { this.addClass('vjs-disabled'); @@ -155,25 +175,51 @@ class ClickableComponent extends Component { } /** - * Handle Click - Override with specific functionality for component + * This gets called when a `ClickableComponent` gets: + * - Clicked (via the `click` event, listening starts in the constructor) + * - Tapped (via the `tap` event, listening starts in the constructor) + * - The following things happen in order: + * 1. {@link ClickableComponent#handleFocus} is called via a `focus` event on the + * `ClickableComponent`. + * 2. {@link ClickableComponent#handleFocus} adds a listener for `keydown` on using + * {@link ClickableComponent#handleKeyPress}. + * 3. `ClickableComponent` has not had a `blur` event (`blur` means that focus was lost). The user presses + * the space or enter key. + * 4. {@link ClickableComponent#handleKeyPress} calls this function with the `keydown` + * event as a parameter. + * + * @param {EventTarget~Event} event + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. * - * @method handleClick + * @listens tap + * @listens click + * @abstract */ - handleClick() {} + handleClick(event) {} /** - * Handle Focus - Add keyboard functionality to element + * This gets called when a `ClickableComponent` gains focus via a `focus` event. + * Turns on listening for `keydown` events. When they happen it + * calls `this.handleKeyPress`. * - * @method handleFocus + * @param {EventTarget~Event} event + * The `focus` event that caused this function to be called. + * + * @listens focus */ - handleFocus() { + handleFocus(event) { Events.on(document, 'keydown', Fn.bind(this, this.handleKeyPress)); } /** - * Handle KeyPress (document level) - Trigger click when Space or Enter key is pressed + * Called when this ClickableComponent has focus and a key gets pressed down. By + * default it will call `this.handleClick` when the key is space or enter. + * + * @param {EventTarget~Event} event + * The `keydown` event that caused this function to be called. * - * @method handleKeyPress + * @listens keydown */ handleKeyPress(event) { @@ -189,11 +235,15 @@ class ClickableComponent extends Component { } /** - * Handle Blur - Remove keyboard triggers + * Called when a `ClickableComponent` loses focus. Turns off the listener for + * `keydown` events. Which Stops `this.handleKeyPress` from getting called. + * + * @param {EventTarget~Event} event + * The `blur` event that caused this function to be called. * - * @method handleBlur + * @listens blur */ - handleBlur() { + handleBlur(event) { Events.off(document, 'keydown', Fn.bind(this, this.handleKeyPress)); } } diff --git a/src/js/close-button.js b/src/js/close-button.js index 7db8da10ee..60aa561a70 100644 --- a/src/js/close-button.js +++ b/src/js/close-button.js @@ -1,25 +1,66 @@ +/** + * @file close-button.js + */ import Button from './button'; import Component from './component'; /** - * The `CloseButton` component is a button which fires a "close" event - * when it is activated. + * The `CloseButton` is a `{@link Button}` that fires a `close` event when + * it gets clicked. * * @extends Button - * @class CloseButton */ class CloseButton extends Button { + /** + * Creates an instance of the this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + */ constructor(player, options) { super(player, options); this.controlText(options && options.controlText || this.localize('Close')); } + /** + * Builds the default DOM `className`. + * + * @return {string} + * The DOM `className` for this object. + */ buildCSSClass() { return `vjs-close-button ${super.buildCSSClass()}`; } - handleClick() { + /** + * This gets called when a `CloseButton` gets clicked. See + * {@link ClickableComponent#handleClick} for more information on when this will be + * triggered + * + * @param {EventTarget~Event} event + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click + * @fires CloseButton#close + */ + handleClick(event) { + + /** + * Triggered when the a `CloseButton` is clicked. + * + * @event CloseButton#close + * @type {EventTarget~Event} + * + * @property {boolean} [bubbles=false] + * set to false so that the close event does not + * bubble up to parents if there is no listener + */ this.trigger({type: 'close', bubbles: false}); } } diff --git a/src/js/component.js b/src/js/component.js index 09fcba9f94..35b9e502a6 100644 --- a/src/js/component.js +++ b/src/js/component.js @@ -1,7 +1,7 @@ /** - * @file component.js - * * Player Component - Base class for all UI objects + * + * @file component.js */ import window from 'global/window'; import * as Dom from './utils/dom.js'; @@ -13,35 +13,62 @@ import toTitleCase from './utils/to-title-case.js'; import mergeOptions from './utils/merge-options.js'; /** - * Base UI Component class - * Components are embeddable UI objects that are represented by both a - * javascript object and an element in the DOM. They can be children of other - * components, and can have many children themselves. - * ```js - * // adding a button to the player - * var button = player.addChild('button'); - * button.el(); // -> button element + * Base class for all UI Components. + * Components are UI objects which represent both a javascript object and an element + * in the DOM. They can be children of other components, and can have + * children themselves. + * + * Creating a button component. + * ``` js + * // adding a button to the player + * var player = videojs('some-video-id'); + * var Component = videojs.getComponent('Component'); + * var button = new Component(player); + * + * console.log(button.el()); * ``` + * + * Above code will log this html. * ```html - *
- *
Button
- *
+ *
+ *
Button
+ *
* ``` - * Components are also event targets. - * ```js - * button.on('click', function() { - * console.log('Button Clicked!'); - * }); - * button.trigger('customevent'); + * + * Adding a button to the player + * ``` js + * // adding a button to the player + * var player = videojs('some-video-id'); + * var button = player.addChild('button'); + * + * console.log(button.el()); + * // will have the same html result as the previous example * ``` * - * @param {Object} player Main Player - * @param {Object=} options Object of option names and values - * @param {Function=} ready Ready callback function - * @class Component + * Components can also use methods from {@link EventTarget} */ class Component { + /** + * A callback that is called when a component is ready. Does not have any + * paramters and any callback value will be ignored. + * + * @callback Component~ReadyCallback + * @this Component + */ + + /** + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options] + * The key/value store of player options. + * + * @param {Component~ReadyCallback} [ready] + * Function that gets called when the `Component` is ready. + */ constructor(player, options, ready) { // The component might be the player itself and we can't pass `this` to super @@ -96,12 +123,23 @@ class Component { } /** - * Dispose of the component and all child components + * Dispose of the `Component` and all child components. * - * @method dispose + * @fires Component#dispose */ dispose() { - this.trigger({ type: 'dispose', bubbles: false }); + + /** + * Triggered when a `Component` is disposed. + * + * @event Component#dispose + * @type {EventTarget~Event} + * + * @property {boolean} [bubbles=false] + * set to false so that the close event does not + * bubble up + */ + this.trigger({type: 'dispose', bubbles: false}); // Dispose all children. if (this.children_) { @@ -130,53 +168,62 @@ class Component { } /** - * Return the component's player + * Return the {@link Player} that the `Component` has attached to. * * @return {Player} - * @method player + * The player that this `Component` has attached to. */ player() { return this.player_; } /** - * Deep merge of options objects - * Whenever a property is an object on both options objects - * the two properties will be merged using mergeOptions. + * Deep merge of options objects with new options. + * > Note: When both `obj` and `options` contain properties whose values are objects. + * The two properties get merged using {@link module:mergeOptions} * + * Example * ```js - * Parent.prototype.options_ = { - * optionSet: { - * 'childOne': { 'foo': 'bar', 'asdf': 'fdsa' }, - * 'childTwo': {}, - * 'childThree': {} - * } + * var player = videojs('some-vid-id'); + * var Component = videojs.getComponent('Component'); + * var component = new Component(player, { + * optionSet: { + * childOne: {foo: 'bar', asdf: 'fdsa'}, + * childTwo: {}, + * childThree: {} * } - * newOptions = { - * optionSet: { - * 'childOne': { 'foo': 'baz', 'abc': '123' } - * 'childTwo': null, - * 'childFour': {} - * } + * }); + * + * const newOptions = { + * optionSet: { + * childOne: {foo: 'baz', abc: '123'} + * childTwo: null, + * childFour: {} * } + * }; * - * this.options(newOptions); + * console.log(component.options(newOptions)); * ``` - * RESULT + * + * Result * ```js - * { - * optionSet: { - * 'childOne': { 'foo': 'baz', 'asdf': 'fdsa', 'abc': '123' }, - * 'childTwo': null, // Disabled. Won't be initialized. - * 'childThree': {}, - * 'childFour': {} - * } + * { + * optionSet: { + * childOne: {foo: 'baz', asdf: 'fdsa', abc: '123' }, + * childTwo: null, + * childThree: {}, + * childFour: {} * } + * } * ``` * - * @param {Object} obj Object of new option values - * @return {Object} A NEW object of this.options_ and obj merged - * @method options + * @param {Object} obj + * The object that contains new options. + * + * @return {Object} + * A new object of `this.options_` and `obj` merged together. + * + * @deprecated since version 5 */ options(obj) { log.warn('this.options() has been deprecated and will be moved to the constructor in 6.0'); @@ -190,31 +237,43 @@ class Component { } /** - * Get the component's DOM element - * ```js - * var domEl = myComponent.el(); - * ``` + * Get the `Component`s DOM element * * @return {Element} - * @method el + * The DOM element for this `Component`. */ el() { return this.el_; } /** - * Create the component's DOM element + * Create the `Component`s DOM element. + * + * @param {string} [tagName] + * Element's DOM node type. e.g. 'div' + * + * @param {Object} [properties] + * An object of properties that should be set. + * + * @param {Object} [attributes] + * An object of attributes that should be set. * - * @param {String=} tagName Element's node type. e.g. 'div' - * @param {Object=} properties An object of properties that should be set - * @param {Object=} attributes An object of attributes that should be set * @return {Element} - * @method createEl + * The element that gets created. */ createEl(tagName, properties, attributes) { return Dom.createEl(tagName, properties, attributes); } + /** + * Localize a string given the string in english. + * + * @param {string} string + * The string to localize. + * + * @return {string} + * The localized string or if no localization exists the english string. + */ localize(string) { const code = this.player_.language && this.player_.language(); const languages = this.player_.languages && this.player_.languages(); @@ -240,37 +299,32 @@ class Component { } /** - * Return the component's DOM element where children are inserted. - * Will either be the same as el() or a new element defined in createEl(). + * Return the `Component`s DOM element. This is where children get inserted. + * This will usually be the the same as the element returned in {@link Component#el}. * * @return {Element} - * @method contentEl + * The content element for this `Component`. */ contentEl() { return this.contentEl_ || this.el_; } /** - * Get the component's ID - * ```js - * var id = myComponent.id(); - * ``` + * Get this `Component`s ID * - * @return {String} - * @method id + * @return {string} + * The id of this `Component` */ id() { return this.id_; } /** - * Get the component's name. The name is often used to reference the component. - * ```js - * var name = myComponent.name(); - * ``` + * Get the `Component`s name. The name gets used to reference the `Component` + * and is set during registration. * - * @return {String} - * @method name + * @return {string} + * The name of this `Component`. */ name() { return this.name_; @@ -278,32 +332,35 @@ class Component { /** * Get an array of all child components - * ```js - * var kids = myComponent.children(); - * ``` * - * @return {Array} The children - * @method children + * @return {Array} + * The children */ children() { return this.children_; } /** - * Returns a child component with the provided ID + * Returns the child `Component` with the given `id`. * - * @return {Component} - * @method getChildById + * @param {string} id + * The id of the child `Component` to get. + * + * @return {Component|undefined} + * The child `Component` with the given `id` or undefined. */ getChildById(id) { return this.childIndex_[id]; } /** - * Returns a child component with the provided name + * Returns the child `Component` with the given `name`. * - * @return {Component} - * @method getChild + * @param {string} name + * The name of the child `Component` to get. + * + * @return {Component|undefined} + * The child `Component` with the given `name` or undefined. */ getChild(name) { if (!name) { @@ -316,32 +373,53 @@ class Component { } /** - * Adds a child component inside this component + * Add a child `Component` inside the current `Component`. + * + * Example: * ```js - * myComponent.el(); - * // ->
- * myComponent.children(); - * // [empty array] - * - * var myButton = myComponent.addChild('MyButton'); - * // ->
myButton
- * // -> myButton === myComponent.children()[0]; + * var player = videojs('some-vid-id'); + * var Component = videojs.getComponent('Component'); + * var myComponent = new Component(player); + * + * console.log(myComponent.el()); + * // ->
+ * console.log(myComponent.children()); + * // [empty array] + * + * var myButton = myComponent.addChild('MyButton'); + * + * console.log(myComponent.el()); + * // ->
myButton
+ * console.log(myComponent.children()); + * // -> myButton === myComponent.children()[0]; * ``` - * Pass in options for child constructors and options for children of the child + * + * Pass in options for child constructors and options for children of the child. * ```js - * var myButton = myComponent.addChild('MyButton', { - * text: 'Press Me', - * buttonChildExample: { - * buttonChildOption: true - * } - * }); + * var player = videojs('some-vid-id'); + * var Component = videojs.getComponent('Component'); + * var myComponent = new Component(player); + * var myButton = myComponent.addChild('MyButton', { + * text: 'Press Me', + * buttonChildExample: { + * buttonChildOption: true + * } + * }); * ``` * - * @param {String|Component} child The class name or instance of a child to add - * @param {Object=} options Options, including options to be passed to children of the child. - * @param {Number} index into our children array to attempt to add the child - * @return {Component} The child component (created by this process if a string was used) - * @method addChild + * @param {string|Component} child + * The name or instance of a child to add. + * + * @param {Object} [options={}] + * The key/value store of options that will get passed to children of + * the child. + * + * @param {number} [index=this.children_.length] + * The index to attempt to add a child into. + * + * @return {Component} + * The `Component` that gets added as a child. When using a string the + * `Component` will get created by this process. */ addChild(child, options = {}, index = this.children_.length) { let component; @@ -351,14 +429,18 @@ class Component { if (typeof child === 'string') { componentName = toTitleCase(child); - // Options can also be specified as a boolean, so convert to an empty object if false. + // Options can also be specified as a boolean, + // so convert to an empty object if false. if (!options) { options = {}; } // Same as above, but true is deprecated so show a warning. if (options === true) { - log.warn('Initializing a child component with `true` is deprecated. Children should be defined in an array when possible, but if necessary use an object instead of `true`.'); + log.warn('Initializing a child component with `true` is deprecated.' + + 'Children should be defined in an array when possible, ' + + 'but if necessary use an object instead of `true`.' + ); options = {}; } @@ -418,11 +500,11 @@ class Component { } /** - * Remove a child component from this component's list of children, and the - * child component's element from this component's element + * Remove a child `Component` from this `Component`s list of children. Also removes + * the child `Component`s element from this `Component`s element. * - * @param {Component} component Component to remove - * @method removeChild + * @param {Component} component + * The child `Component` to remove. */ removeChild(component) { if (typeof component === 'string') { @@ -458,50 +540,49 @@ class Component { } /** - * Add and initialize default child components from options + * Add and initialize default child `Component`s based upon options. + * + * Example. * ```js - * // when an instance of MyComponent is created, all children in options - * // will be added to the instance by their name strings and options - * MyComponent.prototype.options_ = { - * children: [ - * 'myChildComponent' - * ], - * myChildComponent: { - * myChildOption: true - * } - * }; - * - * // Or when creating the component - * var myComp = new MyComponent(player, { - * children: [ - * 'myChildComponent' - * ], - * myChildComponent: { - * myChildOption: true - * } - * }); + * var MyComponent = videojs.extend(videojs.getComponent('Component')); + * // when an instance of MyComponent is created, all children in options + * // will be added to the instance by their name strings and options + * MyComponent.prototype.options_ = { + * children: [ + * 'myChildComponent' + * ], + * myChildComponent: { + * myChildOption: true + * } + * }; + * + * // Or when creating the component + * var player = videojs('some-player-id'); + * var myComp = new MyComponent(player, { + * children: [ + * 'myChildComponent' + * ], + * myChildComponent: { + * myChildOption: true + * } + * }); * ``` - * The children option can also be an array of - * child options objects (that also include a 'name' key). - * This can be used if you have two child components of the - * same type that need different options. + * + * The children option can also be an array of child options objects + * (that also include a 'name' key). This will get used if you have two child + * components of the same type that need different options. * ```js - * var myComp = new MyComponent(player, { - * children: [ - * 'button', - * { - * name: 'button', - * someOtherOption: true - * }, - * { - * name: 'button', - * someOtherOption: false - * } - * ] - * }); + * // MyComponent is from the above example + * var myComp = new MyComponent(player, { + * children: ['button', { + * name: 'button', + * someOtherOption: true + * }, { + * name: 'button', + * someOtherOption: false + * }] + * }); * ``` - * - * @method initChildren */ initChildren() { const children = this.options_.children; @@ -533,7 +614,8 @@ class Component { opts = {}; } - // We also want to pass the original player options to each component as well so they don't need to + // We also want to pass the original player options + // to each component as well so they don't need to // reach back into the player for options later. opts.playerOptions = this.options_.playerOptions; @@ -598,10 +680,12 @@ class Component { } /** - * Allows sub components to stack CSS class names + * Builds the default DOM class name. Should be overriden by sub-components. * - * @return {String} The constructed class name - * @method buildCSSClass + * @return {string} + * The DOM class name for this object. + * + * @abstract */ buildCSSClass() { // Child classes can include a function that does: @@ -610,35 +694,63 @@ class Component { } /** - * Add an event listener to this component's element + * Add an `event listener` to this `Component`s element. + * * ```js - * var myFunc = function() { - * var myComponent = this; - * // Do something when the event is fired - * }; + * var player = videojs('some-player-id'); + * var Component = videojs.getComponent('Component'); + * var myComponent = new Component(player); + * var myFunc = function() { + * var myComponent = this; + * console.log('myFunc called'); + * }; * - * myComponent.on('eventType', myFunc); + * myComponent.on('eventType', myFunc); + * myComponent.trigger('eventType'); + * // logs 'myFunc called' * ``` - * The context of myFunc will be myComponent unless previously bound. - * Alternatively, you can add a listener to another element or component. + * + * The context of `myFunc` will be `myComponent` unless it is bound. You can add + * a listener to another element or component. * ```js - * myComponent.on(otherElement, 'eventName', myFunc); - * myComponent.on(otherComponent, 'eventName', myFunc); + * var otherComponent = new Component(player); + * + * // myComponent/myFunc is from the above example + * myComponent.on(otherComponent.el(), 'eventName', myFunc); + * myComponent.on(otherComponent, 'eventName', myFunc); + * + * otherComponent.trigger('eventName'); + * // logs 'myFunc called' twice * ``` - * The benefit of using this over `VjsEvents.on(otherElement, 'eventName', myFunc)` - * and `otherComponent.on('eventName', myFunc)` is that this way the listeners - * will be automatically cleaned up when either component is disposed. - * It will also bind myComponent as the context of myFunc. - * **NOTE**: When using this on elements in the page other than window - * and document (both permanent), if you remove the element from the DOM - * you need to call `myComponent.trigger(el, 'dispose')` on it to clean up - * references to it and allow the browser to garbage collect it. - * - * @param {String|Component} first The event type or other component - * @param {Function|String} second The event handler or event type - * @param {Function} third The event handler + * + * The benefit of using this over the following: + * - `VjsEvents.on(otherElement, 'eventName', myFunc)` + * - `otherComponent.on('eventName', myFunc)` + * Is that the listeners will get cleaned up when either component gets disposed. + * It will also bind `myComponent` as the context of `myFunc`. + * > NOTE: If you remove the element from the DOM that has used `on` you need to + * clean up references using: + * + * `myComponent.trigger(el, 'dispose')` + * + * This will also allow the browser to garbage collect it. In special + * cases such as with `window` and `document`, which are both permanent, + * this is not necessary. + * + * @param {string|Component|string[]} [first] + * The event name, and array of event names, or another `Component`. + * + * @param {EventTarget~EventListener|string|string[]} [second] + * The listener function, an event name, or an Array of events names. + * + * @param {EventTarget~EventListener} [third] + * The event handler if `first` is a `Component` and `second` is an event name + * or an Array of event names. + * * @return {Component} - * @method on + * Returns itself; method can be chained. + * + * @listens Component#dispose */ on(first, second, third) { if (typeof first === 'string' || Array.isArray(first)) { @@ -685,25 +797,60 @@ class Component { } /** - * Remove an event listener from this component's element + * Remove an event listener from this `Component`s element. * ```js - * myComponent.off('eventType', myFunc); + * var player = videojs('some-player-id'); + * var Component = videojs.getComponent('Component'); + * var myComponent = new Component(player); + * var myFunc = function() { + * var myComponent = this; + * console.log('myFunc called'); + * }; + * myComponent.on('eventType', myFunc); + * myComponent.trigger('eventType'); + * // logs 'myFunc called' + * + * myComponent.off('eventType', myFunc); + * myComponent.trigger('eventType'); + * // does nothing * ``` - * If myFunc is excluded, ALL listeners for the event type will be removed. - * If eventType is excluded, ALL listeners will be removed from the component. - * Alternatively you can use `off` to remove listeners that were added to other - * elements or components using `myComponent.on(otherComponent...`. - * In this case both the event type and listener function are REQUIRED. + * + * If myFunc gets excluded, ALL listeners for the event type will get removed. If + * eventType gets excluded, ALL listeners will get removed from the component. + * You can use `off` to remove listeners that get added to other elements or + * components using: + * + * `myComponent.on(otherComponent...` + * + * In this case both the event type and listener function are **REQUIRED**. + * * ```js - * myComponent.off(otherElement, 'eventType', myFunc); - * myComponent.off(otherComponent, 'eventType', myFunc); + * var otherComponent = new Component(player); + * + * // myComponent/myFunc is from the above example + * myComponent.on(otherComponent.el(), 'eventName', myFunc); + * myComponent.on(otherComponent, 'eventName', myFunc); + * + * otherComponent.trigger('eventName'); + * // logs 'myFunc called' twice + * myComponent.off(ootherComponent.el(), 'eventName', myFunc); + * myComponent.off(otherComponent, 'eventName', myFunc); + * otherComponent.trigger('eventName'); + * // does nothing * ``` * - * @param {String=|Component} first The event type or other component - * @param {Function=|String} second The listener function or event type - * @param {Function=} third The listener for other component + * @param {string|Component|string[]} [first] + * The event name, and array of event names, or another `Component`. + * + * @param {EventTarget~EventListener|string|string[]} [second] + * The listener function, an event name, or an Array of events names. + * + * @param {EventTarget~EventListener} [third] + * The event handler if `first` is a `Component` and `second` is an event name + * or an Array of event names. + * * @return {Component} - * @method off + * Returns itself; method can be chained. */ off(first, second, third) { if (!first || typeof first === 'string' || Array.isArray(first)) { @@ -733,22 +880,52 @@ class Component { } /** - * Add an event listener to be triggered only once and then removed + * Add an event listener that gets triggered only once and then gets removed. * ```js - * myComponent.one('eventName', myFunc); + * var player = videojs('some-player-id'); + * var Component = videojs.getComponent('Component'); + * var myComponent = new Component(player); + * var myFunc = function() { + * var myComponent = this; + * console.log('myFunc called'); + * }; + * myComponent.one('eventName', myFunc); + * myComponent.trigger('eventName'); + * // logs 'myFunc called' + * + * myComponent.trigger('eventName'); + * // does nothing + * * ``` - * Alternatively you can add a listener to another element or component - * that will be triggered only once. + * + * You can also add a listener to another element or component that will get + * triggered only once. * ```js - * myComponent.one(otherElement, 'eventName', myFunc); - * myComponent.one(otherComponent, 'eventName', myFunc); + * var otherComponent = new Component(player); + * + * // myComponent/myFunc is from the above example + * myComponent.one(otherComponent.el(), 'eventName', myFunc); + * myComponent.one(otherComponent, 'eventName', myFunc); + * + * otherComponent.trigger('eventName'); + * // logs 'myFunc called' twice + * + * otherComponent.trigger('eventName'); + * // does nothing * ``` * - * @param {String|Component} first The event type or other component - * @param {Function|String} second The listener function or event type - * @param {Function=} third The listener function for other component + * @param {string|Component|string[]} [first] + * The event name, and array of event names, or another `Component`. + * + * @param {EventTarget~EventListener|string|string[]} [second] + * The listener function, an event name, or an Array of events names. + * + * @param {EventTarget~EventListener} [third] + * The event handler if `first` is a `Component` and `second` is an event name + * or an Array of event names. + * * @return {Component} - * @method one + * Returns itself; method can be chained. */ one(first, second, third) { if (typeof first === 'string' || Array.isArray(first)) { @@ -773,18 +950,40 @@ class Component { } /** - * Trigger an event on an element + * Trigger an event on an element. + * * ```js - * myComponent.trigger('eventName'); - * myComponent.trigger({'type':'eventName'}); - * myComponent.trigger('eventName', {data: 'some data'}); - * myComponent.trigger({'type':'eventName'}, {data: 'some data'}); + * var player = videojs('some-player-id'); + * var Component = videojs.getComponent('Component'); + * var myComponent = new Component(player); + * var myFunc = function(data) { + * var myComponent = this; + * console.log('myFunc called'); + * console.log(data); + * }; + * myComponent.one('eventName', myFunc); + * myComponent.trigger('eventName'); + * // logs 'myFunc called' and 'undefined' + * + * myComponent.trigger({'type':'eventName'}); + * // logs 'myFunc called' and 'undefined' + * + * myComponent.trigger('eventName', {data: 'some data'}); + * // logs 'myFunc called' and "{data: 'some data'}" + * + * myComponent.trigger({'type':'eventName'}, {data: 'some data'}); + * // logs 'myFunc called' and "{data: 'some data'}" * ``` * - * @param {Event|Object|String} event A string (the type) or an event object with a type attribute - * @param {Object} [hash] data hash to pass along with the event - * @return {Component} self - * @method trigger + * @param {EventTarget~Event|Object|string} event + * The event name, and Event, or an event-like object with a type attribute + * set to the event name. + * + * @param {Object} [hash] + * Data hash to pass along with the event + * + * @return {Component} + * Returns itself; method can be chained. */ trigger(event, hash) { Events.trigger(this.el_, event, hash); @@ -792,14 +991,17 @@ class Component { } /** - * Bind a listener to the component's ready state. - * Different from event listeners in that if the ready event has already happened - * it will trigger the function immediately. + * Bind a listener to the component's ready state. If the ready event has already + * happened it will trigger the function immediately. + * + * @param {Component~ReadyCallback} fn + * A function to call when ready is triggered. + * + * @param {boolean} [sync=false] + * Execute the listener synchronously if `Component` is ready. * - * @param {Function} fn Ready listener - * @param {Boolean} sync Exec the listener synchronously if component is ready * @return {Component} - * @method ready + * Returns itself; method can be chained. */ ready(fn, sync = false) { if (fn) { @@ -819,10 +1021,9 @@ class Component { } /** - * Trigger the ready listeners + * Trigger all the ready listeners for this `Component`. * - * @return {Component} - * @method triggerReady + * @fires Component#ready */ triggerReady() { this.isReady_ = true; @@ -841,67 +1042,82 @@ class Component { } // Allow for using event listeners also + /** + * Triggered when a `Component` is ready. + * + * @event Component#ready + * @type {EventTarget~Event} + */ this.trigger('ready'); }, 1); } /** - * Finds a single DOM element matching `selector` within the component's - * `contentEl` or another custom context. + * Find a single DOM element matching a `selector`. This can be within the `Component`s + * `contentEl()` or another custom context. * - * @method $ - * @param {String} selector - * A valid CSS selector, which will be passed to `querySelector`. + * @param {string} selector + * A valid CSS selector, which will be passed to `querySelector`. * - * @param {Element|String} [context=document] - * A DOM element within which to query. Can also be a selector - * string in which case the first matching element will be used - * as context. If missing (or no element matches selector), falls - * back to `document`. + * @param {Element|string} [context=this.contentEl()] + * A DOM element within which to query. Can also be a selector string in + * which case the first matching element will get used as context. If + * missing `this.contentEl()` gets used. If `this.contentEl()` returns + * nothing it falls back to `document`. * * @return {Element|null} + * the dom element that was found, or null + * + * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) */ $(selector, context) { return Dom.$(selector, context || this.contentEl()); } /** - * Finds a all DOM elements matching `selector` within the component's - * `contentEl` or another custom context. + * Finds all DOM element matching a `selector`. This can be within the `Component`s + * `contentEl()` or another custom context. * - * @method $$ - * @param {String} selector - * A valid CSS selector, which will be passed to `querySelectorAll`. + * @param {string} selector + * A valid CSS selector, which will be passed to `querySelectorAll`. * - * @param {Element|String} [context=document] - * A DOM element within which to query. Can also be a selector - * string in which case the first matching element will be used - * as context. If missing (or no element matches selector), falls - * back to `document`. + * @param {Element|string} [context=this.contentEl()] + * A DOM element within which to query. Can also be a selector string in + * which case the first matching element will get used as context. If + * missing `this.contentEl()` gets used. If `this.contentEl()` returns + * nothing it falls back to `document`. * * @return {NodeList} + * a list of dom elements that were found + * + * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) */ $$(selector, context) { return Dom.$$(selector, context || this.contentEl()); } /** - * Check if a component's element has a CSS class name + * Check if a component's element has a CSS class name. * - * @param {String} classToCheck Classname to check - * @return {Component} - * @method hasClass + * @param {string} classToCheck + * CSS class name to check. + * + * @return {boolean} + * - True if the `Component` has the class. + * - False if the `Component` does not have the class` */ hasClass(classToCheck) { return Dom.hasElClass(this.el_, classToCheck); } /** - * Add a CSS class name to the component's element + * Add a CSS class name to the `Component`s element. + * + * @param {string} classToAdd + * CSS class name to add * - * @param {String} classToAdd Classname to add * @return {Component} - * @method addClass + * Returns itself; method can be chained. */ addClass(classToAdd) { Dom.addElClass(this.el_, classToAdd); @@ -909,11 +1125,13 @@ class Component { } /** - * Remove a CSS class name from the component's element + * Remove a CSS class name from the `Component`s element. + * + * @param {string} classToRemove + * CSS class name to remove * - * @param {String} classToRemove Classname to remove * @return {Component} - * @method removeClass + * Returns itself; method can be chained. */ removeClass(classToRemove) { Dom.removeElClass(this.el_, classToRemove); @@ -921,16 +1139,18 @@ class Component { } /** - * Add or remove a CSS class name from the component's element + * Add or remove a CSS class name from the component's element. + * - `classToToggle` gets added when {@link Component#hasClass} would return false. + * - `classToToggle` gets removed when {@link Component#hasClass} would return true. + * + * @param {string} classToToggle + * The class to add or remove based on (@link Component#hasClass} * - * @param {String} classToToggle - * @param {Boolean|Function} [predicate] - * Can be a function that returns a Boolean. If `true`, the class - * will be added; if `false`, the class will be removed. If not - * given, the class will be added if not present and vice versa. + * @param {boolean|Dom~predicate} [predicate] + * An {@link Dom~predicate} function or a boolean * * @return {Component} - * @method toggleClass + * Returns itself; method can be chained. */ toggleClass(classToToggle, predicate) { Dom.toggleElClass(this.el_, classToToggle, predicate); @@ -938,10 +1158,11 @@ class Component { } /** - * Show the component element if hidden + * Show the `Component`s element if it is hidden by removing the + * 'vjs-hidden' class name from it. * * @return {Component} - * @method show + * Returns itself; method can be chained. */ show() { this.removeClass('vjs-hidden'); @@ -949,10 +1170,11 @@ class Component { } /** - * Hide the component element if currently showing + * Hide the `Component`s element if it is currently showing by adding the + * 'vjs-hidden` class name to it. * * @return {Component} - * @method hide + * Returns itself; method can be chained. */ hide() { this.addClass('vjs-hidden'); @@ -960,12 +1182,13 @@ class Component { } /** - * Lock an item in its visible state - * To be used with fadeIn/fadeOut. + * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing' + * class name to it. Used during fadeIn/fadeOut. * * @return {Component} + * Returns itself; method can be chained. + * * @private - * @method lockShowing */ lockShowing() { this.addClass('vjs-lock-showing'); @@ -973,12 +1196,13 @@ class Component { } /** - * Unlock an item to be hidden - * To be used with fadeIn/fadeOut. + * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing' + * class name from it. Used during fadeIn/fadeOut. * * @return {Component} + * Returns itself; method can be chained. + * * @private - * @method unlockShowing */ unlockShowing() { this.removeClass('vjs-lock-showing'); @@ -986,23 +1210,37 @@ class Component { } /** - * Get the value of an attribute on the component's element + * Get the value of an attribute on the `Component`s element. * - * @param {String} attribute Attribute to get - * @return {String} - * @method getAttribute + * @param {string} attribute + * Name of the attribute to get the value from. + * + * @return {string|null} + * - The value of the attribute that was asked for. + * - Can be an empty string on some browsers if the attribute does not exist + * or has no value + * - Most browsers will return null if the attibute does not exist or has + * no value. + * + * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute} */ getAttribute(attribute) { return Dom.getAttribute(this.el_, attribute); } /** - * Set the value of an attribute on the component's element + * Set the value of an attribute on the `Component`'s element + * + * @param {string} attribute + * Name of the attribute to set. + * + * @param {string} value + * Value to set the attribute to. * - * @param {String} attribute Attribute to set - * @param {String} value Value to set the attribute to * @return {Component} - * @method setAttribute + * Returns itself; method can be chained. + * + * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute} */ setAttribute(attribute, value) { Dom.setAttribute(this.el_, attribute, value); @@ -1010,11 +1248,15 @@ class Component { } /** - * Remove an attribute from the component's element + * Remove an attribute from the `Component`s element. + * + * @param {string} attribute + * Name of the attribute to remove. * - * @param {String} attribute Attribute to remove * @return {Component} - * @method removeAttribute + * Returns itself; method can be chained. + * + * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute} */ removeAttribute(attribute) { Dom.removeAttribute(this.el_, attribute); @@ -1022,46 +1264,54 @@ class Component { } /** - * Set or get the width of the component (CSS values) - * Setting the video tag dimension values only works with values in pixels. - * Percent values will not work. - * Some percents can be used, but width()/height() will return the number + %, - * not the actual computed width/height. - * - * @param {Number|String=} num Optional width number - * @param {Boolean} skipListeners Skip the 'resize' event trigger - * @return {Component} This component, when setting the width - * @return {Number|String} The width, when getting - * @method width + * Get or set the width of the component based upon the CSS styles. + * See {@link Component#dimension} for more detailed information. + * + * @param {number|string} [num] + * The width that you want to set postfixed with '%', 'px' or nothing. + * + * @param {boolean} [skipListeners] + * Skip the resize event trigger + * + * @return {Component|number|string} + * - The width when getting, zero if there is no width. Can be a string + * postpixed with '%' or 'px'. + * - Returns itself when setting; method can be chained. */ width(num, skipListeners) { return this.dimension('width', num, skipListeners); } /** - * Get or set the height of the component (CSS values) - * Setting the video tag dimension values only works with values in pixels. - * Percent values will not work. - * Some percents can be used, but width()/height() will return the number + %, - * not the actual computed width/height. - * - * @param {Number|String=} num New component height - * @param {Boolean=} skipListeners Skip the resize event trigger - * @return {Component} This component, when setting the height - * @return {Number|String} The height, when getting - * @method height + * Get or set the height of the component based upon the CSS styles. + * See {@link Component#dimension} for more detailed information. + * + * @param {number|string} [num] + * The height that you want to set postfixed with '%', 'px' or nothing. + * + * @param {boolean} [skipListeners] + * Skip the resize event trigger + * + * @return {Component|number|string} + * - The width when getting, zero if there is no width. Can be a string + * postpixed with '%' or 'px'. + * - Returns itself when setting; method can be chained. */ height(num, skipListeners) { return this.dimension('height', num, skipListeners); } /** - * Set both width and height at the same time + * Set both the width and height of the `Component` element at the same time. + * + * @param {number|string} width + * Width to set the `Component`s element to. * - * @param {Number|String} width Width of player - * @param {Number|String} height Height of player - * @return {Component} The component - * @method dimensions + * @param {number|string} height + * Height to set the `Component`s element to. + * + * @return {Component} + * Returns itself; method can be chained. */ dimensions(width, height) { // Skip resize listeners on width for optimization @@ -1069,21 +1319,33 @@ class Component { } /** - * Get or set width or height - * This is the shared code for the width() and height() methods. - * All for an integer, integer + 'px' or integer + '%'; - * Known issue: Hidden elements officially have a width of 0. We're defaulting - * to the style.width value and falling back to computedStyle which has the - * hidden element issue. Info, but probably not an efficient fix: - * http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/ - * - * @param {String} widthOrHeight 'width' or 'height' - * @param {Number|String=} num New dimension - * @param {Boolean=} skipListeners Skip resize event trigger - * @return {Component} The component if a dimension was set - * @return {Number|String} The dimension if nothing was set - * @private - * @method dimension + * Get or set width or height of the `Component` element. This is the shared code + * for the {@link Component#width} and {@link Component#height}. + * + * Things to know: + * - If the width or height in an number this will return the number postfixed with 'px'. + * - If the width/height is a percent this will return the percent postfixed with '%' + * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function + * defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`. + * See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/} + * for more information + * - If you want the computed style of the component, use {@link Component#currentWidth} + * and {@link {Component#currentHeight} + * + * @fires Component#resize + * + * @param {string} widthOrHeight + 8 'width' or 'height' + * + * @param {number|string} [num] + 8 New dimension + * + * @param {boolean} [skipListeners] + * Skip resize event trigger + * + * @return {Component} + * - the dimension when getting or 0 if unset + * - Returns itself when setting; method can be chained. */ dimension(widthOrHeight, num, skipListeners) { if (num !== undefined) { @@ -1103,6 +1365,12 @@ class Component { // skipListeners allows us to avoid triggering the resize event when setting both width and height if (!skipListeners) { + /** + * Triggered when a component is resized. + * + * @event Component#resize + * @type {EventTarget~Event} + */ this.trigger('resize'); } @@ -1132,10 +1400,15 @@ class Component { } /** - * Get width or height of computed style - * @param {String} widthOrHeight 'width' or 'height' - * @return {Number|Boolean} The bolean false if nothing was set - * @method currentDimension + * Get the width or the height of the `Component` elements computed style. Uses + * `window.getComputedStyle`. + * + * @param {string} widthOrHeight + * A string containing 'width' or 'height'. Whichever one you want to get. + * + * @return {number} + * The dimension that gets asked for or 0 if nothing was set + * for that dimension. */ currentDimension(widthOrHeight) { let computedWidthOrHeight = 0; @@ -1166,9 +1439,24 @@ class Component { } /** - * Get an object which contains width and height values of computed style - * @return {Object} The dimensions of element - * @method currentDimensions + * An object that contains width and height values of the `Component`s + * computed style. Uses `window.getComputedStyle`. + * + * @typedef {Object} Component~DimensionObject + * + * @property {number} width + * The width of the `Component`s computed style. + * + * @property {number} height + * The height of the `Component`s computed style. + */ + + /** + * Get an object that contains width and height values of the `Component`s + * computed style. + * + * @return {Component~DimensionObject} + * The dimensions of the components element */ currentDimensions() { return { @@ -1178,32 +1466,38 @@ class Component { } /** - * Get width of computed style - * @return {Integer} - * @method currentWidth + * Get the width of the `Component`s computed style. Uses `window.getComputedStyle`. + * + * @return {number} width + * The width of the `Component`s computed style. */ currentWidth() { return this.currentDimension('width'); } /** - * Get height of computed style - * @return {Integer} - * @method currentHeight + * Get the height of the `Component`s computed style. Uses `window.getComputedStyle`. + * + * @return {number} height + * The height of the `Component`s computed style. */ currentHeight() { return this.currentDimension('height'); } /** - * Emit 'tap' events when touch events are supported - * This is used to support toggling the controls through a tap on the video. - * We're requiring them to be enabled because otherwise every component would - * have this extra overhead unnecessarily, on mobile devices where extra - * overhead is especially bad. + * Emit a 'tap' events when touch event support gets detected. This gets used to + * support toggling the controls through a tap on the video. They get enabled + * because every sub-component would have extra overhead otherwise. * * @private - * @method emitTapEvents + * @fires Component#tap + * @listens Component#touchstart + * @listens Component#touchmove + * @listens Component#touchleave + * @listens Component#touchcancel + * @listens Component#touchend + */ emitTapEvents() { // Track the start time so we can determine how long the touch lasted @@ -1211,7 +1505,8 @@ class Component { let firstTouch = null; // Maximum movement allowed during a touch event to still be considered a tap - // Other popular libs use anywhere from 2 (hammer.js) to 15, so 10 seems like a nice, round number. + // Other popular libs use anywhere from 2 (hammer.js) to 15, + // so 10 seems like a nice, round number. const tapMovementThreshold = 10; // The maximum length a touch can be while still being considered a tap @@ -1272,6 +1567,12 @@ class Component { if (touchTime < touchTimeThreshold) { // Don't let browser turn this into a click event.preventDefault(); + /** + * Triggered when a `Component` is tapped. + * + * @event Component#tap + * @type {EventTarget~Event} + */ this.trigger('tap'); // It may be good to copy the touchend event object and change the // type to tap, if the other event properties aren't exact after @@ -1282,25 +1583,27 @@ class Component { } /** - * Report user touch activity when touch events occur - * User activity is used to determine when controls should show/hide. It's - * relatively simple when it comes to mouse events, because any mouse event - * should show the controls. So we capture mouse events that bubble up to the - * player and report activity when that happens. - * With touch events it isn't as easy. We can't rely on touch events at the - * player level, because a tap (touchstart + touchend) on the video itself on - * mobile devices is meant to turn controls off (and on). User activity is - * checked asynchronously, so what could happen is a tap event on the video - * turns the controls off, then the touchend event bubbles up to the player, - * which if it reported user activity, would turn the controls right back on. - * (We also don't want to completely block touch events from bubbling up) - * Also a touchmove, touch+hold, and anything other than a tap is not supposed - * to turn the controls back on on a mobile device. - * Here we're setting the default component behavior to report user activity - * whenever touch events happen, and this can be turned off by components that - * want touch events to act differently. - * - * @method enableTouchActivity + * This function reports user activity whenever touch events happen. This can get + * turned off by any sub-components that wants touch events to act another way. + * + * Report user touch activity when touch events occur. User activity gets used to + * determine when controls should show/hide. It is simple when it comes to mouse + * events, because any mouse event should show the controls. So we capture mouse + * events that bubble up to the player and report activity when that happens. + * With touch events it isn't as easy as `touchstart` and `touchend` toggle player + * controls. So touch events can't help us at the player level either. + * + * User activity gets checked asynchronously. So what could happen is a tap event + * on the video turns the controls off. Then the `touchend` event bubbles up to + * the player. Which, if it reported user activity, would turn the controls right + * back on. We also don't want to completely block touch events from bubbling up. + * Furthermore a `touchmove` event and anything other than a tap, should not turn + * controls back on. + * + * @listens Component#touchstart + * @listens Component#touchmove + * @listens Component#touchend + * @listens Component#touchcancel */ enableTouchActivity() { // Don't continue if the root player doesn't support reporting user activity @@ -1335,19 +1638,42 @@ class Component { } /** - * Creates timeout and sets up disposal automatically. + * A callback that has no parameters and is bound into `Component`s context. * - * @param {Function} fn The function to run after the timeout. - * @param {Number} timeout Number of ms to delay before executing specified function. - * @return {Number} Returns the timeout ID - * @method setTimeout + * @callback Component~GenericCallback + * @this Component + */ + + /** + * Creates a function that runs after an `x` millisecond timeout. This function is a + * wrapper around `window.setTimeout`. There are a few reasons to use this one + * instead though: + * 1. It gets cleared via {@link Component#clearTimeout} when + * {@link Component#dispose} gets called. + * 2. The function callback will gets turned into a {@link Component~GenericCallback} + * + * > Note: You can use `window.clearTimeout` on the id returned by this function. This + * will cause its dispose listener not to get cleaned up! Please use + * {@link Component#clearTimeout} or {@link Component#dispose}. + * + * @param {Component~GenericCallback} fn + * The function that will be run after `timeout`. + * + * @param {number} timeout + * Timeout in milliseconds to delay before executing the specified function. + * + * @return {number} + * Returns a timeout ID that gets used to identify the timeout. It can also + * get used in {@link Component#clearTimeout} to clear the timeout that + * was set. + * + * @listens Component#dispose + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout} */ setTimeout(fn, timeout) { fn = Fn.bind(this, fn); - // window.setTimeout would be preferable here, but due to some bizarre issue with Sinon and/or Phantomjs, we can't. const timeoutId = window.setTimeout(fn, timeout); - const disposeFn = function() { this.clearTimeout(timeoutId); }; @@ -1360,11 +1686,19 @@ class Component { } /** - * Clears a timeout and removes the associated dispose listener + * Clears a timeout that gets created via `window.setTimeout` or + * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout} + * use this function instead of `window.clearTimout`. If you don't your dispose + * listener will not get cleaned up until {@link Component#dispose}! + * + * @param {number} timeoutId + * The id of the timeout to clear. The return value of + * {@link Component#setTimeout} or `window.setTimeout`. * - * @param {Number} timeoutId The id of the timeout to clear - * @return {Number} Returns the timeout ID - * @method clearTimeout + * @return {number} + * Returns the timeout id that was cleared. + * + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout} */ clearTimeout(timeoutId) { window.clearTimeout(timeoutId); @@ -1379,12 +1713,24 @@ class Component { } /** - * Creates an interval and sets up disposal automatically. + * Creates a function that gets run every `x` milliseconds. This function is a wrapper + * around `window.setInterval`. There are a few reasons to use this one instead though. + * 1. It gets cleared via {@link Component#clearInterval} when + * {@link Component#dispose} gets called. + * 2. The function callback will be a {@link Component~GenericCallback} + * + * @param {Component~GenericCallback} fn + * The function to run every `x` seconds. + * + * @param {number} interval + * Execute the specified function every `x` milliseconds. * - * @param {Function} fn The function to run every N seconds. - * @param {Number} interval Number of ms to delay before executing specified function. - * @return {Number} Returns the interval ID - * @method setInterval + * @return {number} + * Returns an id that can be used to identify the interval. It can also be be used in + * {@link Component#clearInterval} to clear the interval. + * + * @listens Component#dispose + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval} */ setInterval(fn, interval) { fn = Fn.bind(this, fn); @@ -1403,11 +1749,19 @@ class Component { } /** - * Clears an interval and removes the associated dispose listener + * Clears an interval that gets created via `window.setInterval` or + * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval} + * use this function instead of `window.clearInterval`. If you don't your dispose + * listener will not get cleaned up until {@link Component#dispose}! + * + * @param {number} intervalId + * The id of the interval to clear. The return value of + * {@link Component#setInterval} or `window.setInterval`. * - * @param {Number} intervalId The id of the interval to clear - * @return {Number} Returns the interval ID - * @method clearInterval + * @return {number} + * Returns the interval id that was cleared. + * + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval} */ clearInterval(intervalId) { window.clearInterval(intervalId); @@ -1422,12 +1776,23 @@ class Component { } /** - * Registers a component + * Register a `Component` with `videojs` given the name and the component. + * + * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s + * should be registered using {@link Tech.registerTech} or + * {@link videojs:videojs.registerTech}. + * + * > NOTE: This function can also be seen on videojs as + * {@link videojs:videojs.registerComponent}. + * + * @param {string} name + * The name of the `Component` to register. + * + * @param {Component} comp + * The `Component` class to register. * - * @param {String} name Name of the component to register - * @param {Object} comp The component to register - * @static - * @method registerComponent + * @return {Component} + * The `Component` that was registered. */ static registerComponent(name, comp) { if (!name) { @@ -1460,12 +1825,18 @@ class Component { } /** - * Gets a component by name + * Get a `Component` based on the name it was registered with. + * + * @param {string} name + * The Name of the component to get. * - * @param {String} name Name of the component to get * @return {Component} - * @static - * @method getComponent + * The `Component` that got registered under the given name. + * + * @deprecated In `videojs` 6 this will not return `Component`s that were not + * registered using {@link Component.registerComponent}. Currently we + * check the global `videojs` object for a `Component` name and + * return that if it exists. */ static getComponent(name) { if (!name) { @@ -1486,23 +1857,29 @@ class Component { } /** - * Sets up the constructor using the supplied init method - * or uses the init of the parent object + * Sets up the constructor using the supplied init method or uses the init of the + * parent object. + * + * @param {Object} [props={}] + * An object of properties. + * + * @return {Object} + * the extended object. * - * @param {Object} props An object of properties - * @static - * @deprecated - * @method extend + * @deprecated since version 5 */ static extend(props) { props = props || {}; - log.warn('Component.extend({}) has been deprecated, use videojs.extend(Component, {}) instead'); + log.warn('Component.extend({}) has been deprecated, ' + + ' use videojs.extend(Component, {}) instead' + ); // Set up the constructor using the supplied init method // or using the init of the parent object // Make sure to check the unobfuscated version for external libs - const init = props.init || props.init || this.prototype.init || this.prototype.init || function() {}; + const init = props.init || props.init || this.prototype.init || + this.prototype.init || function() {}; // In Resig's simple class inheritance (previously used) the constructor // is a function that calls `this.init.apply(arguments)` // However that would prevent us from using `ParentObject.call(this);` diff --git a/src/js/error-display.js b/src/js/error-display.js index 0959040288..54ef6a2579 100644 --- a/src/js/error-display.js +++ b/src/js/error-display.js @@ -6,18 +6,21 @@ import ModalDialog from './modal-dialog'; import mergeOptions from './utils/merge-options'; /** - * Display that an error has occurred making the video unplayable. + * A display that indicates an error has occurred. This means that the video + * is unplayable. * * @extends ModalDialog - * @class ErrorDisplay */ class ErrorDisplay extends ModalDialog { /** - * Constructor for error display modal. + * Creates an instance of this class. * * @param {Player} player + * The `Player` that this class should be attached to. + * * @param {Object} [options] + * The key/value store of player options. */ constructor(player, options) { super(player, options); @@ -25,22 +28,22 @@ class ErrorDisplay extends ModalDialog { } /** - * Include the old class for backward-compatibility. + * Builds the default DOM `className`. * - * This can be removed in 6.0. + * @return {string} + * The DOM `className` for this object. * - * @method buildCSSClass - * @deprecated - * @return {String} + * @deprecated Since version 5. */ buildCSSClass() { return `vjs-error-display ${super.buildCSSClass()}`; } /** - * Generates the modal content based on the player error. + * Gets the localized error message based on the `Player`s error. * - * @return {String|Null} + * @return {string} + * The `Player`s error message localized or an empty string. */ content() { const error = this.player().error(); @@ -49,6 +52,11 @@ class ErrorDisplay extends ModalDialog { } } +/** + * The default options for an `ErrorDisplay`. + * + * @private + */ ErrorDisplay.prototype.options_ = mergeOptions(ModalDialog.prototype.options_, { fillAlways: true, temporary: false, diff --git a/src/js/event-target.js b/src/js/event-target.js index 2c738df3cd..8a100ef4aa 100644 --- a/src/js/event-target.js +++ b/src/js/event-target.js @@ -1,12 +1,73 @@ /** - * @file event-target.js + * @file src/js/event-target.js */ import * as Events from './utils/events.js'; +/** + * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It + * adds shorthand functions that wrap around lengthy functions. For example: + * the `on` function is a wrapper around `addEventListener`. + * + * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget} + * @class EventTarget + */ const EventTarget = function() {}; +/** + * A Custom DOM event. + * + * @typedef {Object} EventTarget~Event + * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent} + */ + +/** + * All event listeners should follow the following format. + * + * @callback EventTarget~EventListener + * @this {EventTarget} + * + * @param {EventTarget~Event} event + * the event that triggered this function + * + * @param {Object} [hash] + * hash of data sent during the event + */ + +/** + * An object containing event names as keys and booleans as values. + * + * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger} + * will have extra functionality. See that function for more information. + * + * @property EventTarget.prototype.allowedEvents_ + * @private + */ EventTarget.prototype.allowedEvents_ = {}; +/** + * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a + * function that will get called when an event with a certain name gets triggered. + * + * ```js + * var foo = new EventTarget(); + * var handleBar = function() { + * console.log('bar was triggered'); + * }; + * + * foo.on('bar', handleBar); + * + * // This causes any `event listeners` for the `bar` event to get called + * // see {@link EventTarget#trigger} for more information + * foo.trigger('bar'); + * // logs 'bar was triggered' + * ``` + * + * @param {string|string[]} type + * An event name or an array of event names. + * + * @param {EventTarget~EventListener} fn + * The function to call with `EventTarget`s + */ EventTarget.prototype.on = function(type, fn) { // Remove the addEventListener alias before calling Events.on // so we don't get into an infinite type loop @@ -17,16 +78,105 @@ EventTarget.prototype.on = function(type, fn) { this.addEventListener = ael; }; +/** + * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic + * the standard DOM API. + * + * @function + * @see {@link EventTarget#on} + */ EventTarget.prototype.addEventListener = EventTarget.prototype.on; +/** + * Removes an `event listener` for a specific event from an instance of `EventTarget`. + * This makes it so that the `event listener` will no longer get called when the + * named event happens. + * + * ```js + * var foo = new EventTarget(); + * var handleBar = function() { + * console.log('bar was triggered'); + * }; + * + * // adds an `event listener` for the `bar` event + * // see {@link EventTarget#on} for more info + * foo.on('bar', handleBar); + * + * // runs all `event listeners` for the `bar` event + * // see {@link EventTarget#trigger} for more info + * foo.trigger('bar'); + * // logs 'bar was triggered' + * + * foo.off('bar', handleBar); + * foo.trigger('bar'); + * // does nothing + * ``` + * + * @param {string|string[]} type + * An event name or an array of event names. + * + * @param {EventTarget~EventListener} fn + * The function to remove. + */ EventTarget.prototype.off = function(type, fn) { Events.off(this, type, fn); }; +/** + * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic + * the standard DOM API. + * + * @function + * @see {@link EventTarget#off} + */ EventTarget.prototype.removeEventListener = EventTarget.prototype.off; +/** + * This function will add an `event listener` that gets triggered only once. After the + * first trigger it will get removed. This is like adding an `event listener` + * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself. + * + * Using {@link EventTarget#on} and {@link EventTarget#off} to mimic {@link EventTarget#one} + * ```js + * var foo = new EventTarget(); + * var handleBar = function() { + * console.log('bar was triggered'); + * // after the first trigger remove this handler + * foo.off('bar', handleBar); + * }; + * + * foo.on('bar', handleBar); + * foo.trigger('bar'); + * // logs 'bar was triggered' + * + * foo.trigger('bar'); + * // does nothing + * ``` + * + * Using {@link EventTarget#one} + * ```js + * var foo = new EventTarget(); + * var handleBar = function() { + * console.log('bar was triggered'); + * }; + * + * // removed after the first trigger + * foo.one('bar', handleBar); + * foo.trigger('bar'); + * // logs 'bar was triggered' + * + * foo.trigger('bar'); + * // does nothing + * ``` + * + * @param {string|string[]} type + * An event name or an array of event names. + * + * @param {EventTarget~EventListener} fn + * The function to be called once for each event name. + */ EventTarget.prototype.one = function(type, fn) { - // Remove the addEventListener alias before calling Events.on + // Remove the addEventListener alialing Events.on // so we don't get into an infinite type loop const ael = this.addEventListener; @@ -35,6 +185,39 @@ EventTarget.prototype.one = function(type, fn) { this.addEventListener = ael; }; +/** + * This function causes an event to happen. This will then cause any `event listeners` + * that are waiting for that event, to get called. If there are no `event listeners` + * for an event then nothing will happen. + * + * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`. + * Trigger will also call the `on` + `uppercaseEventName` function. + * + * Example: + * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call + * `onClick` if it exists. + * + * ```js + * var foo = new EventTarget(); + * var handleBar = function() { + * console.log('bar was triggered'); + * }; + * + * foo.on('bar', handleBar); + * foo.trigger('bar'); + * // logs 'bar was triggered' + * + * foo.trigger('bar'); + * // logs 'bar was triggered' + * + * foo.trigger('foo'); + * // does nothing + * ``` + * + * @param {string|EventTarget~Event|Object} event + * The name of the event, an `Event`, or an object with a key of type set to + * an event name. + */ EventTarget.prototype.trigger = function(event) { const type = event.type || event; @@ -50,7 +233,13 @@ EventTarget.prototype.trigger = function(event) { Events.trigger(this, event); }; -// The standard DOM EventTarget.dispatchEvent() is aliased to trigger() +/** + * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic + * the standard DOM API. + * + * @function + * @see {@link EventTarget#trigger} + */ EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger; export default EventTarget;