2016-02-06 12:42:31 +00:00
|
|
|
// DOM fragment display driver
|
|
|
|
var Patch = require("./patch.js");
|
|
|
|
var DemandMatcher = require('./demand-matcher.js').DemandMatcher;
|
2016-05-08 15:33:39 +00:00
|
|
|
var Struct = require('./struct.js');
|
2016-02-07 19:39:09 +00:00
|
|
|
var Ack = require('./ack.js').Ack;
|
2016-02-06 20:06:59 +00:00
|
|
|
var Seal = require('./seal.js').Seal;
|
2016-02-07 19:39:09 +00:00
|
|
|
|
2016-04-07 07:42:54 +00:00
|
|
|
var Dataspace_ = require("./dataspace.js");
|
|
|
|
var Dataspace = Dataspace_.Dataspace;
|
|
|
|
var __ = Dataspace_.__;
|
|
|
|
var _$ = Dataspace_._$;
|
2016-02-06 12:42:31 +00:00
|
|
|
|
2016-05-10 04:40:53 +00:00
|
|
|
var DOM = Struct.makeConstructor('DOM', ['selector', 'fragmentClass', 'fragmentSpec']);
|
2016-05-08 15:11:16 +00:00
|
|
|
|
2016-02-06 12:42:31 +00:00
|
|
|
function spawnDOMDriver(domWrapFunction, jQueryWrapFunction) {
|
2016-05-08 15:11:16 +00:00
|
|
|
domWrapFunction = domWrapFunction || DOM;
|
2016-02-06 12:42:31 +00:00
|
|
|
var spec = domWrapFunction(_$('selector'), _$('fragmentClass'), _$('fragmentSpec'));
|
2016-04-07 07:42:54 +00:00
|
|
|
Dataspace.spawn(
|
2016-05-10 04:40:53 +00:00
|
|
|
new DemandMatcher([spec],
|
|
|
|
[Patch.advertise(spec)],
|
2016-02-06 12:42:31 +00:00
|
|
|
{
|
|
|
|
onDemandIncrease: function (c) {
|
2016-04-07 07:42:54 +00:00
|
|
|
Dataspace.spawn(new DOMFragment(c.selector,
|
2016-05-08 15:11:16 +00:00
|
|
|
c.fragmentClass,
|
|
|
|
c.fragmentSpec,
|
|
|
|
domWrapFunction,
|
|
|
|
jQueryWrapFunction));
|
2016-02-06 12:42:31 +00:00
|
|
|
}
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
function DOMFragment(selector, fragmentClass, fragmentSpec, domWrapFunction, jQueryWrapFunction) {
|
|
|
|
this.selector = selector;
|
|
|
|
this.fragmentClass = fragmentClass;
|
|
|
|
this.fragmentSpec = fragmentSpec;
|
|
|
|
this.domWrapFunction = domWrapFunction;
|
|
|
|
this.jQueryWrapFunction = jQueryWrapFunction;
|
2016-02-06 19:32:42 +00:00
|
|
|
this.demandExists = false;
|
2016-02-07 19:39:09 +00:00
|
|
|
this.subscriptionEstablished = new Ack();
|
2016-02-06 12:42:31 +00:00
|
|
|
this.nodes = this.buildNodes();
|
|
|
|
}
|
|
|
|
|
|
|
|
DOMFragment.prototype.boot = function () {
|
|
|
|
var self = this;
|
|
|
|
var specification = self.domWrapFunction(self.selector, self.fragmentClass, self.fragmentSpec);
|
|
|
|
|
2016-04-07 07:42:54 +00:00
|
|
|
Dataspace.spawn(new Dataspace(function () {
|
2016-02-06 12:42:31 +00:00
|
|
|
Syndicate.JQuery.spawnJQueryDriver(self.selector+" > ."+self.fragmentClass,
|
|
|
|
1,
|
|
|
|
self.jQueryWrapFunction);
|
2016-04-07 07:42:54 +00:00
|
|
|
Dataspace.spawn({
|
2016-02-06 19:32:42 +00:00
|
|
|
demandExists: false,
|
2016-02-07 19:39:09 +00:00
|
|
|
subscriptionEstablished: new Ack(1),
|
2016-02-06 12:42:31 +00:00
|
|
|
boot: function () {
|
2016-02-06 19:32:42 +00:00
|
|
|
this.subscriptionEstablished.arm();
|
2016-02-06 12:42:31 +00:00
|
|
|
return Patch.sub(Patch.advertise(specification), 1);
|
|
|
|
},
|
|
|
|
handleEvent: function (e) {
|
2016-02-06 19:32:42 +00:00
|
|
|
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) {
|
2016-04-07 07:42:54 +00:00
|
|
|
Dataspace.exitDataspace();
|
2016-02-06 12:42:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2016-02-06 19:32:42 +00:00
|
|
|
this.subscriptionEstablished.arm();
|
2016-02-06 12:42:31 +00:00
|
|
|
return Patch.sub(specification).andThen(Patch.pub(specification));
|
|
|
|
};
|
|
|
|
|
|
|
|
DOMFragment.prototype.handleEvent = function (e) {
|
2016-02-06 19:32:42 +00:00
|
|
|
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) {
|
2016-02-06 12:42:31 +00:00
|
|
|
for (var i = 0; i < this.nodes.length; i++) {
|
|
|
|
var n = this.nodes[i];
|
|
|
|
n.parentNode.removeChild(n);
|
|
|
|
}
|
2016-04-07 07:42:54 +00:00
|
|
|
Dataspace.exit();
|
2016-02-06 12:42:31 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
function isAttributes(x) {
|
|
|
|
return Array.isArray(x) && ((x.length === 0) || Array.isArray(x[0]));
|
|
|
|
}
|
|
|
|
|
2016-05-10 19:31:47 +00:00
|
|
|
DOMFragment.prototype.interpretSpec = function (spec, xmlns) {
|
2016-02-06 12:42:31 +00:00
|
|
|
// Fragment specs are roughly JSON-equivalents of SXML.
|
|
|
|
// spec ::== ["tag", [["attr", "value"], ...], spec, spec, ...]
|
|
|
|
// | ["tag", spec, spec, ...]
|
|
|
|
// | "cdata"
|
|
|
|
if (typeof(spec) === "string" || typeof(spec) === "number") {
|
|
|
|
return document.createTextNode(spec);
|
|
|
|
} else if ($.isArray(spec)) {
|
|
|
|
var tagName = spec[0];
|
|
|
|
var hasAttrs = isAttributes(spec[1]);
|
2016-05-10 19:31:47 +00:00
|
|
|
var attrs = hasAttrs ? spec[1] : [];
|
2016-02-06 12:42:31 +00:00
|
|
|
var kidIndex = hasAttrs ? 2 : 1;
|
|
|
|
|
2016-05-10 19:31:47 +00:00
|
|
|
var xmlnsAttr = attrs.find(function (e) { return e[0] === 'xmlns' });
|
|
|
|
if (xmlnsAttr) {
|
|
|
|
xmlns = xmlnsAttr[1];
|
|
|
|
}
|
|
|
|
|
2016-05-10 04:40:53 +00:00
|
|
|
// TODO: Wow! Such XSS! Many hacks! So vulnerability! Amaze!
|
2016-05-10 19:31:47 +00:00
|
|
|
var n = xmlns
|
|
|
|
? document.createElementNS(xmlns, tagName)
|
|
|
|
: document.createElement(tagName);
|
2016-02-06 12:42:31 +00:00
|
|
|
for (var i = 0; i < attrs.length; i++) {
|
2016-05-10 19:31:47 +00:00
|
|
|
if (attrs[i][0] !== 'xmlns') n.setAttribute(attrs[i][0], attrs[i][1]);
|
2016-02-06 12:42:31 +00:00
|
|
|
}
|
|
|
|
for (var i = kidIndex; i < spec.length; i++) {
|
2016-05-10 19:31:47 +00:00
|
|
|
n.appendChild(this.interpretSpec(spec[i], xmlns));
|
2016-02-06 12:42:31 +00:00
|
|
|
}
|
|
|
|
return n;
|
|
|
|
} else {
|
|
|
|
throw new Error("Ill-formed DOM specification");
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
DOMFragment.prototype.buildNodes = function () {
|
|
|
|
var self = this;
|
|
|
|
var nodes = [];
|
|
|
|
$(self.selector).each(function (index, domNode) {
|
2016-05-10 04:40:53 +00:00
|
|
|
if (!(self.fragmentSpec instanceof Syndicate.Seal)) {
|
|
|
|
throw new Error("DOM fragmentSpec not contained in a Syndicate.Seal: " + JSON.stringify(self.fragmentSpec));
|
|
|
|
}
|
2016-05-10 19:31:47 +00:00
|
|
|
var n = self.interpretSpec(self.fragmentSpec.sealContents, '');
|
2016-03-22 16:11:47 +00:00
|
|
|
if ('classList' in n) {
|
|
|
|
n.classList.add(self.fragmentClass);
|
|
|
|
}
|
2016-02-06 12:42:31 +00:00
|
|
|
domNode.appendChild(n);
|
|
|
|
nodes.push(n);
|
|
|
|
});
|
|
|
|
return nodes;
|
|
|
|
};
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
module.exports.spawnDOMDriver = spawnDOMDriver;
|
2016-05-08 15:11:16 +00:00
|
|
|
module.exports.DOM = DOM;
|