Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' into event-defaults

Conflicts:
	CHANGELOG
  • Loading branch information...
commit 0959da37257c973f235107707021812743fe40f5 2 parents f5afd05 + 6b51a23
David Aurelio davidaurelio authored
Showing with 527 additions and 343 deletions.
  1. +3 −0  CHANGELOG
  2. +0 −5 example/library/index.html
  3. +7 −9 example/library/movies/assets/submovies/redpanda.js
  4. +14 −15 example/library/movies/bitmap-move-rotation.js
  5. +8 −9 example/library/movies/bitmap.js
  6. +9 −12 example/library/movies/fill-image.js
  7. +105 −116 example/library/movies/filter-list.js
  8. +27 −30 example/library/movies/filter-multi.js
  9. +1 −1  example/library/movies/movie_list.js
  10. +1 −1  example/library/movies/submovie-basic.js
  11. +1 −1  example/library/movies/submovie-with-asset.js
  12. +34 −29 src/bootstrapper/context/iframe/bootstrap.js
  13. +25 −19 src/bootstrapper/context/node/context.js
  14. +8 −4 src/bootstrapper/context/script_loader.js
  15. +13 −7 src/bootstrapper/context/worker/bootstrap.js
  16. +2 −2 src/color.js
  17. +15 −0 src/event_emitter.js
  18. +47 −0 src/runner/asset_display_object.js
  19. +42 −17 src/runner/bitmap.js
  20. +6 −2 src/runner/font_family.js
  21. +0 −1  src/runner/gradient.js
  22. +19 −4 src/runner/movie.js
  23. +1 −27 src/runner/stage.js
  24. +12 −11 src/runner/video.js
  25. +29 −0 test/asset_display_object-spec.js
  26. +73 −0 test/bitmap-spec.js
  27. +18 −14 test/integration/assets/integration.js
  28. +5 −6 test/movie-spec.js
  29. +2 −1  test/runner.html
