Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Commit

Permalink
feat: expose hooks for enqueuing and dequing tasks
Browse files Browse the repository at this point in the history
  • Loading branch information
btford committed May 7, 2014
1 parent 21b47ae commit ba72f34
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 34 deletions.
36 changes: 25 additions & 11 deletions counting-zone.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,36 @@
/*
* See example/counting.html
*/

Zone.countingZone = {
'-onZoneCreated': function () {
Zone.countingZone.counter += 1;

// setTimeout
enqueueTask: function () {
this.data.count += 1;
},
'+afterTask': function () {
Zone.countingZone.counter -= 1;
if (Zone.countingZone.counter <= 0) {
Zone.countingZone.counter = 0;
this.onFlush();
}

// fires when...
// - clearTimeout
// - setTimeout finishes
dequeueTask: function () {
this.data.count -= 1;
},
'-run': function () {
Zone.countingZone.counter = 0;

afterTask: function () {
if (this.data.count === 0 && !this.data.flushed) {
this.data.flushed = true;
this.run(this.onFlush);
}
},

counter: function () {
return Zone.countingZone.counter;
return this.data.count;
},

data: {
count: 0,
flushed: false
},

onFlush: function () {}
};
83 changes: 70 additions & 13 deletions test/counting-zone.spec.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,90 @@
'use strict';

describe('Zone.countingZone', function () {
var flushSpy = jasmine.createSpy('flush'),
countingZone = zone.fork(Zone.countingZone).fork({
onFlush: flushSpy
});
var flushSpy, countingZone;

beforeEach(function () {
jasmine.Clock.useMock();
flushSpy.reset();
flushSpy = jasmine.createSpy('flush');
countingZone = zone.fork(Zone.longStackTraceZone).
fork(Zone.countingZone).
fork({
onFlush: flushSpy
});
});

it('should flush at the end of a run', function () {
countingZone.run(function () {});
expect(flushSpy).toHaveBeenCalled();
countingZone.run(function () {
expect(countingZone.counter()).toBe(0);
});
expect(countingZone.counter()).toBe(0);
expect(flushSpy.calls.length).toBe(1);
});

it('should work with setTimeout', function () {
var latch;

runs(function () {
countingZone.run(function () {
setTimeout(function () {
latch = true;
}, 0);
expect(countingZone.counter()).toBe(1);
});
});

waitsFor(function () {
return latch;
});

runs(function () {
expect(countingZone.counter()).toBe(0);
})
});

it('should work', function () {
it('should work with clearTimeout', function () {
var latch = false;
countingZone.run(function () {
var id = setTimeout(function () {
latch = true;
}, 0);
expect(countingZone.counter()).toBe(1);
clearTimeout(id);
expect(countingZone.counter()).toBe(0);
});
});


setTimeout(function () {}, 0);
it('should work with addEventListener', function () {
var elt = document.createElement('button');
var clicked = false;

runs(function () {
countingZone.run(main);
});

function main () {
expect(countingZone.counter()).toBe(0);
elt.addEventListener('click', onClick);
expect(countingZone.counter()).toBe(1);

//jasmine.Clock.tick(0);
elt.click();
function onClick () {
expect(countingZone.counter()).toBe(1);
elt.removeEventListener('click', onClick);
expect(countingZone.counter()).toBe(0);
clicked = true;
}

expect(countingZone.counter()).toBe(0);
}

waitsFor(function () {
return clicked;
}, 10, 'the thing');

//expect(countingZone.counter()).toBe(0);
runs(function () {
expect(flushSpy.calls.length).toBe(1);
});

//jasmine.Clock.tick(0);
});
});
100 changes: 90 additions & 10 deletions zone.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,21 @@ Zone.prototype = {
},

bind: function (fn) {
this.enqueueTask(fn);
var zone = this.fork();
return function zoneBoundFn() {
return zone.run(fn, this, arguments);
};
},

bindOnce: function (fn) {
return this.bind(function () {
var result = fn.apply(this, arguments);
zone.dequeueTask(fn);
return result;
});
},

run: function run (fn, applyTo, applyWith) {
applyWith = applyWith || [];

Expand All @@ -90,18 +99,72 @@ Zone.prototype = {

beforeTask: function () {},
onZoneCreated: function () {},
afterTask: function () {}
afterTask: function () {},
enqueueTask: function () {},
dequeueTask: function () {}
};

Zone.patchFn = function (obj, fnNames) {

Zone.patchSetClearFn = function (obj, fnNames) {
fnNames.map(function (name) {
return name[0].toUpperCase() + name.substr(1);
}).
forEach(function (name) {
var setName = 'set' + name;
var clearName = 'clear' + name;
var delegate = obj[setName];

if (delegate) {
var ids = {};

zone[setName] = function (fn) {
var id;
arguments[0] = function () {
delete ids[id];
return fn.apply(this, arguments);
};
var args = Zone.bindArgumentsOnce(arguments);
id = delegate.apply(obj, args);
ids[id] = true;
return id;
};

obj[setName] = function () {
return zone[setName].apply(this, arguments);
};

var clearDelegate = obj[clearName];

zone[clearName] = function (id) {
if (ids[id]) {
delete ids[id];
zone.dequeueTask();
}
return clearDelegate.apply(this, arguments);
};

obj[clearName] = function () {
return zone[clearName].apply(this, arguments);
};
}
});
};


Zone.patchSetFn = function (obj, fnNames) {
fnNames.forEach(function (name) {
var delegate = obj[name];

if (delegate) {
zone[name] = function () {
return delegate.apply(obj, Zone.bindArguments(arguments));
zone[name] = function (fn) {
arguments[0] = function () {
return fn.apply(this, arguments);
};
var args = Zone.bindArgumentsOnce(arguments);
return delegate.apply(obj, args);
};

obj[name] = function marker () {
obj[name] = function () {
return zone[name].apply(this, arguments);
};
}
Expand All @@ -128,6 +191,16 @@ Zone.bindArguments = function (args) {
return args;
};


Zone.bindArgumentsOnce = function (args) {
for (var i = args.length - 1; i >= 0; i--) {
if (typeof args[i] === 'function') {
args[i] = zone.bindOnce(args[i]);
}
}
return args;
};

Zone.patchableFn = function (obj, fnNames) {
fnNames.forEach(function (name) {
var delegate = obj[name];
Expand Down Expand Up @@ -206,17 +279,24 @@ Zone.patchEventTargetMethods = function (obj) {
var removeDelegate = obj.removeEventListener;
obj.removeEventListener = function (eventName, fn) {
arguments[1] = arguments[1]._bound || arguments[1];
return removeDelegate.apply(this, arguments);
var result = removeDelegate.apply(this, arguments);
zone.dequeueTask(fn);
return result;
};
};

Zone.patch = function patch () {
Zone.patchFn(window, [
'setTimeout',
'setInterval',
Zone.patchSetClearFn(window, [
'timeout',
'interval',
'immediate'
]);

Zone.patchSetFn(window, [
'requestAnimationFrame',
'webkitRequestAnimationFrame'
]);

Zone.patchableFn(window, ['alert', 'prompt']);

// patched properties depend on addEventListener, so this needs to come first
Expand Down Expand Up @@ -316,7 +396,7 @@ Zone.patchViaCapturingAllTheEvents = function () {
});
};

// TODO: wrap some native API
// wrap some native API on `window`
Zone.patchClass = function (className) {
var OriginalClass = window[className];
if (!OriginalClass) {
Expand Down

0 comments on commit ba72f34

Please sign in to comment.