From c6202a1a16b19f5404d9078afa1bc3f7119deccd Mon Sep 17 00:00:00 2001 From: Brian Ford Date: Fri, 28 Mar 2014 15:27:37 -0700 Subject: [PATCH] feat: add decorator syntax --- README.md | 43 ++++++++++++++++++++++++++- counting-zone.js | 21 ++++++++++++++ example/counting.html | 67 +++++++++++++------------------------------ test/zone.spec.js | 8 +----- zone.js | 35 +++++++++++++++++++++- 5 files changed, 118 insertions(+), 56 deletions(-) create mode 100644 counting-zone.js diff --git a/README.md b/README.md index 49cb93106..27509d310 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,13 @@ someZone.fork({ ### Augmenting A Zone's Hook +When you fork a zone, you'll often want to control how the parent zone's +hook gets called. + +Prefixing a hook with `$` means that the hook will be passed the +parent zone's hook, and the hook will be expected to return the function to +be invoked rather than be the function itself. + ```javascript var someZone = zone.fork({ onZoneLeave: function () { @@ -121,8 +128,36 @@ var someZone = zone.fork({ }); someZone.fork({ + $onZoneLeave: function (parentOnLeave) { + // return the hook + return function onZoneLeave() { + parentOnLeave(); + console.log('cya l8r'); + }; + } +}).run(function () { + // do stuff +}); + +// logs: goodbye +// cya l8r +``` + +#### `+` and `-` Sugar +Most of the time, you'll want to run a hook before or after the parent's implementation. +You can prefix a hook with `-` for running before, and `+` for running after. + +The above can be written like this: + +```javascript +var someZone = zone.fork({ onZoneLeave: function () { - this.parent.onZoneLeave(); + console.log('goodbye'); + } +}); + +someZone.fork({ + '+onZoneLeave': function (parentOnLeave) { console.log('cya l8r'); } }).run(function () { @@ -133,6 +168,8 @@ someZone.fork({ // cya l8r ``` +This frees you from writing boilerplate to compose a new hook. + ## API Zone.js exports a single object: `window.zone`. @@ -166,6 +203,10 @@ myZone.run(function () { Below describes the behavior of each of these hooks. +### `zone.onZoneCreated` + +Runs when a zone is forked. + ### `zone.onZoneEnter` Before a function invoked with `zone.run`, this hook runs. diff --git a/counting-zone.js b/counting-zone.js new file mode 100644 index 000000000..906ecc863 --- /dev/null +++ b/counting-zone.js @@ -0,0 +1,21 @@ +/* + * See example/counting.html + */ +zone.countingZone = { + '-onZoneCreated': function () { + zone.countingZone.counter += 1; + }, + '+onZoneLeave': function () { + zone.countingZone.counter -= 1; + if (zone.countingZone.counter === 0) { + this.onFlush(); + } + }, + reset: function () { + zone.countingZone.counter = 0; + }, + counter: function () { + return zone.countingZone.counter; + }, + onFlush: function () {} +}; diff --git a/example/counting.html b/example/counting.html index 6c4b5d48d..d79d96c5d 100644 --- a/example/counting.html +++ b/example/counting.html @@ -5,7 +5,7 @@ Counting Pending Tasks - + @@ -26,51 +26,24 @@

Counting Pending Tasks

/* * Zone that counts pending async tasks */ - var countingZone = (function () { - var count = 0, - start; - return { - _wrap: function (fn) { - return function () { - var ret = fn.apply(this, arguments); - count -= 1; - zone._print(); - return ret; - }; - }, - - _print: function () { - output.innerHTML = 'pending task count: ' + count + - (count === 0 ? (' DONE! ' + (Date.now() - start)/1000 + 's') : ''); - }, - - reset: function () { - start = 0; - }, - - run: function () { - if (!start) { - start = Date.now(); - } - return zone.run.apply(this, arguments); - }, - - - onError: function (e) { - console.log(e.stack); - }, - - setTimeout: function () { - count += 1; - zone._print(); - arguments[0] = zone._wrap(arguments[0]); - return zone._setTimeout.apply(this, arguments); - }, - - _setTimeout: zone.setTimeout - }; - }()); - + var myCountingZone = zone.fork(zone.countingZone).fork({ + '+onZoneCreated': function () { + zone.countingZone.start || (zone.countingZone.start = Date.now()); + this.print(); + }, + '-onZoneLeave': function (delegate) { + this.print(); + }, + '+reset': function (delegate) { + zone.countingZone.start = 0; + }, + print: function () { + counter = this.counter(); + output.innerHTML = counter ? + 'pending task count: ' + counter : + ' DONE! ' + (Date.now() - zone.countingZone.start)/1000 + 's'; + } + }); /* * We want to profile just the actions that are a result of this button, so with @@ -78,7 +51,7 @@

Counting Pending Tasks

*/ b1.addEventListener('click', function () { - zone.fork(countingZone).run(main); + myCountingZone.run(main); }); diff --git a/test/zone.spec.js b/test/zone.spec.js index 7fd4aedfe..e1fc6acc0 100644 --- a/test/zone.spec.js +++ b/test/zone.spec.js @@ -183,13 +183,7 @@ describe('Zone.patch', function () { expect(leaveSpy).toHaveBeenCalled(); }); - it('should throw if onError is not defined', function () { - expect(function () { - zone.run(throwError); - }).toThrow(); - }); - - it('should fire onZoneCreeated when a zone is forked', function () { + it('should fire onZoneCreated when a zone is forked', function () { var createdSpy = jasmine.createSpy(); var counter = 0; var myZone = zone.fork({ diff --git a/zone.js b/zone.js index ad26b225b..211091d22 100644 --- a/zone.js +++ b/zone.js @@ -7,7 +7,40 @@ function Zone(parentZone, data) { zone.parent = parentZone; Object.keys(data || {}).forEach(function(property) { - zone[property] = data[property]; + + var _property = property.substr(1); + + // augment the new zone with a hook decorates the parent's hook + if (property[0] === '$') { + zone[_property] = data[property](parentZone[_property] || function () {}); + + // augment the new zone with a hook that runs after the parent's hook + } else if (property[0] === '+') { + if (parentZone[_property]) { + zone[_property] = function () { + var result = parentZone[_property].apply(this, arguments); + data[property].apply(this, arguments); + return result; + }; + } else { + zone[_property] = data[property]; + } + + // augment the new zone with a hook that runs before the parent's hook + } else if (property[0] === '-') { + if (parentZone[_property]) { + zone[_property] = function () { + data[property].apply(this, arguments); + return parentZone[_property].apply(this, arguments); + }; + } else { + zone[_property] = data[property]; + } + + // set the new zone's hook (replacing the parent zone's) + } else { + zone[property] = data[property]; + } }); return zone;