3  CHANGELOG
View
@@ -1,7 +1,10 @@
v0.2.1 / 2012-07-12
-------------------
+
+* Aligned callback usage to use node-style single-callbacks. See https://github.com/uxebu/bonsai/pull/1
* Added an option to bonsai.run that allows browser events not to be preventDefault’ed:
`bonsai.run(node, {allowEventDefaults: true})`
+ see: https://github.com/uxebu/bonsai/pull/4
v0.2.0 / 2012-07-09
-------------------
5 example/library/index.html
View
@@ -81,10 +81,5 @@
});
});
- function resolve(url) { //TODO: write own resolver
- var a = document.createElement('a');
- a.href = url;
- return a.href;
- }
</script>
</body>
16 example/library/movies/assets/submovies/redpanda.js
View
@@ -1,10 +1,8 @@
-new bonsai.Bitmap('../redpanda.jpg', {
- onload: function() {
- this.attr({
- y: -256,
- x: -256
- });
- stage.addChild(this);
- this.animate('1s', {x: 0, y: 0, rotation: .2});
- }
+new bonsai.Bitmap('../redpanda.jpg', function(err) {
+ this.attr({
+ y: -256,
+ x: -256
+ });
+ stage.addChild(this);
+ this.animate('1s', {x: 0, y: 0, rotation: .2});
});
29 example/library/movies/bitmap-move-rotation.js
View
@@ -1,16 +1,15 @@
-new bonsai.Bitmap('assets/redpanda.jpg', {
- onload: function() {
- this.attr({
- origin: {x: 128, y: 128},
- y: -256,
- x: -256
- });
- stage.addChild(this);
- stage.addChild(bonsai.Shape.circle(250, 250, 128).attr({fillColor: 'rgba(255, 0, 0, .5)'}));
- this.animate('5s', {
- rotation: Math.PI * 10 + .2,
- x: 149.61,
- y: 98.41
- });
- }
+new bonsai.Bitmap('assets/redpanda.jpg', function(err) {
+ if (err) return;
+ this.attr({
+ origin: {x: 128, y: 128},
+ y: -256,
+ x: -256
+ });
+ stage.addChild(this);
+ stage.addChild(bonsai.Shape.circle(250, 250, 128).attr({fillColor: 'rgba(255, 0, 0, .5)'}));
+ this.animate('5s', {
+ rotation: Math.PI * 10 + .2,
+ x: 149.61,
+ y: 98.41
+ });
});
17 example/library/movies/bitmap.js
View
@@ -1,10 +1,9 @@
-new bonsai.Bitmap('assets/redpanda.jpg', {
- onload: function() {
- this.attr({
- y: -256,
- x: -256
- });
- stage.addChild(this);
- this.animate('1s', {x: 0, y: 0, rotation: .2});
- }
+new bonsai.Bitmap('assets/redpanda.jpg', function(err) {
+ if (err) return;
+ this.attr({
+ y: -256,
+ x: -256
+ });
+ stage.addChild(this);
+ this.animate('1s', {x: 0, y: 0, rotation: .2});
});
21 example/library/movies/fill-image.js
View
@@ -1,13 +1,10 @@
-new bonsai.Bitmap('assets/redpanda.jpg', {
- onload: function() {
-
- Shape.rect(0, 0, 400, 400).attr({
- fillImage: this.attr({
- width: 100,
- height: 100
- }),
- fillRepeat: 4
- }).addTo(stage);
-
- }
+new bonsai.Bitmap('assets/redpanda.jpg', function(err) {
+ if (err) return;
+ Shape.rect(0, 0, 400, 400).attr({
+ fillImage: this.attr({
+ width: 100,
+ height: 100
+ }),
+ fillRepeat: 4
+ }).addTo(stage);
});
221 example/library/movies/filter-list.js
View
@@ -1,145 +1,134 @@
/* ============ ROW 1 ============ */
-new bonsai.Bitmap('assets/redpanda.jpg', {
- onload: function() {
- this.attr({
- y: 10,
- x: 10,
- scale: 0.5,
- filters: filter.blur(1)
- });
- stage.addChild(this);
- }
+new bonsai.Bitmap('assets/redpanda.jpg', function(err) {
+ if (err) return;
+ this.attr({
+ y: 10,
+ x: 10,
+ scale: 0.5,
+ filters: filter.blur(1)
+ });
+ stage.addChild(this);
});
-new bonsai.Bitmap('assets/redpanda.jpg', {
- onload: function() {
- this.attr({
- y: 10,
- x: 150,
- scale: 0.5,
- filters: filter.sepia(1)
- });
- stage.addChild(this);
- }
+new bonsai.Bitmap('assets/redpanda.jpg', function(err) {
+ if (err) return;
+ this.attr({
+ y: 10,
+ x: 150,
+ scale: 0.5,
+ filters: filter.sepia(1)
+ });
+ stage.addChild(this);
});
-new bonsai.Bitmap('assets/redpanda.jpg', {
- onload: function() {
- this.attr({
- y: 10,
- x: 290,
- scale: 0.5,
- filters: filter.saturate(5)
- });
- stage.addChild(this);
- }
+new bonsai.Bitmap('assets/redpanda.jpg', function(err) {
+ if (err) return;
+ this.attr({
+ y: 10,
+ x: 290,
+ scale: 0.5,
+ filters: filter.saturate(5)
+ });
+ stage.addChild(this);
});
/* ============ ROW 2 ============ */
-new bonsai.Bitmap('assets/redpanda.jpg', {
- onload: function() {
- this.attr({
- y: 150,
- x: 10,
- scale: 0.5,
- filters: filter.grayscale(1)
- });
- stage.addChild(this);
- }
+new bonsai.Bitmap('assets/redpanda.jpg', function(err) {
+ if (err) return;
+ this.attr({
+ y: 150,
+ x: 10,
+ scale: 0.5,
+ filters: filter.grayscale(1)
+ });
+ stage.addChild(this);
});
-new bonsai.Bitmap('assets/redpanda.jpg', {
- onload: function() {
- this.attr({
- y: 150,
- x: 150,
- scale: 0.5,
- filters: filter.hueRotate(90)
- });
- stage.addChild(this);
- }
+new bonsai.Bitmap('assets/redpanda.jpg', function(err) {
+ if (err) return;
+ this.attr({
+ y: 150,
+ x: 150,
+ scale: 0.5,
+ filters: filter.hueRotate(90)
+ });
+ stage.addChild(this);
});
-new bonsai.Bitmap('assets/redpanda.jpg', {
- onload: function() {
- this.attr({
- y: 150,
- x: 290,
- scale: 0.5,
- filters: filter.invert(1)
- });
- stage.addChild(this);
- }
+new bonsai.Bitmap('assets/redpanda.jpg', function(err) {
+ if (err) return;
+ this.attr({
+ y: 150,
+ x: 290,
+ scale: 0.5,
+ filters: filter.invert(1)
+ });
+ stage.addChild(this);
});
/* ============ ROW 3 ============ */
-new bonsai.Bitmap('assets/redpanda.jpg', {
- onload: function() {
- this.attr({
- y: 290,
- x: 10,
- scale: 0.5,
- filters: filter.brightness(2)
- });
- stage.addChild(this);
- }
+new bonsai.Bitmap('assets/redpanda.jpg', function(err) {
+ if (err) return;
+ this.attr({
+ y: 290,
+ x: 10,
+ scale: 0.5,
+ filters: filter.brightness(2)
+ });
+ stage.addChild(this);
});
-new bonsai.Bitmap('assets/redpanda.jpg', {
- onload: function() {
- this.attr({
- y: 290,
- x: 150,
- scale: 0.5,
- filters: filter.contrast(2)
- });
- stage.addChild(this);
- }
+new bonsai.Bitmap('assets/redpanda.jpg', function(err) {
+ if (err) return;
+ this.attr({
+ y: 290,
+ x: 150,
+ scale: 0.5,
+ filters: filter.contrast(2)
+ });
+ stage.addChild(this);
});
-new bonsai.Bitmap('assets/redpanda.jpg', {
- onload: function() {
- this.attr({
- y: 290,
- x: 290,
- scale: 0.5,
- filters: filter.opacity(0.5)
- });
- stage.addChild(this);
- }
+new bonsai.Bitmap('assets/redpanda.jpg', function(err) {
+ if (err) return;
+ this.attr({
+ y: 290,
+ x: 290,
+ scale: 0.5,
+ filters: filter.opacity(0.5)
+ });
+ stage.addChild(this);
});
/* ============ ROW 4 ============ */
-new bonsai.Bitmap('assets/redpanda.jpg', {
- onload: function() {
- this.attr({
- y: 430,
- x: 10,
- scale: 0.5,
- filters: filter.dropShadow([0,0,5,'#000'])
- });
- stage.addChild(this);
- }
+new bonsai.Bitmap('assets/redpanda.jpg', function(err) {
+ if (err) return;
+ this.attr({
+ y: 430,
+ x: 10,
+ scale: 0.5,
+ filters: filter.dropShadow([0,0,5,'#000'])
+ });
+ stage.addChild(this);
});
-new bonsai.Bitmap('assets/redpanda.jpg', {
- onload: function() {
- this.attr({
- y: 430,
- x: 150,
- scale: 0.5,
- filters: filter.colorMatrix([
- 1, 1, 1, 0, 0,
- 1, 0.7, -1, 0, 0,
- -1, -1, -1, 0, 0,
- 0, 0, 0, 1, 0
- ])
- });
- stage.addChild(this);
- }
-});
+new bonsai.Bitmap('assets/redpanda.jpg', function(err) {
+ if (err) return;
+ this.attr({
+ y: 430,
+ x: 150,
+ scale: 0.5,
+ filters: filter.colorMatrix([
+ 1, 1, 1, 0, 0,
+ 1, 0.7, -1, 0, 0,
+ -1, -1, -1, 0, 0,
+ 0, 0, 0, 1, 0
+ ])
+ });
+ stage.addChild(this);
+});
57 example/library/movies/filter-multi.js
View
@@ -1,41 +1,38 @@
// shorthands, defaults
-new bonsai.Bitmap('assets/redpanda.jpg', {
- onload: function() {
- this.attr({
- y: 10,
- x: 10,
- scale: 0.5,
- filters: ['blur', 'sepia', 'saturate']
- });
- stage.addChild(this);
- }
+new bonsai.Bitmap('assets/redpanda.jpg', function(err) {
+ if (err) return;
+ this.attr({
+ y: 10,
+ x: 10,
+ scale: 0.5,
+ filters: ['blur', 'sepia', 'saturate']
+ });
+ stage.addChild(this);
});
// shorthand methods
-new bonsai.Bitmap('assets/redpanda.jpg', {
- onload: function() {
- this.attr({
- y: 10,
- x: 150,
- scale: 0.5,
- filters: [filter.blur(1), filter.saturate(1)]
- });
- stage.addChild(this);
- }
+new bonsai.Bitmap('assets/redpanda.jpg', function(err) {
+ if (err) return;
+ this.attr({
+ y: 10,
+ x: 150,
+ scale: 0.5,
+ filters: [filter.blur(1), filter.saturate(1)]
+ });
+ stage.addChild(this);
});
// fully qualified (and insane)
var blurFilter = new filter.Blur(10);
var saturationFilter = new filter.Saturate(100);
-new bonsai.Bitmap('assets/redpanda.jpg', {
- onload: function() {
- this.attr({
- y: 10,
- x: 290,
- scale: 0.5,
- filters: [blurFilter, saturationFilter]
- });
- stage.addChild(this);
- }
+new bonsai.Bitmap('assets/redpanda.jpg', function(err) {
+ if (err) return;
+ this.attr({
+ y: 10,
+ x: 290,
+ scale: 0.5,
+ filters: [blurFilter, saturationFilter]
+ });
+ stage.addChild(this);
});
2  example/library/movies/movie_list.js
View
@@ -86,7 +86,7 @@ movieList = {
'layering.js'
],
'Logo': [
- 'bonsai-logo-full.js'
+ 'bikeshed-logo-full.js'
],
'Misc': [
'stepping_feet.js',
2  example/library/movies/submovie-basic.js
View
@@ -1,3 +1,3 @@
-stage.loadSubMovie('assets/submovies/redsquare.js', function(movie) {
+stage.loadSubMovie('assets/submovies/redsquare.js', function(err, movie) {
movie.addTo(stage);
});
2  example/library/movies/submovie-with-asset.js
View
@@ -1,3 +1,3 @@
-stage.loadSubMovie('assets/submovies/redpanda.js', function(movie) {
+stage.loadSubMovie('assets/submovies/redpanda.js', function(err, movie) {
movie.addTo(stage);
});
63 src/bootstrapper/context/iframe/bootstrap.js
View
@@ -5,18 +5,21 @@ define([
], function(Stage, makeScriptLoader, tools) {
'use strict';
- function loadUrl(url, successCallback, errorCallback) {
- var xhr = new XMLHttpRequest();
- xhr.open('GET', url);
- xhr.onload = function() {
- if (xhr.status >= 200 || xhr.status < 300 || xhr.status == 304) {
- successCallback(this.responseText);
+ function exposePlugins(isGlobal, target, mixin) {
+ for (var i in mixin) {
+ if (i === 'stage') {
+ continue; // don't allow stage to be overwritten
+ }
+ if (isGlobal) {
+ // Make sure any global assignment errors don't prevent other
+ // properties from being exposed. (e.g. trying to expose `Infinity`)
+ try {
+ target[i] = mixin[i];
+ } catch(e) {}
} else {
- errorCallback();
+ target[i] = mixin[i];
}
- };
- xhr.onerror = errorCallback;
- xhr.send(null);
+ }
}
return function(messageChannel, iframeWindow) {
@@ -26,7 +29,8 @@ define([
var loader = makeScriptLoader(function(url, cb) {
var script = doc.createElement('script');
script.src = url;
- script.onload = cb;
+ script.onload = function() { cb(null); };
+ script.onerror = function() { cb('Could not load: ' + url); };
doc.documentElement.appendChild(script);
});
@@ -34,15 +38,15 @@ define([
iframeWindow.wait = function() { return loader.wait(); };
iframeWindow.done = function() { return loader.done(); };
- var stage = new Stage(messageChannel, loadUrl);
+ var stage = new Stage(messageChannel);
var env = stage.env.exports;
// Expose bonsai API in iframe window
tools.mixin(iframeWindow, env);
- iframeWindow.exports = {}; // for plugins
+ var globalExports = iframeWindow.exports = {}; // for plugins
// As per the boostrap's contract, it must provide stage.loadSubMovie
- stage.loadSubMovie = function(movieUrl, doDone, doError, movieInstance) {
+ stage.loadSubMovie = function(movieUrl, callback, movieInstance) {
var iframe = doc.createElement('iframe');
doc.documentElement.appendChild(iframe);
@@ -54,20 +58,31 @@ define([
// (Opera would initiate a separate script context if we did it after)
subWindow.document.open();
subWindow.document.close();
+
+ // Expose bonsai on sub-movie:
tools.mixin(subWindow, subEnvironment.exports);
+ // Expose top-level plugin exports on every sub-movie:
+ exposePlugins(false, subWindow.bonsai, globalExports);
+ exposePlugins(true, subWindow, globalExports);
+
subWindow.stage = subMovie;
subMovie.root = this;
var subLoader = makeScriptLoader(function(url, cb) {
var script = subWindow.document.createElement('script');
script.src = url;
- script.onload = cb;
+ script.onload = function() { cb(null); };
+ script.onerror = function() { cb('Could not load: ' + url); };
subWindow.document.documentElement.appendChild(script);
});
- subLoader.load(stage.assetBaseUrl.resolveUri(movieUrl), function() {
- doDone && doDone.call(subMovie, subMovie);
+ subLoader.load(stage.assetBaseUrl.resolveUri(movieUrl), function(err) {
+ if (err) {
+ callback.call(subMovie, err);
+ } else {
+ callback.call(subMovie, null, subMovie);
+ }
});
};
@@ -83,18 +98,8 @@ define([
} else if (message.command === 'runScript') {
loader.load('data:text/javascript,' + encodeURIComponent(message.code));
} else if (message.command === 'exposePluginExports') {
- var exports = iframeWindow.exports;
- for (var i in exports) {
- if (i === 'stage') {
- continue; // don't allow stage to be overwritten
- }
- // Make sure any global assignment errors don't prevent other
- // properties from being exposed. (e.g. trying to expose `NaN`)
- try {
- iframeWindow[i] = exports[i];
- env[i] = exports[i];
- } catch(e) {}
- }
+ exposePlugins(false, env, globalExports);
+ exposePlugins(true, iframeWindow, globalExports);
}
});
44 src/bootstrapper/context/node/context.js
View
@@ -37,20 +37,25 @@ requirejs.requirejs([
},
init: function(options) {
+
var messageChannel = this.messageChannel =
new this.MessageChannel(tools.hitch(this, this.notify), function() {});
+
var vmContext = this.vmContext = this.vm.createContext();
- var scriptLoader = this.scriptLoader =
- makeScriptLoader(this._importScript.bind(null, this.vm, vmContext));
+ var scriptLoader = this.scriptLoader = makeScriptLoader(
+ this._importScript.bind(null, this.vm, vmContext)
+ );
var stage = this.initVmContext(vmContext, messageChannel, scriptLoader);
+
this.initStage(stage);
this.startMovie(stage);
},
initStage: function(stage) {
+ var context = this;
var env = stage.env;
- stage.loadSubMovie = function(movieUrl, doDone, doError, movieInstance) {
+ stage.loadSubMovie = function(movieUrl, callback, movieInstance) {
movieUrl = this.assetBaseUrl.resolveUri(movieUrl);
var subMovie = movieInstance || new env.Movie();
@@ -76,16 +81,20 @@ requirejs.requirejs([
functionArgValues.push(subEnvExports[i]);
}
- this.loadUrl(movieUrl, function(code) {
- functionArgNames.push(code); // Actual code to execute
- Function.apply(null, functionArgNames).apply(subMovie, functionArgValues);
- doDone && doDone.call(subMovie, subMovie);
+ context._loadUrl(movieUrl, function(err, code) {
+ if (err) {
+ callback.call(subMovie, err);
+ } else {
+ functionArgNames.push(code); // Actual code to execute
+ Function.apply(null, functionArgNames).apply(subMovie, functionArgValues);
+ callback.call(subMovie, null, subMovie);
+ }
});
}
},
initVmContext: function(context, messageChannel, scriptLoader) {
- var stage = context.stage = new Stage(messageChannel, this._loadUrl);
+ var stage = context.stage = new Stage(messageChannel);
// expose bonsain API in vm context
var env = stage.env.exports;
@@ -134,21 +143,18 @@ requirejs.requirejs([
this.scriptLoader.load(url, this.emit.bind(this, 'scriptLoaded', url));
},
- _loadUrl: function(url, successCallback, errorCallback) {
- fs.readFile(url, 'utf-8', function(error, data) {
- if (error) {
- errorCallback();
- } else {
- successCallback(data);
- }
- });
+ _loadUrl: function(url, callback) {
+ fs.readFile(url, 'utf-8', callback);
},
_importScript: function(vm, vmContext, url, callback) {
fs.readFile(url, 'utf-8', function(error, script) {
- if (error) { throw error; }
- vm.runInContext(script, vmContext, url);
- callback();
+ if (error) {
+ callback(error);
+ } else {
+ vm.runInContext(script, vmContext, url);
+ callback(null);
+ }
});
}
}, EventEmitter);
12 src/bootstrapper/context/script_loader.js
View
@@ -30,14 +30,18 @@ define(function() {
this.isLoading = true;
- loadFn(url, function() {
+ loadFn(url, function(err) {
+ if (err) {
+ cb(err);
+ return;
+ }
if (cb) {
if (me.isWaiting) {
// If we're waiting then we shouldn't fire the cb until
// done() has been called.
waitingCallbacks.push(cb);
} else {
- cb();
+ cb(null);
}
}
me.isLoading = false;
@@ -56,7 +60,7 @@ define(function() {
if (!this.isWaiting) {
// Call any queued callbacks:
for (var i = 0, l = this.waitingCallbacks.length; i < l; ++i) {
- this.waitingCallbacks[i]();
+ this.waitingCallbacks[i](null);
}
}
if (this.queue.length) {
@@ -65,4 +69,4 @@ define(function() {
}
};
};
-});
+});
20 src/bootstrapper/context/worker/bootstrap.js
View
@@ -9,10 +9,10 @@ define([
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function() {
- if (xhr.status >= 200 || xhr.status < 300 || xhr.status == 304) {
+ if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
successCallback(this.responseText);
} else {
- errorCallback();
+ errorCallback(xhr.status + ' (' + xhr.statusText + ')');
}
};
xhr.onerror = errorCallback;
@@ -42,15 +42,19 @@ define([
}
var loader = makeScriptLoader(function(url, cb) {
- importScripts(url);
- cb();
+ try {
+ importScripts(url);
+ cb(null);
+ } catch(e) {
+ console.log('>>ERROR WORKER', e);
+ }
});
self.load = function(url, cb) { return loader.load(url, cb); };
self.wait = function() { return loader.wait(); };
self.done = function() { return loader.done(); };
- var stage = new Stage(messageChannel, loadUrl);
+ var stage = new Stage(messageChannel);
var env = stage.env.exports;
// Expose bonsai API in iframe window
tools.mixin(self, env);
@@ -78,7 +82,7 @@ define([
});
// As per the boostrap's contract, it must provide stage.loadSubMovie
- stage.loadSubMovie = function(movieUrl, doDone, doError, movieInstance) {
+ stage.loadSubMovie = function(movieUrl, callback, movieInstance) {
movieUrl = this.assetBaseUrl.resolveUri(movieUrl);
@@ -108,7 +112,9 @@ define([
loadUrl(movieUrl, function(code) {
functionArgNames.push(code); // Actual code to execute
Function.apply(null, functionArgNames).apply(subMovie, functionArgValues);
- doDone && doDone.call(subMovie, subMovie);
+ callback.call(subMovie, null, subMovie);
+ }, function(error) {
+ callback.call(subMovie, error + ':' + movieUrl);
});
};
4 src/color.js
View
@@ -250,10 +250,10 @@ function(tools, colorMap) {
break;
default:
// h,s,l,a
- this.set(prop, range ? min(
+ this.set(prop, range ? max(0, min(
1,
(n * range) + current - range/2
- ) : n);
+ )) : n);
}
return this;
15 src/event_emitter.js
View
@@ -90,6 +90,21 @@ define([
},
/**
+ * Executes all registered listeners of the specified event type after
+ * the current execution block completes (i.e. asynchronously)
+ *
+ * @param {String} type The event type.
+ * @param {Mixed} [args1, ...] Any number of arguments to pass to the listener.
+ * @returns {Object} The host object.
+ */
+ emitAsync: function(type) {
+ var me = this, args = arguments;
+ setTimeout(function() {
+ me.emit.apply(me, args);
+ }, 1);
+ },
+
+ /**
* Executes all registered listeners of the specified event type.
*
* @param {String} type The event type.
47 src/runner/asset_display_object.js
View
@@ -0,0 +1,47 @@
+/**
+ * Contains the AssetDisplayObject class.
+ * @exports AssetDisplayObject
+ * @requires module:display_object
+ */
+define(['../tools'], function(tools) {
+ 'use strict';
+
+ /**
+ * The AssetDisplayObject mixin
+ *
+ * @mixin
+ */
+ var AssetDisplayObject = {
+ /**
+ * Registers events for a callback function which'll be triggered for both
+ * `error` and `load` events (i.e. node-style callback, `function(err, data) {}`)
+ *
+ * @param {Function} callback The function to be triggered upon `load` or `error`
+ *
+ * @returns {this}
+ */
+ bindAssetCallback: function(callback) {
+
+ // We need to unbind both events after either one is fired:
+ var unbind = tools.hitch(this, function() {
+ this.removeListener('load', loadHandler);
+ this.removeListener('error', errorHandler);
+ });
+
+ function loadHandler() {
+ unbind();
+ callback.call(this, null, this);
+ }
+ function errorHandler(error) {
+ unbind();
+ callback.call(this, error, this);
+ }
+
+ this.on('load', loadHandler);
+ this.on('error', errorHandler);
+ return this;
+ }
+ };
+
+ return AssetDisplayObject;
+});
59 src/runner/bitmap.js
View
@@ -7,9 +7,10 @@
*/
define([
'./display_object',
+ './asset_display_object',
'../asset/asset_request',
'../tools'
-], function(DisplayObject, AssetRequest, tools) {
+], function(DisplayObject, AssetDisplayObject, AssetRequest, tools) {
'use strict';
var data = tools.descriptorData, accessor = tools.descriptorAccessor;
@@ -43,18 +44,14 @@ define([
* @property {number} __supportedAttributes__.width The width of the bitmap.
*
*/
- function Bitmap(loader, source, options) {
- options || (options = {});
+ function Bitmap(loader, source, callback) {
+
this._loader = loader;
DisplayObject.call(this);
- if (options.onload) {
- this.on('load', options.onload);
- }
- if (options.onerror) {
- // TODO: choose diff evt name to avoid special 'error' treatment in eventemitter
- this.on('error', options.onerror);
+ if (callback) {
+ this.bindAssetCallback(callback);
}
this.type = 'Bitmap';
@@ -77,7 +74,7 @@ define([
this.attr('source', source);
}
- var proto = Bitmap.prototype = Object.create(DisplayObject.prototype);
+ var proto = Bitmap.prototype = tools.mixin(Object.create(DisplayObject.prototype), AssetDisplayObject);
/**
*
@@ -126,28 +123,56 @@ define([
this._attributes._naturalHeight = data.height;
this._mutatedAttributes.naturalWidth = true;
this._mutatedAttributes.naturalHeight = true;
- this.emit('load', this);
+ // We trigger the event asynchronously so as to ensure that any events
+ // bound after instantiation are still triggered:
+ this.emitAsync('load', this);
this.markUpdate();
break;
case 'error':
- this.emit('error', Error(data.error), this);
+ // We trigger the event asynchronously so as to ensure that any events
+ // bound after instantiation are still triggered:
+ this.emitAsync('error', Error(data.error), this);
break;
}
return this;
};
+ /**
+ * Get computed dimensions of the bitmap
+ *
+ * @param {String} key any of 'size', 'width', 'height', 'top', 'right', 'left', 'bottom'
+ * @returns {Object|Number} For the key 'size' it'll return an object with all
+ * properties, otherwise it'll return a single number for the key specified.
+ */
proto.getComputed = function(key) {
- var value, size = key === 'size' && {top: 0, right: 0, bottom: 0, left: 0};
+
+ var value,
+ size = key === 'size' && {top: 0, right: 0, bottom: 0, left: 0},
+ naturalWidth = this._attributes._naturalWidth,
+ naturalHeight = this._attributes._naturalHeight,
+ attrWidth = this.attr('width'),
+ attrHeight = this.attr('height'),
+ naturalRatio = naturalWidth / naturalHeight,
+
+ // If one dimensions is not specified, then we use the other dimension
+ // and the ratio to calculate its size:
+ width = attrWidth || (
+ attrHeight != null ? naturalRatio * attrHeight : naturalWidth
+ ) || 0,
+ height = attrHeight || (
+ attrWidth != null ? attrWidth / naturalRatio : naturalHeight
+ ) || 0;
+
if (key === 'width' || key === 'right') {
- value = this.attr('width') || 0;
+ value = width;
} else if (size) {
- size.right = size.width = this.attr('width') || 0;
+ size.right = size.width = width;
}
if (key === 'height' || key === 'bottom') {
- value = this.attr('height') || 0;
+ value = height;
} else if (size) {
- size.bottom = size.height = this.attr('height') || 0;
+ size.bottom = size.height = height;
}
if (key === 'top' || key === 'left') {
value = 0;
8 src/runner/font_family.js
View
@@ -45,10 +45,14 @@ define([
switch (type) {
case 'load':
- this.emit('load', this);
+ // We trigger the event asynchronously so as to ensure that any events
+ // bound after instantiation are still triggered:
+ this.emitAsync('load', this);
break;
case 'error':
- this.emit('error', Error(data.error), this);
+ // We trigger the event asynchronously so as to ensure that any events
+ // bound after instantiation are still triggered:
+ this.emitAsync('error', Error(data.error), this);
break;
}
1  src/runner/gradient.js
View
@@ -287,7 +287,6 @@ function(tools, color, Matrix) {
gradient.advancedRadial = function(stops, r, matrix, repeat, units, fx, fy) {
units = units || gradient.DEFAULT_UNITS;
- matrix = matrix || gradient.DEFAULT_RADIAL_MATRIX;
fx = fx || 0;
fy = fy || 0;
23 src/runner/movie.js
View
@@ -10,10 +10,11 @@
// a timeline-controlled container.
define([
'./display_object',
+ './asset_display_object',
'./display_list',
'./timeline',
'../tools'
-], function(DisplayObject, DisplayList, Timeline, tools) {
+], function(DisplayObject, AssetDisplayObject, DisplayList, Timeline, tools) {
'use strict';
/**
@@ -26,16 +27,30 @@ define([
* @mixes module:timeline.Timeline
* @mixes module:display_list.DisplayList
*/
- function Movie(root, url, onLoad, onError) {
+ function Movie(root, url, callback) {
DisplayObject.call(this);
+
+ if (callback) {
+ this.bindAssetCallback(callback);
+ }
+
this.root = root;
this._children = [];
+ var me = this;
if (url) {
- root.loadSubMovie(url, onLoad, onError, this);
+ root.loadSubMovie(url, function(err) {
+ // We trigger the event asynchronously so as to ensure that any events
+ // bound after instantiation are still triggered:
+ if (err) {
+ me.emitAsync('error', err, me);
+ } else {
+ me.emitAsync('load', me);
+ }
+ }, this);
}
}
- var proto = Movie.prototype = tools.mixin(Object.create(DisplayObject.prototype), Timeline, DisplayList);
+ var proto = Movie.prototype = tools.mixin(Object.create(DisplayObject.prototype), Timeline, DisplayList, AssetDisplayObject);
proto.loadSubMovie = function() {
return this.root.loadSubMovie.apply(this.root, arguments);
28 src/runner/stage.js
View
@@ -51,13 +51,11 @@ define([
*
* @constructor
* @param {Object} messageChannel used to communicate with the renderer
- * @param {Function} loadUrl A function that takes a bonsai movie url
- * (or path) and a callback that receives the source code of that movie.
*
* @mixes module:event_emitter.EventEmitter
* @mixes module:timeline.Timeline
*/
- function Stage(messageChannel, loadUrl) {
+ function Stage(messageChannel) {
var registry = this.registry = new Registry();
var assetLoader = this.assetLoader =
@@ -65,8 +63,6 @@ define([
.on('request', hitch(this, this.loadAsset, null));
this.env = new Environment(this, assetLoader);
-
- this.loadUrl = loadUrl;
this.stage = this.root = this;
this._canRender = true;
@@ -154,28 +150,6 @@ define([
}
},
- /**
- * Handles the 'run' command event.
- *
- * @private
- * @param data {object} The data property of the message event
- */
- handleRunCommand: function(data, stage, environment, doDone, doError) {
- if (data.code) {
- this.run(data.code, stage, environment);
- doDone && doDone.call(this);
- } else if (data.url) {
- this.loadUrl(
- this.assetBaseUrl.resolveUri(data.url),
- hitch(this, function(code) {
- this.run(code, stage, environment);
- doDone && doDone.call(this);
- }),
- doError ? hitch(this, doError) : function(){}
- );
- }
- },
-
loadAsset: function(baseUrl, id, request, type) {
// Make asset urls absolute here
baseUrl || (baseUrl = this.assetBaseUrl);
23 src/runner/video.js
View
@@ -7,9 +7,10 @@
*/
define([
'./display_object',
+ './asset_display_object',
'../asset/asset_request',
'../tools'
-], function(DisplayObject, AssetRequest, tools) {
+], function(DisplayObject, AssetDisplayObject, AssetRequest, tools) {
'use strict';
var data = tools.descriptorData;
@@ -39,18 +40,14 @@ define([
* @property {number} __supportedAttributes__.width The width of the video.
*
*/
- function Video(loader, aRequest, options) {
+ function Video(loader, aRequest, callback, options) {
options || (options = {});
this._loader = loader;
DisplayObject.call(this);
- if (options.onload) {
- this.on('load', options.onload);
- }
- if (options.onerror) {
- // TODO: choose diff evt name to avoid special 'error' treatment in eventemitter
- this.on('error', options.onerror);
+ if (callback) {
+ this.bindAssetCallback(callback);
}
this.type = 'Video';
@@ -69,7 +66,7 @@ define([
this.request(aRequest);
}
- var proto = Video.prototype = Object.create(DisplayObject.prototype);
+ var proto = Video.prototype = tools.mixin(Object.create(DisplayObject.prototype), AssetDisplayObject);
/**
*
@@ -131,10 +128,14 @@ define([
// TODO: videoWidth vs attr.width
// TODO: send onload some infos about the target
this.attr({width: data.width, height: data.height});
- this.emit('load');
+ // We trigger the event asynchronously so as to ensure that any events
+ // bound after instantiation are still triggered:
+ this.emitAsync('load', this);
break;
case 'error':
- this.emit('error', new Error(data.error));
+ // We trigger the event asynchronously so as to ensure that any events
+ // bound after instantiation are still triggered:
+ this.emitAsync('error', Error(data.error));
}
return this;
29 test/asset_display_object-spec.js
View
@@ -0,0 +1,29 @@
+require([
+ 'bonsai/runner/display_object',
+ 'bonsai/runner/asset_display_object',
+ 'bonsai/tools',
+ './runner.js'
+], function(DisplayObject, AssetDisplayObject, tools) {
+
+ describe('AssetDisplayObject', function() {
+
+ it('Provides bindAssetCallback method which binds a single callback to load and error events', function() {
+ var d = new DisplayObject;
+ tools.mixin(d, AssetDisplayObject);
+ var callback = jasmine.createSpy('callback');
+ d.bindAssetCallback(callback);
+ d.emit('load');
+ expect(callback).toHaveBeenCalledWith(null, d);
+ // Both events should have been unbound
+ expect(d._events[':load']).toEqual([]);
+ expect(d._events[':error']).toEqual([]);
+ d.bindAssetCallback(callback);
+ d.emit('error', 'foo123');
+ expect(callback).toHaveBeenCalledWith('foo123', d);
+ // Both events should have been unbound
+ expect(d._events[':load']).toEqual([]);
+ expect(d._events[':error']).toEqual([]);
+ });
+
+ });
+});
73 test/bitmap-spec.js
View
@@ -5,6 +5,64 @@ require([
'use strict';
describe('Bitmap', function() {
+ describe('Callbacks', function() {
+ it('Calls loader.request()', function() {
+ var loader = { request: jasmine.createSpy('request') };
+ new Bitmap(loader, 'img.png');
+ expect(loader.request).toHaveBeenCalled();
+ });
+ it('Calls load event [and callback] upon load success', function() {
+ var loader = {
+ request: function(bitmapInstance, request, type) {
+ bitmapInstance.notify('load', { width: 0, height: 0 });
+ }
+ };
+ var eventHandler = jasmine.createSpy('eventHandler');
+ var callbackCalled = false;
+ var callback = function(err) {
+ expect(err).toBe(null);
+ expect(this).toBe(bitmap);
+ callbackCalled = true;
+ };
+ var bitmap = new Bitmap(loader, 'img.jpg', callback).on('load', eventHandler);
+ // Events will be triggered async, even with a mock loader,
+ // This is forced by Bitmap, so as to make sure that post-instantiation events
+ // still get fired.
+ async(function(next) {
+ setTimeout(function() {
+ expect(eventHandler).toHaveBeenCalled();
+ expect(callbackCalled).toBe(true);
+ next();
+ }, 10);
+ });
+ });
+ it('Calls error event [and callback] upon load error', function() {
+ var loader = {
+ request: function(bitmapInstance, request, type) {
+ bitmapInstance.notify('error', { error: 'errorMessage123' });
+ }
+ };
+ var eventHandler = jasmine.createSpy('eventHandler');
+ var callbackCalled = false;
+ var callback = function(err) {
+ expect(err instanceof Error).toBe(true);
+ expect(err.message).toBe('errorMessage123');
+ expect(this).toBe(bitmap);
+ callbackCalled = true;
+ };
+ var bitmap = new Bitmap(loader, 'img.jpg', callback).on('error', eventHandler);
+ // Events will be triggered async, even with a mock loader,
+ // This is forced by Bitmap, so as to make sure that post-instantiation events
+ // still get fired.
+ async(function(next) {
+ setTimeout(function() {
+ expect(eventHandler).toHaveBeenCalled();
+ expect(callbackCalled).toBe(true);
+ next();
+ }, 10);
+ });
+ });
+ });
describe('#getComputed()', function() {
it('should return the bitmap width if invoked with "width"', function() {
var width = 123;
@@ -62,6 +120,21 @@ require([
expect(new Bitmap().getComputed('left')).toBe(0);
});
+ it('Should calculate width from width/height ratio if not specified and vice-versa', function() {
+ var b = new Bitmap();
+ b._attributes._naturalWidth = 400;
+ b._attributes._naturalHeight = 200;
+ expect(b.getComputed('width')).toBe(400);
+ expect(b.getComputed('height')).toBe(200);
+ b.attr('width', 500);
+ expect(b.getComputed('width')).toBe(500);
+ expect(b.getComputed('height')).toBe(250); // <- calculated from ratio
+ b.attr('width', null);
+ b.attr('height', 10);
+ expect(b.getComputed('width')).toBe(20); // <- calculated from ratio
+ expect(b.getComputed('height')).toBe(10);
+ });
+
it('should return an object with "top", "right", "bottom", "left", ' +
'"width" and "height" properties of 0 when invoked with "size"', function() {
var width = 123, height = 456;
32 test/integration/assets/integration.js
View
@@ -6,7 +6,11 @@ var code = '(' + function() {
redBoxFromPlugin().addTo(stage);
- stage.loadSubMovie('green.js', function(subMovie) {
+ new Movie('green.js', function(err, subMovie) {
+ if (err) {
+ console.log('Error: ' + err);
+ return;
+ }
subMovie.attr({
x: 100,
y: 100,
@@ -19,20 +23,20 @@ var code = '(' + function() {
repeat: Infinity
})
});
- new Bitmap('redpanda.jpg', {
-
- onload: function() {
- this.addTo(stage).attr({
- x: 50,
- y: 50,
- width: 100,
- height: 100,
- opacity: 0
- }).animate('.5s', {
- opacity: 1
- });
+ new Bitmap('redpanda.jpg', function(err) {
+ if (err) {
+ console.log('Error: ' + err);
+ return;
}
-
+ this.addTo(stage).attr({
+ x: 50,
+ y: 50,
+ width: 100,
+ height: 100,
+ opacity: 0
+ }).animate('.5s', {
+ opacity: 1
+ });
});
});
11 test/movie-spec.js
View
@@ -2,7 +2,7 @@ require([
'bonsai/runner/movie',
'./runner.js'
], function(Movie) {
-
+
describe('Movie', function(){
it('Sets its root to first argument [usually bound by environment.js]', function() {
@@ -16,12 +16,11 @@ require([
loadSubMovie: jasmine.createSpy('loadSubMovie')
};
var url = 'http://abc.def/pa/t/h.js';
- var onSuccess = function() {};
- var onError = function() {};
- var m = new Movie(root, url, onSuccess, onError);
- expect(root.loadSubMovie).toHaveBeenCalledWith(url, onSuccess, onError, m);
+ var callback = function() {};
+ var m = new Movie(root, url, callback);
+ expect(root.loadSubMovie).toHaveBeenCalled();
});
});
-
+
});
3  test/runner.html
View
@@ -19,7 +19,7 @@
<script type="text/javascript" src="../lib/requirejs/require.js"></script>
<script>
require([
-
+
// types
'./uri-spec',
@@ -32,6 +32,7 @@
'./tools-spec',
'./color-spec.js',
'./gradient-spec',
+ './asset_display_object-spec',
'./display_object-spec',
'./display_list-spec',
'./dom_element-spec',
Please sign in to comment.
Something went wrong with that request. Please try again.