diff --git a/Gruntfile.js b/Gruntfile.js
index 58864fc58..352cace04 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -39,7 +39,7 @@ module.exports = function(grunt) {
minified: ['test/minified.html']
},
watch: {
- files: [ "src/**/*.js" ],
+ files: [ "src/**/*.js", "test/unit/*.js" ],
tasks: "dev"
}
// Copy is broken. Waiting for an update to use.
@@ -133,6 +133,7 @@ module.exports = function(grunt) {
// + ' --formatting=pretty_print'
+ ' --js_output_file=' + dest
+ ' --create_source_map ' + dest + '.map --source_map_format=V3'
+ + ' --jscomp_warning=checkTypes --warning_level=VERBOSE';
+ ' --output_wrapper "(function() {%output%})();//@ sourceMappingURL=video.js.map"';
files.forEach(function(file){
diff --git a/src/js/component.js b/src/js/component.js
index 148dbf94a..90f0446d2 100644
--- a/src/js/component.js
+++ b/src/js/component.js
@@ -17,8 +17,11 @@ goog.require('vjs.dom');
vjs.Component = function(player, options, ready){
this.player_ = player;
- // // Allow for overridding default component options
- options = this.options_ = this.mergeOptions(this.options_, options);
+ // Make a copy of prototype.options_ to protect against overriding global defaults
+ this.options_ = vjs.obj.copy(this.options_);
+
+ // Updated options with supplied options
+ options = this.options(options);
// Get ID from options, element, or create using player ID and unique ID
this.id_ = options['id'] || ((options['el'] && options['el']['id']) ? options['el']['id'] : player.id() + '_component_' + vjs.guid++ );
@@ -95,20 +98,20 @@ vjs.Component.prototype.options_;
/**
* Deep merge of options objects
* Whenever a property is an object on both options objects
- * the two properties will be merged using mergeOptions
+ * the two properties will be merged using vjs.obj.deepMerge.
*
* This is used for merging options for child components. We
* want it to be easy to override individual options on a child
* component without having to rewrite all the other default options.
*
- * parentDefaultOptions = {
+ * Parent.prototype.options_ = {
* children: {
* 'childOne': { 'foo': 'bar', 'asdf': 'fdsa' },
* 'childTwo': {},
* 'childThree': {}
* }
* }
- * parentOptionsFromInit = {
+ * newOptions = {
* children: {
* 'childOne': { 'foo': 'baz', 'abc': '123' }
* 'childTwo': null,
@@ -116,7 +119,7 @@ vjs.Component.prototype.options_;
* }
* }
*
- * this.mergeOptions(parentDefaultOptions, parentOptionsFromInit);
+ * this.options(newOptions);
*
* RESULT
*
@@ -129,41 +132,13 @@ vjs.Component.prototype.options_;
* }
* }
*
- *
- * @param {Object} obj1 Object whose values will be overwritten
- * @param {Object} obj2 Object whose values will overwrite
+ * @param {Object} obj Object whose values will be overwritten
* @return {Object} NEW merged object. Does not return obj1.
*/
-vjs.Component.prototype.mergeOptions = function(obj1, obj2){
- var retObj, toString, hasOwnProp, propName, objDef, val1, val2;
+vjs.Component.prototype.options = function(obj){
+ if (obj === undefined) return this.options_;
- hasOwnProp = Object.prototype.hasOwnProperty;
- toString = Object.prototype.toString;
- objDef = '[object Object]';
- obj1 = obj1 || {};
- retObj = {};
-
- // Make a copy of obj1 so we don't affect the original options
- vjs.eachProp(obj1, function(name, val){
- retObj[name] = val;
- });
-
- if (!obj2) { return retObj; }
-
- for (propName in obj2){
- if (hasOwnProp.call(obj2, propName)) {
- val1 = retObj[propName];
- val2 = obj2[propName];
-
- if (toString.call(val1) === objDef && toString.call(val2) === objDef) {
- retObj[propName] = this.mergeOptions(val1, val2);
- } else {
- retObj[propName] = obj2[propName];
- }
- }
- }
-
- return retObj;
+ return this.options_ = vjs.obj.deepMerge(this.options_, obj);
};
/**
@@ -363,7 +338,7 @@ vjs.Component.prototype.initChildren = function(){
var self = this;
// Loop through components and add them to the player
- vjs.eachProp(options['children'], function(name, opts){
+ vjs.obj.each(options['children'], function(name, opts){
// Allow for disabling default components
// e.g. vjs.options['children']['posterImage'] = false
diff --git a/src/js/controls.js b/src/js/controls.js
index 7066ff17a..993728a42 100644
--- a/src/js/controls.js
+++ b/src/js/controls.js
@@ -96,7 +96,7 @@ goog.inherits(vjs.Button, vjs.Control);
vjs.Button.prototype.createEl = function(type, props){
// Add standard Aria and Tabindex info
- props = vjs.merge({
+ props = vjs.obj.merge({
className: this.buildCSSClass(),
innerHTML: '
' + (this.buttonText || 'Need Text') + '
',
role: 'button',
@@ -486,7 +486,7 @@ vjs.Slider = function(player, options){
goog.inherits(vjs.Slider, vjs.Component);
vjs.Slider.prototype.createEl = function(type, props) {
- props = vjs.merge({
+ props = vjs.obj.merge({
role: 'slider',
'aria-valuenow': 0,
'aria-valuemin': 0,
@@ -983,7 +983,7 @@ vjs.MenuItem = function(player, options){
goog.inherits(vjs.MenuItem, vjs.Button);
vjs.MenuItem.prototype.createEl = function(type, props){
- return goog.base(this, 'createEl', 'li', vjs.merge({
+ return goog.base(this, 'createEl', 'li', vjs.obj.merge({
className: 'vjs-menu-item',
innerHTML: this.options_['label']
}, props));
diff --git a/src/js/json.js b/src/js/json.js
index ae6b16cdf..3bbb63cdf 100644
--- a/src/js/json.js
+++ b/src/js/json.js
@@ -1,3 +1,9 @@
+/**
+ * @fileoverview Add JSON support
+ * @suppress {undefinedVars}
+ * (Compiler doesn't like JSON not being declared)
+ */
+
goog.provide('vjs.JSON');
/**
diff --git a/src/js/lib.js b/src/js/lib.js
index 712ee74b4..9a3e98a78 100644
--- a/src/js/lib.js
+++ b/src/js/lib.js
@@ -1,4 +1,5 @@
goog.provide('vjs.dom');
+goog.provide('vjs.obj');
goog.require('vjs');
@@ -35,6 +36,14 @@ vjs.capitalize = function(string){
return string.charAt(0).toUpperCase() + string.slice(1);
};
+/**
+ * Object functions container
+ * @type {Object}
+ */
+vjs.obj = {};
+vjs.obj.toString = Object.prototype.toString;
+vjs.obj.hasOwnProperty = Object.prototype.hasOwnProperty;
+
/**
* Loop through each property in an object and call a function
* whose arguments are (key,value)
@@ -42,33 +51,71 @@ vjs.capitalize = function(string){
* @param {Function} fn Function to be called on each property.
* @this {*}
*/
-vjs.eachProp = function(obj, fn){
- if (!obj) { return; }
-
- for (var name in obj) {
- if (obj.hasOwnProperty(name)) {
- fn.call(this, name, obj[name]);
+vjs.obj.each = function(obj, fn){
+ for (var key in obj) {
+ if (vjs.obj.hasOwnProperty.call(obj, key)) {
+ fn.call(this, key, obj[key]);
}
}
};
/**
* Merge two objects together and return the original.
- * @param {[type]} obj1 [description]
- * @param {[type]} obj2 [description]
- * @param {[type]} safe [description]
- * @return {[type]}
+ * @param {Object} obj1
+ * @param {Object} obj2
+ * @return {Object}
*/
-vjs.merge = function(obj1, obj2){
- // Make sure second object exists
+vjs.obj.merge = function(obj1, obj2){
if (!obj2) { return obj1; }
-
- for (var propName in obj2){
- if (obj2.hasOwnProperty(propName)) { obj1[propName] = obj2[propName]; }
+ for (var key in obj2){
+ if (vjs.obj.hasOwnProperty.call(obj2, key)) {
+ obj1[key] = obj2[key];
+ }
}
return obj1;
};
+/**
+ * Merge two objects, and merge any properties that are objects
+ * instead of just overwriting one. Uses to merge options hashes
+ * where deeper default settings are important.
+ * @param {Object} obj1 Object to override
+ * @param {Object} obj2 Overriding object
+ * @return {Object} New object. Obj1 and Obj2 will be untouched.
+ */
+vjs.obj.deepMerge = function(obj1, obj2){
+ var key, val1, val2, objDef;
+ objDef = '[object Object]';
+
+ // Make a copy of obj1 so we're not ovewriting original values.
+ // like prototype.options_ and all sub options objects
+ obj1 = vjs.obj.copy(obj1);
+
+ for (key in obj2){
+ if (vjs.obj.hasOwnProperty.call(obj2, key)) {
+ val1 = obj1[key];
+ val2 = obj2[key];
+
+ // Check if both properties are pure objects and do a deep merge if so
+ if (vjs.obj.toString.call(val1) === objDef && vjs.obj.toString.call(val2) === objDef) {
+ obj1[key] = vjs.obj.deepMerge(val1, val2);
+ } else {
+ obj1[key] = obj2[key];
+ }
+ }
+ }
+ return obj1;
+};
+
+/**
+ * Make a copy of the supplied object
+ * @param {Object} obj Object to copy
+ * @return {Object} Copy of object
+ */
+vjs.obj.copy = function(obj){
+ return vjs.obj.merge({}, obj);
+};
+
/**
* Bind (a.k.a proxy or Context). A simple method for changing the context of a function
It also stores a unique id on the function so it can be easily removed from events
diff --git a/src/js/media.flash.js b/src/js/media.flash.js
index f109a0779..e89a24790 100644
--- a/src/js/media.flash.js
+++ b/src/js/media.flash.js
@@ -35,7 +35,7 @@ vjs.Flash = function(player, options, ready){
playerOptions = player.options_,
// Merge default flashvars with ones passed in to init
- flashVars = vjs.merge({
+ flashVars = vjs.obj.merge({
// SWF Callback Functions
'readyFunction': 'videojs.Flash.onReady',
@@ -51,13 +51,13 @@ vjs.Flash = function(player, options, ready){
}, options['flashVars']),
// Merge default parames with ones passed in
- params = vjs.merge({
+ params = vjs.obj.merge({
'wmode': 'opaque', // Opaque is needed to overlay controls, but can affect playback performance
'bgcolor': '#000000' // Using bgcolor prevents a white flash when the object is loading
}, options['params']),
// Merge default attributes with ones passed in
- attributes = vjs.merge({
+ attributes = vjs.obj.merge({
'id': objId,
'name': objId, // Both ID and Name needed or swf to identifty itself
'class': 'vjs-tech'
@@ -285,16 +285,19 @@ var createGetter = function(attr){
api[attr] = function(){ return this.el_.vjs_getProperty(attr); };
};
-// Create getter and setters for all read/write attributes
-for (var i = 0; i < readWrite.length; i++) {
- createGetter(readWrite[i]);
- createSetter(readWrite[i]);
-}
+(function(){
+ var i;
+ // Create getter and setters for all read/write attributes
+ for (i = 0; i < readWrite.length; i++) {
+ createGetter(readWrite[i]);
+ createSetter(readWrite[i]);
+ }
-// Create getters for read-only attributes
-for (var i = 0; i < readOnly.length; i++) {
- createGetter(readOnly[i]);
-}
+ // Create getters for read-only attributes
+ for (i = 0; i < readOnly.length; i++) {
+ createGetter(readOnly[i]);
+ }
+})();
/* Flash Support Testing -------------------------------------------------------- */
@@ -434,13 +437,13 @@ vjs.Flash.getEmbedCode = function(swf, flashVars, params, attributes){
// Convert flash vars to string
if (flashVars) {
- vjs.eachProp(flashVars, function(key, val){
+ vjs.obj.each(flashVars, function(key, val){
flashVarsString += (key + '=' + val + '&');
});
}
// Add swf, flashVars, and other default params
- params = vjs.merge({
+ params = vjs.obj.merge({
'movie': swf,
'flashvars': flashVarsString,
'allowScriptAccess': 'always', // Required to talk to swf
@@ -448,11 +451,11 @@ vjs.Flash.getEmbedCode = function(swf, flashVars, params, attributes){
}, params);
// Create param tags string
- vjs.eachProp(params, function(key, val){
+ vjs.obj.each(params, function(key, val){
paramsString += '';
});
- attributes = vjs.merge({
+ attributes = vjs.obj.merge({
// Add swf to attributes (need both for IE and Others to work)
'data': swf,
@@ -463,7 +466,7 @@ vjs.Flash.getEmbedCode = function(swf, flashVars, params, attributes){
}, attributes);
// Create Attributes string
- vjs.eachProp(attributes, function(key, val){
+ vjs.obj.each(attributes, function(key, val){
attrsString += (key + '="' + val + '" ');
});
diff --git a/src/js/player.js b/src/js/player.js
index 579c79c95..79339a1ec 100644
--- a/src/js/player.js
+++ b/src/js/player.js
@@ -17,7 +17,7 @@ vjs.Player = function(tag, options, ready){
// which overrides globally set options.
// This latter part coincides with the load order
// (tag must exist before Player)
- options = this.mergeOptions(this.getTagSettings(tag), options);
+ options = vjs.obj.merge(this.getTagSettings(tag), options);
// Cache for video property values.
this.cache_ = {};
@@ -93,7 +93,7 @@ vjs.Player.prototype.getTagSettings = function(tag){
'tracks': []
};
- vjs.merge(options, vjs.getAttributeValues(tag));
+ vjs.obj.merge(options, vjs.getAttributeValues(tag));
// Get tag children settings
if (tag.hasChildNodes()) {
@@ -216,7 +216,7 @@ vjs.Player.prototype.loadTech = function(techName, source){
};
// Grab tech-specific options from player options and add source and parent element to use.
- var techOptions = vjs.merge({ source: source, parentEl: this.el_ }, this.options_[techName.toLowerCase()]);
+ var techOptions = vjs.obj.merge({ source: source, parentEl: this.el_ }, this.options_[techName.toLowerCase()]);
if (source) {
if (source.src == this.cache_.src && this.cache_.currentTime > 0) {
@@ -832,7 +832,7 @@ vjs.Player.prototype.poster_;
/**
* Get or set the poster image source url.
* @param {String} src Poster image source URL
- * @return {String=} Poster image source URL or null
+ * @return {String} Poster image source URL or null
*/
vjs.Player.prototype.poster = function(src){
if (src !== undefined) {
diff --git a/src/js/tracks.js b/src/js/tracks.js
index 742399e87..2bd1af3a8 100644
--- a/src/js/tracks.js
+++ b/src/js/tracks.js
@@ -1012,7 +1012,7 @@ vjs.ChaptersTrackMenuItem.prototype.update = function(){
};
// Add Buttons to controlBar
-vjs.merge(vjs.ControlBar.prototype.options_['children'], {
+vjs.obj.merge(vjs.ControlBar.prototype.options_['children'], {
'subtitlesButton': {},
'captionsButton': {},
'chaptersButton': {}
diff --git a/test/unit/component.js b/test/unit/component.js
index f2d098137..6d34754d9 100644
--- a/test/unit/component.js
+++ b/test/unit/component.js
@@ -37,24 +37,25 @@ test('should init child coponents from options', function(){
});
test('should do a deep merge of child options', function(){
- var compDefaultOptions = {
- 'children': {
+ // Create a default option for component
+ vjs.Component.prototype.options_ = {
+ 'example': {
'childOne': { 'foo': 'bar', 'asdf': 'fdsa' },
'childTwo': {},
'childThree': {}
}
}
- var compInitOptions = {
- 'children': {
+ var comp = new vjs.Component(getFakePlayer(), {
+ 'example': {
'childOne': { 'foo': 'baz', 'abc': '123' },
'childThree': null,
'childFour': {}
}
- }
+ });
- var mergedOptions = vjs.Component.prototype.mergeOptions(compDefaultOptions, compInitOptions);
- var children = mergedOptions['children'];
+ var mergedOptions = comp.options();
+ var children = mergedOptions['example'];
ok(children['childOne']['foo'] === 'baz', 'value three levels deep overridden');
ok(children['childOne']['asdf'] === 'fdsa', 'value three levels deep maintained');
@@ -62,6 +63,11 @@ test('should do a deep merge of child options', function(){
ok(children['childTwo'], 'object two levels deep maintained');
ok(children['childThree'] === null, 'object two levels deep removed');
ok(children['childFour'], 'object two levels deep added');
+
+ ok(vjs.Component.prototype.options_['example']['childOne']['foo'] === 'bar', 'prototype options were not overridden');
+
+ // Reset default component options to none
+ vjs.Component.prototype.options_ = null;
});
test('should dispose of component and children', function(){
diff --git a/test/unit/core.js b/test/unit/core.js
index 4f0ae7246..5e6390220 100644
--- a/test/unit/core.js
+++ b/test/unit/core.js
@@ -5,7 +5,7 @@ test('should create a video tag and have access children in old IE', function(){
fixture.innerHTML += "";
- vid = document.getElementById('test_vid_id');
+ var vid = document.getElementById('test_vid_id');
ok(vid.childNodes.length === 1);
ok(vid.childNodes[0].getAttribute('type') === 'video/mp4');
diff --git a/test/unit/lib.js b/test/unit/lib.js
index 910d130c1..526db0e84 100644
--- a/test/unit/lib.js
+++ b/test/unit/lib.js
@@ -22,13 +22,25 @@ test('should loop through each property on an object', function(){
}
// Add 3 to each value
- vjs.eachProp(asdf, function(key, value){
+ vjs.obj.each(asdf, function(key, value){
asdf[key] = value + 3;
});
deepEqual(asdf,{a:4,b:5,'c':6})
});
+test('should copy an object', function(){
+ var asdf = {
+ a: 1,
+ b: 2,
+ 'c': 3
+ }
+
+ var fdsa = vjs.obj.copy(asdf);
+
+ deepEqual(asdf,fdsa)
+});
+
test('should add context to a function', function(){
var newContext = { test: 'obj'};
var asdf = function(){
diff --git a/test/unit/mediafaker.js b/test/unit/mediafaker.js
index 332623f17..6381eef52 100644
--- a/test/unit/mediafaker.js
+++ b/test/unit/mediafaker.js
@@ -1,6 +1,9 @@
-// Fake a media playback tech controller so that player tests
+// Fake a media playback tech controller so that player tests
// can run without HTML5 or Flash, of which PhantomJS supports neither.
+/**
+ * @constructor
+ */
vjs.MediaFaker = function(player, options, onReady){
goog.base(this, player, options, onReady);
@@ -30,4 +33,4 @@ vjs.MediaFaker.prototype.volume = function(){ return 0; };
goog.exportSymbol('videojs.MediaFaker', vjs.MediaFaker);
goog.exportProperty(vjs.MediaFaker, 'isSupported', vjs.MediaFaker.isSupported);
-goog.exportProperty(vjs.MediaFaker, 'canPlaySource', vjs.MediaFaker.canPlaySource);
\ No newline at end of file
+goog.exportProperty(vjs.MediaFaker, 'canPlaySource', vjs.MediaFaker.canPlaySource);
diff --git a/test/unit/player.js b/test/unit/player.js
index d4331b5d6..524a7bc40 100644
--- a/test/unit/player.js
+++ b/test/unit/player.js
@@ -8,12 +8,13 @@ var PlayerTest = {
return videoTag;
},
makePlayer: function(playerOptions){
+ var player;
var videoTag = PlayerTest.makeTag();
var fixture = document.getElementById('qunit-fixture');
fixture.appendChild(videoTag);
- var opts = vjs.merge({
+ var opts = vjs.obj.merge({
'techOrder': ['mediaFaker']
}, playerOptions);