DOM driver.

This commit is contained in:
Tony Garnock-Jones 2016-02-06 07:42:31 -05:00
parent a0670ec3a3
commit 3489b5fab7
4 changed files with 191 additions and 1 deletions

View File

@ -0,0 +1,16 @@
<!doctype html>
<html>
<head>
<title>Syndicate: DOM Example</title>
<meta charset="utf-8">
<script src="../../third-party/jquery-2.2.0.min.js"></script>
<script src="../../dist/syndicate.js"></script>
<script src="index.js"></script>
</head>
<body>
<h1>DOM example</h1>
<div id="counter-holder"></div>
<div id="clicker-holder"></div>
<pre id="spy-holder"></pre>
</body>
</html>

54
js/examples/dom/index.js Normal file
View File

@ -0,0 +1,54 @@
var G;
$(document).ready(function () {
var Network = Syndicate.Network;
var sub = Syndicate.sub;
var assert = Syndicate.assert;
var retract = Syndicate.retract;
var __ = Syndicate.__;
var _$ = Syndicate._$;
G = new Syndicate.Ground(function () {
console.log('starting ground boot');
Syndicate.DOM.spawnDOMDriver();
Network.spawn({
boot: function () {
return assert(["DOM", "#clicker-holder", "clicker",
["button", ["span", [["style", "font-style: italic"]], "Click me!"]]])
.andThen(sub(["jQuery", "button.clicker", "click", __]));
},
handleEvent: function (e) {
if (e.type === "message" && e.message[0] === "jQuery") {
Network.send("bump_count");
}
}
});
Network.spawn({
counter: 0,
boot: function () {
this.updateState();
return sub("bump_count");
},
updateState: function () {
Network.stateChange(retract(["DOM", __, __, __])
.andThen(assert(["DOM", "#counter-holder", "counter",
["div",
["p", "The current count is: ", this.counter]]])));
},
handleEvent: function (e) {
if (e.type === "message" && e.message === "bump_count") {
this.counter++;
this.updateState();
}
}
});
});
G.network.onStateChange = function (mux, patch) {
$("#spy-holder").text(Syndicate.prettyTrie(mux.routingTable));
};
G.startStepping();
});

120
js/src/dom-driver.js Normal file
View File

@ -0,0 +1,120 @@
// DOM fragment display driver
var Syndicate = require("./syndicate.js");
var Patch = require("./patch.js");
var DemandMatcher = require('./demand-matcher.js').DemandMatcher;
var Network = Syndicate.Network;
var __ = Syndicate.__;
var _$ = Syndicate._$;
function spawnDOMDriver(domWrapFunction, jQueryWrapFunction) {
domWrapFunction = domWrapFunction || defaultWrapFunction;
var spec = domWrapFunction(_$('selector'), _$('fragmentClass'), _$('fragmentSpec'));
Network.spawn(
new DemandMatcher(spec,
Patch.advertise(spec),
{
onDemandIncrease: function (c) {
Network.spawn(new DOMFragment(c.selector,
c.fragmentClass,
c.fragmentSpec,
domWrapFunction,
jQueryWrapFunction));
}
}));
}
function defaultWrapFunction(selector, fragmentClass, fragmentSpec) {
return ["DOM", selector, fragmentClass, fragmentSpec];
}
function DOMFragment(selector, fragmentClass, fragmentSpec, domWrapFunction, jQueryWrapFunction) {
this.selector = selector;
this.fragmentClass = fragmentClass;
this.fragmentSpec = fragmentSpec;
this.domWrapFunction = domWrapFunction;
this.jQueryWrapFunction = jQueryWrapFunction;
this.nodes = this.buildNodes();
}
DOMFragment.prototype.boot = function () {
var self = this;
var specification = self.domWrapFunction(self.selector, self.fragmentClass, self.fragmentSpec);
Network.spawn(new Network(function () {
Syndicate.JQuery.spawnJQueryDriver(self.selector+" > ."+self.fragmentClass,
1,
self.jQueryWrapFunction);
Network.spawn({
boot: function () {
return Patch.sub(Patch.advertise(specification), 1);
},
handleEvent: function (e) {
if (e.type === "stateChange" && e.patch.hasRemoved()) {
Network.exitNetwork();
}
}
});
}));
return Patch.sub(specification).andThen(Patch.pub(specification));
};
DOMFragment.prototype.handleEvent = function (e) {
if (e.type === "stateChange" && e.patch.hasRemoved()) {
for (var i = 0; i < this.nodes.length; i++) {
var n = this.nodes[i];
n.parentNode.removeChild(n);
}
Network.exit();
}
};
///////////////////////////////////////////////////////////////////////////
function isAttributes(x) {
return Array.isArray(x) && ((x.length === 0) || Array.isArray(x[0]));
}
DOMFragment.prototype.interpretSpec = function (spec) {
// 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]);
var attrs = hasAttrs ? spec[1] : {};
var kidIndex = hasAttrs ? 2 : 1;
// Wow! Such XSS! Many hacks! So vulnerability! Amaze!
var n = document.createElement(tagName);
for (var i = 0; i < attrs.length; i++) {
n.setAttribute(attrs[i][0], attrs[i][1]);
}
for (var i = kidIndex; i < spec.length; i++) {
n.appendChild(this.interpretSpec(spec[i]));
}
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) {
var n = self.interpretSpec(self.fragmentSpec.toJS());
n.classList.add(self.fragmentClass);
domNode.appendChild(n);
nodes.push(n);
});
return nodes;
};
///////////////////////////////////////////////////////////////////////////
module.exports.spawnDOMDriver = spawnDOMDriver;
module.exports.defaultWrapFunction = defaultWrapFunction;

View File

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