Introduce Syndicate.Ack() to reliably detect lack of demand in DOMFragment.

This commit is contained in:
Tony Garnock-Jones 2016-02-06 14:32:42 -05:00
parent 3489b5fab7
commit afa657096a
5 changed files with 93 additions and 2 deletions

41
js/src/ack.js Normal file
View File

@ -0,0 +1,41 @@
// Utility protocol for measuring when a stateChange takes effect.
var RandomID = require('./randomid.js');
var Syndicate = require('./syndicate.js');
var Route = require('./route.js');
var Patch = require('./patch.js');
var $Ack = new Route.$Special('ack');
function Ack(metaLevel, id) {
this.metaLevel = metaLevel || 0;
this.id = id || RandomID.randomId(16);
this.done = false;
}
Ack.prototype.arm = function () {
Syndicate.Network.stateChange(Patch.sub([$Ack, this.id], this.metaLevel));
Syndicate.Network.send([$Ack, this.id], this.metaLevel);
};
Ack.prototype.disarm = function () {
Syndicate.Network.stateChange(Patch.unsub([$Ack, this.id], this.metaLevel));
};
Ack.prototype.check = function (e) {
if (!this.done) {
if (e.type === 'message') {
var m = Patch.stripAtMeta(e.message, this.metaLevel);
if (m && m[0] === $Ack && m[1] === this.id) {
this.disarm();
this.done = true;
}
}
}
return this.done;
};
///////////////////////////////////////////////////////////////////////////
module.exports.$Ack = $Ack;
module.exports.Ack = Ack;

View File

@ -33,6 +33,8 @@ function DOMFragment(selector, fragmentClass, fragmentSpec, domWrapFunction, jQu
this.fragmentSpec = fragmentSpec;
this.domWrapFunction = domWrapFunction;
this.jQueryWrapFunction = jQueryWrapFunction;
this.demandExists = false;
this.subscriptionEstablished = new Syndicate.Ack();
this.nodes = this.buildNodes();
}
@ -45,22 +47,36 @@ DOMFragment.prototype.boot = function () {
1,
self.jQueryWrapFunction);
Network.spawn({
demandExists: false,
subscriptionEstablished: new Syndicate.Ack(1),
boot: function () {
this.subscriptionEstablished.arm();
return Patch.sub(Patch.advertise(specification), 1);
},
handleEvent: function (e) {
if (e.type === "stateChange" && e.patch.hasRemoved()) {
this.subscriptionEstablished.check(e);
if (e.type === "stateChange") {
if (e.patch.hasAdded()) this.demandExists = true;
if (e.patch.hasRemoved()) this.demandExists = false;
}
if (this.subscriptionEstablished.done && !this.demandExists) {
Network.exitNetwork();
}
}
});
}));
this.subscriptionEstablished.arm();
return Patch.sub(specification).andThen(Patch.pub(specification));
};
DOMFragment.prototype.handleEvent = function (e) {
if (e.type === "stateChange" && e.patch.hasRemoved()) {
this.subscriptionEstablished.check(e);
if (e.type === "stateChange") {
if (e.patch.hasAdded()) this.demandExists = true;
if (e.patch.hasRemoved()) this.demandExists = false;
}
if (this.subscriptionEstablished.done && !this.demandExists) {
for (var i = 0; i < this.nodes.length; i++) {
var n = this.nodes[i];
n.parentNode.removeChild(n);

View File

@ -19,7 +19,9 @@ copyKeys(['__', '_$', '$Capture', '$Special',
module.exports.DemandMatcher = require('./demand-matcher.js').DemandMatcher;
module.exports.Seal = require('./seal.js').Seal;
module.exports.Ack = require('./ack.js').Ack;
module.exports.RandomID = require('./randomid.js');
module.exports.DOM = require("./dom-driver.js");
module.exports.JQuery = require("./jquery-driver.js");
// module.exports.RoutingTableWidget = require("./routing-table-widget.js");

View File

@ -33,6 +33,17 @@ function prependAtMeta(p, level) {
return p;
}
function stripAtMeta(p, level) {
while (level--) {
if (p.length === 2 && p[0] === $AtMeta) {
p = p[1];
} else {
return null;
}
}
return p;
}
function observeAtMeta(p, level) {
if (level === 0) {
return Route.compilePattern(true, observe(p));
@ -240,6 +251,7 @@ module.exports.isAtMeta = isAtMeta;
module.exports.isAdvertise = isAdvertise;
module.exports.prependAtMeta = prependAtMeta;
module.exports.stripAtMeta = stripAtMeta;
module.exports.observeAtMeta = observeAtMeta;
module.exports.assert = assert;
module.exports.retract = retract;

20
js/src/randomid.js Normal file
View File

@ -0,0 +1,20 @@
var randomId;
if ((typeof window !== 'undefined') &&
(typeof window.crypto !== 'undefined') &&
(typeof window.crypto.getRandomValues !== 'undefined')) {
randomId = function (byteCount) {
var buf = new Uint8Array(byteCount);
window.crypto.getRandomValues(buf);
return btoa(String.fromCharCode.apply(null, buf)).replace(/=/g,'');
};
} else if ((typeof crypto !== 'undefined') &&
(typeof crypto.randomBytes !== 'undefined')) {
randomId = function (byteCount) {
return crypto.randomBytes(byteCount).base64Slice().replace(/=/g,'');
};
} else {
console.warn('No suitable implementation for RandomID.randomId available.');
}
module.exports.randomId = randomId;