diff --git a/package.json b/package.json index 39f348a8e6..67a4b8dd87 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "dependencies": { "global": "^4.3.0", "safe-json-parse": "^4.0.0", + "inherits": "^2.0.1", "videojs-swf": "4.5.4", "vtt.js": "git+https://github.com/gkatsev/vtt.js.git#shim-build" }, diff --git a/src/js/extends.js b/src/js/extends.js new file mode 100644 index 0000000000..a68ab0b123 --- /dev/null +++ b/src/js/extends.js @@ -0,0 +1,71 @@ +import * as Lib from './lib'; + +/** + * A combination of node inherits and babel's inherits (after transpile). + * Both work the same but node adds `super_` to the subClass + * and Bable adds the superClass as __proto__. Both seem useful. + */ +const _inherits = function (subClass, superClass) { + if (typeof superClass !== 'function' && superClass !== null) { + throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + + if (superClass) { + // node + subClass.super_ = superClass; + } +}; + +/** + * Function for subclassing using the same inheritance that + * videojs uses internally + * + * ``` + * var Button = videojs.getComponent('Button'); + * + * var MyButton = videojs.extends(Button, { + * constructor: function(player, options) { + * Button.call(this, player, options); + * }, + * + * onClick: function() { + * // doSomething + * } + * }); + * ``` + */ +const extendsFn = function(superClass, subClassMethods={}) { + let subClass = function() { + superClass.apply(this, arguments); + }; + let methods = {}; + + if (subClassMethods.constructor !== Object.prototype.constructor) { + subClass = subClassMethods.constructor; + methods = subClassMethods; + } else if (typeof subClassMethods === 'function') { + subClass = subClassMethods; + } + + _inherits(subClass, superClass); + + // Extend subObj's prototype with functions and other properties from props + for (var name in methods) { + if (methods.hasOwnProperty(name)) { + subClass.prototype[name] = methods[name]; + } + } + + return subClass; +}; + +export default extendsFn; diff --git a/src/js/video.js b/src/js/video.js index bdbec35044..7b709efdd3 100644 --- a/src/js/video.js +++ b/src/js/video.js @@ -15,7 +15,8 @@ import * as setup from './setup'; import Component from './component'; import * as Lib from './lib'; import * as Util from './util.js'; - +import Player from './player'; +import extendsFn from './extends.js'; if (typeof HTMLVideoElement === 'undefined') { document.createElement('video'); @@ -36,9 +37,10 @@ videojs.TOUCH_ENABLED = Lib.TOUCH_ENABLED; videojs.util = Util; // Probably want to keep this one for 5.0? -import Player from './player'; videojs.players = Player.players; +videojs.extends = extendsFn; + // REMOVING: We probably should not include this in 5.0 thought it would make it // more backwards compatible // // Expose but deprecate the window[componentName] method for accessing components @@ -55,6 +57,4 @@ videojs.players = Player.players; // Lib.obj.merge(module.exports[name], component); // }); - - export default videojs; diff --git a/test/api/api.js b/test/api/api.js index 483f6ad96b..c627f77dd0 100644 --- a/test/api/api.js +++ b/test/api/api.js @@ -255,4 +255,27 @@ function testHelperMakeTag(){ return videoTag; } -})(); \ No newline at end of file +test('should extend Component', function(){ + var Component = videojs.getComponent('Component'); + var MyComponent = videojs.extends(Component, { + constructor: function() { + this.bar = true; + }, + foo: function() { + return true; + } + }); + + var myComponent = new MyComponent(); + ok(myComponent instanceof Component, 'creates an instance of Component'); + ok(myComponent instanceof MyComponent, 'creates an instance of MyComponent'); + ok(myComponent.bar, 'the constructor function is used'); + ok(myComponent.foo(), 'instance methods are applied'); + + var NoMethods = videojs.extends(Component); + var noMethods = new NoMethods({}); + ok(noMethods.on, 'should extend component with no methods or constructor'); +}); + + +})();