A better UI library. Replaces DOM and jQuery support.
This commit is contained in:
parent
23f269fba6
commit
0208ae7a7d
|
@ -1,19 +1,14 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
var DOM = Syndicate.DOM.DOM;
|
new Syndicate.Ground(function () {
|
||||||
var jQueryEvent = Syndicate.JQuery.jQueryEvent;
|
Syndicate.UI.spawnUIDriver();
|
||||||
|
|
||||||
$(document).ready(function() {
|
|
||||||
new Syndicate.Ground(function () {
|
|
||||||
Syndicate.DOM.spawnDOMDriver();
|
|
||||||
Syndicate.JQuery.spawnJQueryDriver();
|
|
||||||
|
|
||||||
Syndicate.Actor.spawnActor(new Object(), function() {
|
Syndicate.Actor.spawnActor(new Object(), function() {
|
||||||
this.counter = 0;
|
var counter = 0;
|
||||||
|
var ui = new Syndicate.UI.Anchor();
|
||||||
Syndicate.Actor.createFacet()
|
Syndicate.Actor.createFacet()
|
||||||
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(DOM('#button-label','',''+this.counter), 0); }))
|
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(ui.html('#button-label',''+counter), 0); }))
|
||||||
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(jQueryEvent('#counter','click',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: jQueryEvent('#counter','click',_), metalevel: 0 }; }), (function() {
|
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(Syndicate.UI.globalEvent('#counter','click',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: Syndicate.UI.globalEvent('#counter','click',_), metalevel: 0 }; }), (function() {
|
||||||
this.counter++;
|
counter++;
|
||||||
})).completeBuild();
|
})).completeBuild();
|
||||||
});
|
});
|
||||||
}).startStepping();
|
}).startStepping();
|
||||||
});
|
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
<head>
|
<head>
|
||||||
<title>Syndicate: Button Example</title>
|
<title>Syndicate: Button Example</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<script src="../../third-party/jquery-2.2.0.min.js"></script>
|
|
||||||
<script src="../../dist/syndicatecompiler.js"></script>
|
<script src="../../dist/syndicatecompiler.js"></script>
|
||||||
<script src="../../dist/syndicate.js"></script>
|
<script src="../../dist/syndicate.js"></script>
|
||||||
<script type="text/syndicate-js" src="index.js"></script>
|
<script type="text/syndicate-js" src="index.js"></script>
|
||||||
|
|
|
@ -1,19 +1,14 @@
|
||||||
var DOM = Syndicate.DOM.DOM;
|
ground dataspace {
|
||||||
var jQueryEvent = Syndicate.JQuery.jQueryEvent;
|
Syndicate.UI.spawnUIDriver();
|
||||||
|
|
||||||
$(document).ready(function() {
|
|
||||||
ground dataspace {
|
|
||||||
Syndicate.DOM.spawnDOMDriver();
|
|
||||||
Syndicate.JQuery.spawnJQueryDriver();
|
|
||||||
|
|
||||||
actor {
|
actor {
|
||||||
this.counter = 0;
|
var counter = 0;
|
||||||
|
var ui = new Syndicate.UI.Anchor();
|
||||||
react {
|
react {
|
||||||
assert DOM('#button-label', '', '' + this.counter);
|
assert ui.html('#button-label', '' + counter);
|
||||||
on message jQueryEvent('#counter', 'click', _) {
|
on message Syndicate.UI.globalEvent('#counter', 'click', _) {
|
||||||
this.counter++;
|
counter++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
assertion type present(name, status);
|
assertion type present(name, status);
|
||||||
assertion type says(who, message);
|
assertion type says(who, message);
|
||||||
|
|
||||||
var DOM = Syndicate.DOM.DOM;
|
|
||||||
var jQueryEvent = Syndicate.JQuery.jQueryEvent;
|
|
||||||
|
|
||||||
var brokerConnected = Syndicate.Broker.brokerConnected;
|
var brokerConnected = Syndicate.Broker.brokerConnected;
|
||||||
var brokerConnection = Syndicate.Broker.brokerConnection;
|
var brokerConnection = Syndicate.Broker.brokerConnection;
|
||||||
var toBroker = Syndicate.Broker.toBroker;
|
var toBroker = Syndicate.Broker.toBroker;
|
||||||
|
@ -19,14 +16,15 @@ function spawnChatApp() {
|
||||||
if (!($("#nym").val())) { $("#nym").val("nym" + Math.floor(Math.random() * 65536)); }
|
if (!($("#nym").val())) { $("#nym").val("nym" + Math.floor(Math.random() * 65536)); }
|
||||||
|
|
||||||
actor {
|
actor {
|
||||||
|
var ui = new Syndicate.UI.Anchor();
|
||||||
react {
|
react {
|
||||||
on asserted jQueryInput('#nym', $v) { this.nym = v; }
|
on asserted inputValue('#nym', $v) { this.nym = v; }
|
||||||
on asserted jQueryInput('#status', $v) { this.status = v; }
|
on asserted inputValue('#status', $v) { this.status = v; }
|
||||||
|
|
||||||
on asserted brokerConnected($url) { outputState('connected to ' + url); }
|
on asserted brokerConnected($url) { outputState('connected to ' + url); }
|
||||||
on retracted brokerConnected($url) { outputState('disconnected from ' + url); }
|
on retracted brokerConnected($url) { outputState('disconnected from ' + url); }
|
||||||
|
|
||||||
during jQueryInput('#wsurl', $url) {
|
during inputValue('#wsurl', $url) {
|
||||||
assert brokerConnection(url);
|
assert brokerConnection(url);
|
||||||
|
|
||||||
on message Syndicate.WakeDetector.wakeEvent() {
|
on message Syndicate.WakeDetector.wakeEvent() {
|
||||||
|
@ -35,11 +33,12 @@ function spawnChatApp() {
|
||||||
|
|
||||||
assert toBroker(url, present(this.nym, this.status));
|
assert toBroker(url, present(this.nym, this.status));
|
||||||
during fromBroker(url, present($who, $status)) {
|
during fromBroker(url, present($who, $status)) {
|
||||||
assert DOM('#nymlist', 'present-nym',
|
assert ui.context(who)
|
||||||
|
.html('#nymlist',
|
||||||
Mustache.render($('#nym_template').html(), { who: who, status: status }));
|
Mustache.render($('#nym_template').html(), { who: who, status: status }));
|
||||||
}
|
}
|
||||||
|
|
||||||
on message jQueryEvent('#send_chat', 'click', _) {
|
on message Syndicate.UI.globalEvent('#send_chat', 'click', _) {
|
||||||
var inp = $("#chat_input");
|
var inp = $("#chat_input");
|
||||||
var utterance = inp.val();
|
var utterance = inp.val();
|
||||||
inp.val("");
|
inp.val("");
|
||||||
|
@ -79,21 +78,21 @@ function outputUtterance(who, what) {
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
// Input control value monitoring
|
// Input control value monitoring
|
||||||
|
|
||||||
assertion type jQueryInput(selector, value);
|
assertion type inputValue(selector, value);
|
||||||
|
|
||||||
function spawnInputChangeMonitor() {
|
function spawnInputChangeMonitor() {
|
||||||
actor {
|
actor {
|
||||||
react {
|
react {
|
||||||
on asserted Syndicate.observe(jQueryInput($selector, _)) {
|
on asserted Syndicate.observe(inputValue($selector, _)) {
|
||||||
actor {
|
actor {
|
||||||
this.value = $(selector).val();
|
this.value = $(selector).val();
|
||||||
react {
|
react {
|
||||||
assert jQueryInput(selector, this.value);
|
assert inputValue(selector, this.value);
|
||||||
on message jQueryEvent(selector, 'change', $e) {
|
on message Syndicate.UI.globalEvent(selector, 'change', $e) {
|
||||||
this.value = e.target.value;
|
this.value = e.target.value;
|
||||||
}
|
}
|
||||||
} until {
|
} until {
|
||||||
case retracted Syndicate.observe(jQueryInput(selector, _));
|
case retracted Syndicate.observe(inputValue(selector, _));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,8 +105,7 @@ function spawnInputChangeMonitor() {
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
ground dataspace G {
|
ground dataspace G {
|
||||||
Syndicate.JQuery.spawnJQueryDriver();
|
Syndicate.UI.spawnUIDriver();
|
||||||
Syndicate.DOM.spawnDOMDriver();
|
|
||||||
Syndicate.WakeDetector.spawnWakeDetector();
|
Syndicate.WakeDetector.spawnWakeDetector();
|
||||||
Syndicate.Broker.spawnBrokerClientDriver();
|
Syndicate.Broker.spawnBrokerClientDriver();
|
||||||
spawnInputChangeMonitor();
|
spawnInputChangeMonitor();
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
<head>
|
<head>
|
||||||
<title>Syndicate: DOM Example</title>
|
<title>Syndicate: DOM Example</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<script src="../../third-party/jquery-2.2.0.min.js"></script>
|
|
||||||
<script src="../../dist/syndicate.js"></script>
|
<script src="../../dist/syndicate.js"></script>
|
||||||
<script src="index.js"></script>
|
<script src="index.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
var G;
|
var G;
|
||||||
$(document).ready(function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
var Dataspace = Syndicate.Dataspace;
|
var Dataspace = Syndicate.Dataspace;
|
||||||
var sub = Syndicate.sub;
|
var sub = Syndicate.sub;
|
||||||
var assert = Syndicate.assert;
|
var assert = Syndicate.assert;
|
||||||
|
@ -10,18 +10,17 @@ $(document).ready(function () {
|
||||||
G = new Syndicate.Ground(function () {
|
G = new Syndicate.Ground(function () {
|
||||||
console.log('starting ground boot');
|
console.log('starting ground boot');
|
||||||
|
|
||||||
Syndicate.DOM.spawnDOMDriver();
|
Syndicate.UI.spawnUIDriver();
|
||||||
var DOM = Syndicate.DOM.DOM;
|
|
||||||
var jQueryEvent = Syndicate.JQuery.jQueryEvent;
|
|
||||||
|
|
||||||
Dataspace.spawn({
|
Dataspace.spawn({
|
||||||
boot: function () {
|
boot: function () {
|
||||||
return assert(DOM("#clicker-holder", "clicker",
|
var ui = new Syndicate.UI.Anchor();
|
||||||
|
return assert(ui.html("#clicker-holder",
|
||||||
'<button><span style="font-style: italic">Click me!</span></button>'))
|
'<button><span style="font-style: italic">Click me!</span></button>'))
|
||||||
.andThen(sub(jQueryEvent("button.clicker", "click", __)));
|
.andThen(sub(Syndicate.UI.globalEvent("button", "click", __)));
|
||||||
},
|
},
|
||||||
handleEvent: function (e) {
|
handleEvent: function (e) {
|
||||||
if (e.type === "message" && jQueryEvent.isClassOf(e.message)) {
|
if (e.type === "message" && Syndicate.UI.globalEvent.isClassOf(e.message)) {
|
||||||
Dataspace.send("bump_count");
|
Dataspace.send("bump_count");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,13 +28,15 @@ $(document).ready(function () {
|
||||||
|
|
||||||
Dataspace.spawn({
|
Dataspace.spawn({
|
||||||
counter: 0,
|
counter: 0,
|
||||||
|
ui: new Syndicate.UI.Anchor(),
|
||||||
boot: function () {
|
boot: function () {
|
||||||
this.updateState();
|
this.updateState();
|
||||||
return sub("bump_count");
|
return sub("bump_count");
|
||||||
},
|
},
|
||||||
updateState: function () {
|
updateState: function () {
|
||||||
Dataspace.stateChange(retract(DOM.pattern)
|
Dataspace.stateChange(retract(this.ui.htmlPattern)
|
||||||
.andThen(assert(DOM("#counter-holder", "counter",
|
.andThen(assert(this.ui.html(
|
||||||
|
"#counter-holder",
|
||||||
'<div><p>The current count is: '+this.counter+
|
'<div><p>The current count is: '+this.counter+
|
||||||
'</p></div>'))));
|
'</p></div>'))));
|
||||||
},
|
},
|
||||||
|
@ -49,7 +50,7 @@ $(document).ready(function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
G.dataspace.setOnStateChange(function (mux, patch) {
|
G.dataspace.setOnStateChange(function (mux, patch) {
|
||||||
$("#spy-holder").text(Syndicate.prettyTrie(mux.routingTable));
|
document.getElementById('spy-holder').innerText = Syndicate.prettyTrie(mux.routingTable);
|
||||||
});
|
});
|
||||||
|
|
||||||
G.startStepping();
|
G.startStepping();
|
||||||
|
|
|
@ -13,36 +13,25 @@ This is a simple clickable button; each time the button is clicked,
|
||||||
the number on the face of the button is incremented.
|
the number on the face of the button is incremented.
|
||||||
|
|
||||||
The actor maintaining the counter also maintains the button's label
|
The actor maintaining the counter also maintains the button's label
|
||||||
and listens to click events. It uses the Syndicate/js DOM driver to
|
and listens to click events. It uses the Syndicate/js UI driver to
|
||||||
publish the button's label text based on its internal state, and the
|
publish the button's label text based on its internal state and to
|
||||||
Syndicate/js jQuery driver to subscribe to button click events.
|
subscribe to button click events.
|
||||||
|
|
||||||
- [DEMO](button/)
|
- [DEMO](button/)
|
||||||
- [Source code](button/index.js) using the Syndicate/js DSL
|
- [Source code](button/index.js) using the Syndicate/js DSL
|
||||||
|
|
||||||
## DOM example
|
## DOM example
|
||||||
|
|
||||||
This example demonstrates two actors, each using the Syndicate/js DOM
|
This example demonstrates two actors, each using the Syndicate/js UI
|
||||||
driver to display user interface, and the jQuery driver to receive
|
driver to display user interface and receive events from it. The first
|
||||||
events from it. The first actor presents a button to the user, which
|
actor presents a button to the user, which when clicked sends a
|
||||||
when clicked sends a message to the other actor. The second actor
|
message to the other actor. The second actor receives messages from
|
||||||
receives messages from the first, updates its internal state, and
|
the first, updates its internal state, and reflects its new internal
|
||||||
reflects its new internal state in its visible UI.
|
state in its visible UI.
|
||||||
|
|
||||||
- [DEMO](dom/)
|
- [DEMO](dom/)
|
||||||
- [Source code](dom/index.js) in plain JavaScript
|
- [Source code](dom/index.js) in plain JavaScript
|
||||||
|
|
||||||
## jQuery Example
|
|
||||||
|
|
||||||
This example is similar to the button example, but uses plain
|
|
||||||
JavaScript instead of the Syndicate/js DSL, calling out to Syndicate
|
|
||||||
as a library. It uses the Syndicate/js jQuery driver to receive click
|
|
||||||
events from the button, but does not use the Syndicate/js DOM driver;
|
|
||||||
instead, it updates the DOM directly.
|
|
||||||
|
|
||||||
- [DEMO](jquery/)
|
|
||||||
- [Source code](jquery/index.js) in plain JavaScript
|
|
||||||
|
|
||||||
## Text Entry Widget
|
## Text Entry Widget
|
||||||
|
|
||||||
This is a simple text entry GUI control, following a design of
|
This is a simple text entry GUI control, following a design of
|
||||||
|
|
|
@ -5,17 +5,15 @@ assertion type tvAlert(text);
|
||||||
assertion type switchAction(on);
|
assertion type switchAction(on);
|
||||||
assertion type componentPresent(name);
|
assertion type componentPresent(name);
|
||||||
|
|
||||||
var DOM = Syndicate.DOM.DOM;
|
|
||||||
var jQueryEvent = Syndicate.JQuery.jQueryEvent;
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
// TV
|
// TV
|
||||||
|
|
||||||
function spawnTV() {
|
function spawnTV() {
|
||||||
actor {
|
actor {
|
||||||
|
var ui = new Syndicate.UI.Anchor();
|
||||||
react {
|
react {
|
||||||
during tvAlert($text) {
|
during tvAlert($text) {
|
||||||
assert DOM('#tv', 'alert', Mustache.render($('#alert_template').html(), { text: text }));
|
assert ui.context(text).html('#tv', Mustache.render($('#alert_template').html(), { text: text }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +26,7 @@ function spawnRemoteControl() {
|
||||||
actor {
|
actor {
|
||||||
react {
|
react {
|
||||||
assert componentPresent('remote control');
|
assert componentPresent('remote control');
|
||||||
on message jQueryEvent('#remote-control', 'click', _) {
|
on message Syndicate.UI.globalEvent('#remote-control', 'click', _) {
|
||||||
:: remoteClick();
|
:: remoteClick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,23 +61,24 @@ function spawnRemoteListener() {
|
||||||
function spawnStoveSwitch() {
|
function spawnStoveSwitch() {
|
||||||
actor {
|
actor {
|
||||||
this.powerOn = false;
|
this.powerOn = false;
|
||||||
|
this.ui = new Syndicate.UI.Anchor();
|
||||||
react {
|
react {
|
||||||
assert componentPresent('stove switch');
|
assert componentPresent('stove switch');
|
||||||
assert switchState(this.powerOn);
|
assert switchState(this.powerOn);
|
||||||
|
|
||||||
assert DOM('#stove-switch', 'switch-state',
|
assert this.ui.html('#stove-switch',
|
||||||
Mustache.render($('#stove_element_template').html(),
|
Mustache.render($('#stove_element_template').html(),
|
||||||
{ imgurl: ("img/stove-coil-element-" +
|
{ imgurl: ("img/stove-coil-element-" +
|
||||||
(this.powerOn ? "hot" : "cold") + ".jpg") }));
|
(this.powerOn ? "hot" : "cold") + ".jpg") }));
|
||||||
|
|
||||||
on message jQueryEvent('#stove-switch-on', 'click', _) { this.powerOn = true; }
|
on message Syndicate.UI.globalEvent('#stove-switch-on', 'click', _) { this.powerOn = true; }
|
||||||
on message jQueryEvent('#stove-switch-off', 'click', _) { this.powerOn = false; }
|
on message Syndicate.UI.globalEvent('#stove-switch-off', 'click', _) { this.powerOn = false; }
|
||||||
|
|
||||||
on message switchAction($newState) {
|
on message switchAction($newState) {
|
||||||
this.powerOn = newState;
|
this.powerOn = newState;
|
||||||
}
|
}
|
||||||
} until {
|
} until {
|
||||||
case message jQueryEvent('#kill-stove-switch', 'click', _);
|
case message Syndicate.UI.globalEvent('#kill-stove-switch', 'click', _);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,18 +86,19 @@ function spawnStoveSwitch() {
|
||||||
function spawnPowerDrawMonitor() {
|
function spawnPowerDrawMonitor() {
|
||||||
actor {
|
actor {
|
||||||
this.watts = 0;
|
this.watts = 0;
|
||||||
|
this.ui = new Syndicate.UI.Anchor();
|
||||||
react {
|
react {
|
||||||
assert componentPresent('power draw monitor');
|
assert componentPresent('power draw monitor');
|
||||||
assert powerDraw(this.watts);
|
assert powerDraw(this.watts);
|
||||||
|
|
||||||
assert DOM('#power-draw-meter', 'power-draw',
|
assert this.ui.html('#power-draw-meter',
|
||||||
Mustache.render($('#power_draw_template').html(), { watts: this.watts }));
|
Mustache.render($('#power_draw_template').html(), { watts: this.watts }));
|
||||||
|
|
||||||
on asserted switchState($on) {
|
on asserted switchState($on) {
|
||||||
this.watts = on ? 1500 : 0;
|
this.watts = on ? 1500 : 0;
|
||||||
}
|
}
|
||||||
} until {
|
} until {
|
||||||
case message jQueryEvent('#kill-power-draw-monitor', 'click', _);
|
case message Syndicate.UI.globalEvent('#kill-power-draw-monitor', 'click', _);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -207,7 +207,7 @@ function spawnChaosMonkey() {
|
||||||
jKillButtons.prop('disabled', true);
|
jKillButtons.prop('disabled', true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
on message jQueryEvent(spawnButtonSelector, 'click', _) {
|
on message Syndicate.UI.globalEvent(spawnButtonSelector, 'click', _) {
|
||||||
spawnFunction();
|
spawnFunction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -219,8 +219,7 @@ function spawnChaosMonkey() {
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
ground dataspace G {
|
ground dataspace G {
|
||||||
Syndicate.JQuery.spawnJQueryDriver();
|
Syndicate.UI.spawnUIDriver();
|
||||||
Syndicate.DOM.spawnDOMDriver();
|
|
||||||
Syndicate.Timer.spawnTimerDriver();
|
Syndicate.Timer.spawnTimerDriver();
|
||||||
|
|
||||||
spawnTV();
|
spawnTV();
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Syndicate: jQuery 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>jQuery example</h1>
|
|
||||||
<button id="clicker">Click me</button>
|
|
||||||
<div id="result">0</div>
|
|
||||||
<pre id="spy-holder"></pre>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,34 +0,0 @@
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var G;
|
|
||||||
$(document).ready(function () {
|
|
||||||
var Dataspace = Syndicate.Dataspace;
|
|
||||||
var sub = Syndicate.sub;
|
|
||||||
var __ = Syndicate.__;
|
|
||||||
var _$ = Syndicate._$;
|
|
||||||
|
|
||||||
G = new Syndicate.Ground(function () {
|
|
||||||
console.log('starting ground boot');
|
|
||||||
|
|
||||||
Syndicate.JQuery.spawnJQueryDriver();
|
|
||||||
|
|
||||||
Dataspace.spawn({
|
|
||||||
boot: function () {
|
|
||||||
return sub(Syndicate.JQuery.jQueryEvent('#clicker', 'click', __));
|
|
||||||
},
|
|
||||||
handleEvent: function (e) {
|
|
||||||
if (e.type === 'message'
|
|
||||||
&& Syndicate.JQuery.jQueryEvent.isClassOf(e.message)
|
|
||||||
&& e.message[0] === '#clicker')
|
|
||||||
{
|
|
||||||
var r = $('#result');
|
|
||||||
r.html(Number(r.html()) + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
G.dataspace.setOnStateChange(function (mux, patch) {
|
|
||||||
$("#spy-holder").text(Syndicate.prettyTrie(mux.routingTable));
|
|
||||||
});
|
|
||||||
G.startStepping();
|
|
||||||
});
|
|
|
@ -3,7 +3,6 @@
|
||||||
<head>
|
<head>
|
||||||
<title>Syndicate: Smoketest with DSL</title>
|
<title>Syndicate: Smoketest with DSL</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<script src="../../third-party/jquery-2.2.0.min.js"></script>
|
|
||||||
<script src="../../dist/syndicatecompiler.js"></script>
|
<script src="../../dist/syndicatecompiler.js"></script>
|
||||||
<script src="../../dist/syndicate.js"></script>
|
<script src="../../dist/syndicate.js"></script>
|
||||||
<script type="text/syndicate-js" src="index.js"></script>
|
<script type="text/syndicate-js" src="index.js"></script>
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
<head>
|
<head>
|
||||||
<title>Syndicate: Smoketest</title>
|
<title>Syndicate: Smoketest</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<script src="../../third-party/jquery-2.2.0.min.js"></script>
|
|
||||||
<script src="../../dist/syndicate.js"></script>
|
<script src="../../dist/syndicate.js"></script>
|
||||||
<script src="index.js"></script>
|
<script src="index.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
var beep = Syndicate.Struct.makeConstructor('beep', ['counter']);
|
var beep = Syndicate.Struct.makeConstructor('beep', ['counter']);
|
||||||
|
|
||||||
var G;
|
var G;
|
||||||
$(document).ready(function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
var Dataspace = Syndicate.Dataspace;
|
var Dataspace = Syndicate.Dataspace;
|
||||||
var sub = Syndicate.sub;
|
var sub = Syndicate.sub;
|
||||||
var __ = Syndicate.__;
|
var __ = Syndicate.__;
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
<title>Syndicate: SVG</title>
|
<title>Syndicate: SVG</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<script src="../../third-party/jquery-2.2.0.min.js"></script>
|
|
||||||
<script src="../../dist/syndicatecompiler.js"></script>
|
<script src="../../dist/syndicatecompiler.js"></script>
|
||||||
<script src="../../dist/syndicate.js"></script>
|
<script src="../../dist/syndicate.js"></script>
|
||||||
<script type="text/syndicate-js" src="index.js"></script>
|
<script type="text/syndicate-js" src="index.js"></script>
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
var DOM = Syndicate.DOM.DOM;
|
ground dataspace G {
|
||||||
|
Syndicate.UI.spawnUIDriver();
|
||||||
$(document).ready(function () {
|
|
||||||
ground dataspace G {
|
|
||||||
Syndicate.DOM.spawnDOMDriver();
|
|
||||||
Syndicate.Timer.spawnTimerDriver();
|
Syndicate.Timer.spawnTimerDriver();
|
||||||
|
|
||||||
actor {
|
actor {
|
||||||
|
var ui = new Syndicate.UI.Anchor();
|
||||||
react {
|
react {
|
||||||
assert DOM('#clock', 'clock',
|
assert ui.html('#clock',
|
||||||
'<svg width="300px" viewBox="0 0 100 100">'+
|
'<svg width="300px" viewBox="0 0 100 100">'+
|
||||||
'<circle fill="#0B79CE" r=45 cx=50 cy=50/>'+
|
'<circle fill="#0B79CE" r=45 cx=50 cy=50 />'+
|
||||||
'<line stroke="#023963" x1=50 y1=50 x2='+this.handX+' y2='+this.handY+' />'+
|
'<line stroke="#023963" x1=50 y1=50 x2='+this.handX+' y2='+this.handY+' />'+
|
||||||
'</svg>')
|
'</svg>')
|
||||||
when (typeof this.angle === 'number');
|
when (typeof this.angle === 'number');
|
||||||
|
@ -21,5 +19,4 @@ $(document).ready(function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
<title>Syndicate: Textfield Example (DSL variation)</title>
|
<title>Syndicate: Textfield Example (DSL variation)</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<link href="style.css" rel="stylesheet">
|
<link href="style.css" rel="stylesheet">
|
||||||
<script src="../../third-party/jquery-2.2.0.min.js"></script>
|
|
||||||
<script src="../../dist/syndicatecompiler.js"></script>
|
<script src="../../dist/syndicatecompiler.js"></script>
|
||||||
<script src="../../dist/syndicate.js"></script>
|
<script src="../../dist/syndicate.js"></script>
|
||||||
<script type="text/syndicate-js" src="index.js"></script>
|
<script type="text/syndicate-js" src="index.js"></script>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
// GUI
|
// GUI
|
||||||
|
|
||||||
var jQueryEvent = Syndicate.JQuery.jQueryEvent;
|
var globalEvent = Syndicate.UI.globalEvent;
|
||||||
assertion type fieldCommand(detail);
|
assertion type fieldCommand(detail);
|
||||||
assertion type fieldContents(text, pos);
|
assertion type fieldContents(text, pos);
|
||||||
assertion type highlight(state);
|
assertion type highlight(state);
|
||||||
|
@ -36,7 +36,7 @@ function spawnGui() {
|
||||||
var highlight = this.highlightState;
|
var highlight = this.highlightState;
|
||||||
var hLeft = highlight ? highlight[0] : 0;
|
var hLeft = highlight ? highlight[0] : 0;
|
||||||
var hRight = highlight ? highlight[1] : 0;
|
var hRight = highlight ? highlight[1] : 0;
|
||||||
$("#fieldContents")[0].innerHTML = highlight
|
document.getElementById("fieldContents").innerHTML = highlight
|
||||||
? piece(text, pos, 0, hLeft, "normal") +
|
? piece(text, pos, 0, hLeft, "normal") +
|
||||||
piece(text, pos, hLeft, hRight, "highlight") +
|
piece(text, pos, hLeft, hRight, "highlight") +
|
||||||
piece(text, pos, hRight, text.length + 1, "normal")
|
piece(text, pos, hRight, text.length + 1, "normal")
|
||||||
|
@ -44,18 +44,22 @@ function spawnGui() {
|
||||||
};
|
};
|
||||||
|
|
||||||
react {
|
react {
|
||||||
on message jQueryEvent("#inputRow", "+keypress", $event) {
|
on message globalEvent("#inputRow", "+keydown", $event) {
|
||||||
var keycode = event.keyCode;
|
switch (event.keyCode) {
|
||||||
var character = String.fromCharCode(event.charCode);
|
case 37 /* left */: :: fieldCommand("cursorLeft"); break;
|
||||||
if (keycode === 37 /* left */) {
|
case 39 /* right */: :: fieldCommand("cursorRight"); break;
|
||||||
:: fieldCommand("cursorLeft");
|
case 9 /* tab */: /* ignore */ break;
|
||||||
} else if (keycode === 39 /* right */) {
|
case 8 /* backspace */:
|
||||||
:: fieldCommand("cursorRight");
|
event.preventDefault(); // that this works here is a minor miracle
|
||||||
} else if (keycode === 9 /* tab */) {
|
|
||||||
// ignore
|
|
||||||
} else if (keycode === 8 /* backspace */) {
|
|
||||||
:: fieldCommand("backspace");
|
:: fieldCommand("backspace");
|
||||||
} else if (character) {
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
on message globalEvent("#inputRow", "+keypress", $event) {
|
||||||
|
var character = String.fromCharCode(event.charCode);
|
||||||
|
if (event.charCode && character) {
|
||||||
:: fieldCommand(["insert", character]);
|
:: fieldCommand(["insert", character]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,7 +130,7 @@ function spawnSearch() {
|
||||||
this.highlight = false;
|
this.highlight = false;
|
||||||
|
|
||||||
this.search = function () {
|
this.search = function () {
|
||||||
var searchtext = $("#searchBox")[0].value;
|
var searchtext = document.getElementById("searchBox").value;
|
||||||
if (searchtext) {
|
if (searchtext) {
|
||||||
var pos = this.fieldValue.indexOf(searchtext);
|
var pos = this.fieldValue.indexOf(searchtext);
|
||||||
this.highlight = (pos !== -1) && [pos, pos + searchtext.length];
|
this.highlight = (pos !== -1) && [pos, pos + searchtext.length];
|
||||||
|
@ -138,7 +142,7 @@ function spawnSearch() {
|
||||||
react {
|
react {
|
||||||
assert highlight(this.highlight);
|
assert highlight(this.highlight);
|
||||||
|
|
||||||
on message jQueryEvent("#searchBox", "input", $event) {
|
on message globalEvent("#searchBox", "input", $event) {
|
||||||
this.search();
|
this.search();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,17 +157,14 @@ function spawnSearch() {
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
// Main
|
// Main
|
||||||
|
|
||||||
$(document).ready(function () {
|
ground dataspace G {
|
||||||
ground dataspace G {
|
Syndicate.UI.spawnUIDriver();
|
||||||
Syndicate.JQuery.spawnJQueryDriver();
|
|
||||||
Syndicate.DOM.spawnDOMDriver();
|
|
||||||
|
|
||||||
spawnGui();
|
spawnGui();
|
||||||
spawnModel();
|
spawnModel();
|
||||||
spawnSearch();
|
spawnSearch();
|
||||||
}
|
}
|
||||||
|
|
||||||
G.dataspace.setOnStateChange(function (mux, patch) {
|
G.dataspace.setOnStateChange(function (mux, patch) {
|
||||||
$("#spy-holder").text(Syndicate.prettyTrie(mux.routingTable));
|
document.getElementById("spy-holder").innerText = Syndicate.prettyTrie(mux.routingTable);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
<title>Syndicate: Textfield Example</title>
|
<title>Syndicate: Textfield Example</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<link href="style.css" rel="stylesheet">
|
<link href="style.css" rel="stylesheet">
|
||||||
<script src="../../third-party/jquery-2.2.0.min.js"></script>
|
|
||||||
<script src="../../dist/syndicate.js"></script>
|
<script src="../../dist/syndicate.js"></script>
|
||||||
<script src="index.js"></script>
|
<script src="index.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -7,7 +7,7 @@ var Patch = Syndicate.Patch;
|
||||||
var __ = Syndicate.__;
|
var __ = Syndicate.__;
|
||||||
var _$ = Syndicate._$;
|
var _$ = Syndicate._$;
|
||||||
|
|
||||||
var jQueryEvent = Syndicate.JQuery.jQueryEvent;
|
var globalEvent = Syndicate.UI.globalEvent;
|
||||||
var fieldContents = Syndicate.Struct.makeConstructor('fieldContents', ['text', 'pos']);
|
var fieldContents = Syndicate.Struct.makeConstructor('fieldContents', ['text', 'pos']);
|
||||||
var highlight = Syndicate.Struct.makeConstructor('highlight', ['state']);
|
var highlight = Syndicate.Struct.makeConstructor('highlight', ['state']);
|
||||||
var fieldCommand = Syndicate.Struct.makeConstructor('fieldCommand', ['detail']);
|
var fieldCommand = Syndicate.Struct.makeConstructor('fieldCommand', ['detail']);
|
||||||
|
@ -36,7 +36,8 @@ function spawnGui() {
|
||||||
highlight: { state: false },
|
highlight: { state: false },
|
||||||
|
|
||||||
boot: function () {
|
boot: function () {
|
||||||
return Patch.sub(jQueryEvent("#inputRow", "+keypress", __))
|
return Patch.sub(globalEvent("#inputRow", "keypress", __))
|
||||||
|
.andThen(Patch.sub(globalEvent("#inputRow", "+keydown", __)))
|
||||||
.andThen(Patch.sub(fieldContents.pattern))
|
.andThen(Patch.sub(fieldContents.pattern))
|
||||||
.andThen(Patch.sub(highlight.pattern));
|
.andThen(Patch.sub(highlight.pattern));
|
||||||
},
|
},
|
||||||
|
@ -48,20 +49,29 @@ function spawnGui() {
|
||||||
switch (e.type) {
|
switch (e.type) {
|
||||||
case "message":
|
case "message":
|
||||||
var event = e.message[2];
|
var event = e.message[2];
|
||||||
var keycode = event.keyCode;
|
switch (event.type) {
|
||||||
var character = String.fromCharCode(event.charCode);
|
case "keydown":
|
||||||
if (keycode === 37 /* left */) {
|
switch (event.keyCode) {
|
||||||
Dataspace.send(fieldCommand("cursorLeft"));
|
case 37 /* left */: Dataspace.send(fieldCommand("cursorLeft")); break;
|
||||||
} else if (keycode === 39 /* right */) {
|
case 39 /* right */: Dataspace.send(fieldCommand("cursorRight")); break;
|
||||||
Dataspace.send(fieldCommand("cursorRight"));
|
case 9 /* tab */: /* ignore */ break;
|
||||||
} else if (keycode === 9 /* tab */) {
|
case 8 /* backspace */:
|
||||||
// ignore
|
event.preventDefault(); // that this works here is a minor miracle
|
||||||
} else if (keycode === 8 /* backspace */) {
|
|
||||||
Dataspace.send(fieldCommand("backspace"));
|
Dataspace.send(fieldCommand("backspace"));
|
||||||
} else if (character) {
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "keypress":
|
||||||
|
var character = String.fromCharCode(event.charCode);
|
||||||
|
if (event.charCode && character) {
|
||||||
Dataspace.send(fieldCommand(["insert", character]));
|
Dataspace.send(fieldCommand(["insert", character]));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "stateChange":
|
case "stateChange":
|
||||||
Trie.projectObjects(e.patch.added, this.fieldContentsProjection).forEach(function (c) {
|
Trie.projectObjects(e.patch.added, this.fieldContentsProjection).forEach(function (c) {
|
||||||
self.field = c;
|
self.field = c;
|
||||||
|
@ -80,7 +90,7 @@ function spawnGui() {
|
||||||
var highlight = this.highlight ? this.highlight.state : false;
|
var highlight = this.highlight ? this.highlight.state : false;
|
||||||
var hLeft = highlight ? highlight[0] : 0;
|
var hLeft = highlight ? highlight[0] : 0;
|
||||||
var hRight = highlight ? highlight[1] : 0;
|
var hRight = highlight ? highlight[1] : 0;
|
||||||
$("#fieldContents")[0].innerHTML = highlight
|
document.getElementById("fieldContents").innerHTML = highlight
|
||||||
? piece(text, pos, 0, hLeft, "normal") +
|
? piece(text, pos, 0, hLeft, "normal") +
|
||||||
piece(text, pos, hLeft, hRight, "highlight") +
|
piece(text, pos, hLeft, hRight, "highlight") +
|
||||||
piece(text, pos, hRight, text.length + 1, "normal")
|
piece(text, pos, hRight, text.length + 1, "normal")
|
||||||
|
@ -149,14 +159,14 @@ function spawnSearch() {
|
||||||
|
|
||||||
boot: function () {
|
boot: function () {
|
||||||
this.publishState();
|
this.publishState();
|
||||||
return Patch.sub(jQueryEvent("#searchBox", "input", __))
|
return Patch.sub(globalEvent("#searchBox", "input", __))
|
||||||
.andThen(Patch.sub(fieldContents.pattern));
|
.andThen(Patch.sub(fieldContents.pattern));
|
||||||
},
|
},
|
||||||
|
|
||||||
fieldContentsProjection: fieldContents(_$("text"), _$("pos")),
|
fieldContentsProjection: fieldContents(_$("text"), _$("pos")),
|
||||||
handleEvent: function (e) {
|
handleEvent: function (e) {
|
||||||
var self = this;
|
var self = this;
|
||||||
if (jQueryEvent.isClassOf(e.message)) {
|
if (globalEvent.isClassOf(e.message)) {
|
||||||
this.search();
|
this.search();
|
||||||
}
|
}
|
||||||
if (e.type === "stateChange") {
|
if (e.type === "stateChange") {
|
||||||
|
@ -174,7 +184,7 @@ function spawnSearch() {
|
||||||
},
|
},
|
||||||
|
|
||||||
search: function () {
|
search: function () {
|
||||||
var searchtext = $("#searchBox")[0].value;
|
var searchtext = document.getElementById("searchBox").value;
|
||||||
var oldHighlight = this.highlight;
|
var oldHighlight = this.highlight;
|
||||||
if (searchtext) {
|
if (searchtext) {
|
||||||
var pos = this.fieldValue.indexOf(searchtext);
|
var pos = this.fieldValue.indexOf(searchtext);
|
||||||
|
@ -193,10 +203,9 @@ function spawnSearch() {
|
||||||
// Main
|
// Main
|
||||||
|
|
||||||
var G;
|
var G;
|
||||||
$(document).ready(function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
G = new Syndicate.Ground(function () {
|
G = new Syndicate.Ground(function () {
|
||||||
Syndicate.JQuery.spawnJQueryDriver();
|
Syndicate.UI.spawnUIDriver();
|
||||||
Syndicate.DOM.spawnDOMDriver();
|
|
||||||
|
|
||||||
spawnGui();
|
spawnGui();
|
||||||
spawnModel();
|
spawnModel();
|
||||||
|
@ -204,7 +213,7 @@ $(document).ready(function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
G.dataspace.setOnStateChange(function (mux, patch) {
|
G.dataspace.setOnStateChange(function (mux, patch) {
|
||||||
$("#spy-holder").text(Syndicate.prettyTrie(mux.routingTable));
|
document.getElementById("spy-holder").innerText = Syndicate.prettyTrie(mux.routingTable);
|
||||||
});
|
});
|
||||||
|
|
||||||
G.startStepping();
|
G.startStepping();
|
||||||
|
|
|
@ -1,120 +0,0 @@
|
||||||
// DOM fragment display driver
|
|
||||||
var Patch = require("./patch.js");
|
|
||||||
var DemandMatcher = require('./demand-matcher.js').DemandMatcher;
|
|
||||||
var Struct = require('./struct.js');
|
|
||||||
var Ack = require('./ack.js').Ack;
|
|
||||||
|
|
||||||
var Dataspace_ = require("./dataspace.js");
|
|
||||||
var Dataspace = Dataspace_.Dataspace;
|
|
||||||
var __ = Dataspace_.__;
|
|
||||||
var _$ = Dataspace_._$;
|
|
||||||
|
|
||||||
var DOM = Struct.makeConstructor('DOM', ['selector', 'fragmentClass', 'fragmentSpec']);
|
|
||||||
|
|
||||||
function spawnDOMDriver(domWrapFunction, jQueryWrapFunction) {
|
|
||||||
domWrapFunction = domWrapFunction || DOM;
|
|
||||||
var spec = domWrapFunction(_$('selector'), _$('fragmentClass'), _$('fragmentSpec'));
|
|
||||||
Dataspace.spawn(
|
|
||||||
new DemandMatcher([spec],
|
|
||||||
[Patch.advertise(spec)],
|
|
||||||
{
|
|
||||||
onDemandIncrease: function (c) {
|
|
||||||
Dataspace.spawn(new DOMFragment(c.selector,
|
|
||||||
c.fragmentClass,
|
|
||||||
c.fragmentSpec,
|
|
||||||
domWrapFunction,
|
|
||||||
jQueryWrapFunction));
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
function DOMFragment(selector, fragmentClass, fragmentSpec, domWrapFunction, jQueryWrapFunction) {
|
|
||||||
this.selector = selector;
|
|
||||||
this.fragmentClass = fragmentClass;
|
|
||||||
this.fragmentSpec = fragmentSpec;
|
|
||||||
this.domWrapFunction = domWrapFunction;
|
|
||||||
this.jQueryWrapFunction = jQueryWrapFunction;
|
|
||||||
this.demandExists = false;
|
|
||||||
this.subscriptionEstablished = new Ack();
|
|
||||||
this.nodes = this.buildNodes();
|
|
||||||
}
|
|
||||||
|
|
||||||
DOMFragment.prototype.boot = function () {
|
|
||||||
var self = this;
|
|
||||||
var specification = self.domWrapFunction(self.selector, self.fragmentClass, self.fragmentSpec);
|
|
||||||
|
|
||||||
Dataspace.spawn(new Dataspace(function () {
|
|
||||||
Syndicate.JQuery.spawnJQueryDriver(self.selector+" > ."+self.fragmentClass,
|
|
||||||
1,
|
|
||||||
self.jQueryWrapFunction);
|
|
||||||
Dataspace.spawn({
|
|
||||||
demandExists: false,
|
|
||||||
subscriptionEstablished: new Ack(1),
|
|
||||||
boot: function () {
|
|
||||||
this.subscriptionEstablished.arm();
|
|
||||||
return Patch.sub(Patch.advertise(specification), 1);
|
|
||||||
},
|
|
||||||
handleEvent: function (e) {
|
|
||||||
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) {
|
|
||||||
Dataspace.exitDataspace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.subscriptionEstablished.arm();
|
|
||||||
return Patch.sub(specification).andThen(Patch.pub(specification));
|
|
||||||
};
|
|
||||||
|
|
||||||
DOMFragment.prototype.handleEvent = function (e) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
Dataspace.exit();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
DOMFragment.prototype.buildNodes = function () {
|
|
||||||
var self = this;
|
|
||||||
var nodes = [];
|
|
||||||
$(self.selector).each(function (index, domNode) {
|
|
||||||
if (typeof self.fragmentSpec !== 'string') {
|
|
||||||
throw new Error("DOM fragmentSpec not a string: " + JSON.stringify(self.fragmentSpec));
|
|
||||||
}
|
|
||||||
var newNodes = $('<div>' + self.fragmentSpec + '</div>')[0].childNodes;
|
|
||||||
// This next loop looks SUPER SUSPICIOUS. What is happening is
|
|
||||||
// that each time we call domNode.appendChild(n), where n is an
|
|
||||||
// element of the NodeList newNodes, the DOM is **removing** n
|
|
||||||
// from the NodeList in order to place it in its new parent. So,
|
|
||||||
// each call to appendChild shrinks the NodeList by one node until
|
|
||||||
// it is finally empty, and its length property yields zero.
|
|
||||||
while (newNodes.length) {
|
|
||||||
var n = newNodes[0];
|
|
||||||
if ('classList' in n) {
|
|
||||||
n.classList.add(self.fragmentClass);
|
|
||||||
}
|
|
||||||
domNode.appendChild(n);
|
|
||||||
nodes.push(n);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return nodes;
|
|
||||||
};
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
module.exports.spawnDOMDriver = spawnDOMDriver;
|
|
||||||
module.exports.DOM = DOM;
|
|
|
@ -1,90 +0,0 @@
|
||||||
// JQuery event driver
|
|
||||||
var Patch = require("./patch.js");
|
|
||||||
var DemandMatcher = require('./demand-matcher.js').DemandMatcher;
|
|
||||||
var Struct = require('./struct.js');
|
|
||||||
|
|
||||||
var Dataspace_ = require("./dataspace.js");
|
|
||||||
var Dataspace = Dataspace_.Dataspace;
|
|
||||||
var __ = Dataspace_.__;
|
|
||||||
var _$ = Dataspace_._$;
|
|
||||||
|
|
||||||
var jQueryEvent = Struct.makeConstructor('jQueryEvent', ['selector', 'eventName', 'eventValue']);
|
|
||||||
|
|
||||||
function spawnJQueryDriver(baseSelector, metaLevel, wrapFunction) {
|
|
||||||
metaLevel = metaLevel || 0;
|
|
||||||
wrapFunction = wrapFunction || jQueryEvent;
|
|
||||||
Dataspace.spawn(
|
|
||||||
new DemandMatcher([Patch.observe(wrapFunction(_$('selector'), _$('eventName'), __))],
|
|
||||||
[Patch.advertise(wrapFunction(_$('selector'), _$('eventName'), __))],
|
|
||||||
{
|
|
||||||
metaLevel: metaLevel,
|
|
||||||
onDemandIncrease: function (c) {
|
|
||||||
Dataspace.spawn(new JQueryEventRouter(baseSelector,
|
|
||||||
c.selector,
|
|
||||||
c.eventName,
|
|
||||||
metaLevel,
|
|
||||||
wrapFunction));
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
function JQueryEventRouter(baseSelector, selector, eventName, metaLevel, wrapFunction) {
|
|
||||||
var self = this;
|
|
||||||
this.baseSelector = baseSelector || null;
|
|
||||||
this.selector = selector;
|
|
||||||
this.eventName = eventName;
|
|
||||||
this.metaLevel = metaLevel || 0;
|
|
||||||
this.wrapFunction = wrapFunction || jQueryEvent;
|
|
||||||
this.preventDefault = (this.eventName.charAt(0) !== "+");
|
|
||||||
this.handler =
|
|
||||||
Dataspace.wrap(function (e) {
|
|
||||||
Dataspace.send(self.wrapFunction(self.selector, self.eventName, e), self.metaLevel);
|
|
||||||
if (self.preventDefault) e.preventDefault();
|
|
||||||
return !self.preventDefault;
|
|
||||||
});
|
|
||||||
this.computeNodes().on(this.preventDefault ? this.eventName : this.eventName.substring(1),
|
|
||||||
this.handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
JQueryEventRouter.prototype.boot = function () {
|
|
||||||
return Patch.pub(this.wrapFunction(this.selector, this.eventName, __), this.metaLevel)
|
|
||||||
.andThen(Patch.sub(Patch.observe(this.wrapFunction(this.selector, this.eventName, __)),
|
|
||||||
this.metaLevel));
|
|
||||||
};
|
|
||||||
|
|
||||||
JQueryEventRouter.prototype.handleEvent = function (e) {
|
|
||||||
if (e.type === "stateChange" && e.patch.hasRemoved()) {
|
|
||||||
this.computeNodes().off(this.eventName, this.handler);
|
|
||||||
Dataspace.exit();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
JQueryEventRouter.prototype.computeNodes = function () {
|
|
||||||
if (this.baseSelector) {
|
|
||||||
return $(this.baseSelector).children(this.selector).addBack(this.selector);
|
|
||||||
} else {
|
|
||||||
return $(this.selector);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function simplifyDOMEvent(e) {
|
|
||||||
var keys = [];
|
|
||||||
for (var k in e) {
|
|
||||||
var v = e[k];
|
|
||||||
if (typeof v === 'object') continue;
|
|
||||||
if (typeof v === 'function') continue;
|
|
||||||
keys.push(k);
|
|
||||||
}
|
|
||||||
keys.sort();
|
|
||||||
var simplified = [];
|
|
||||||
for (var i = 0; i < keys.length; i++) {
|
|
||||||
simplified.push([keys[i], e[keys[i]]]);
|
|
||||||
}
|
|
||||||
return simplified;
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
module.exports.spawnJQueryDriver = spawnJQueryDriver;
|
|
||||||
module.exports.simplifyDOMEvent = simplifyDOMEvent;
|
|
||||||
module.exports.jQueryEvent = jQueryEvent;
|
|
|
@ -27,8 +27,7 @@ module.exports.DemandMatcher = require('./demand-matcher.js').DemandMatcher;
|
||||||
module.exports.Ack = require('./ack.js').Ack;
|
module.exports.Ack = require('./ack.js').Ack;
|
||||||
|
|
||||||
module.exports.RandomID = require('./randomid.js');
|
module.exports.RandomID = require('./randomid.js');
|
||||||
module.exports.DOM = require("./dom-driver.js");
|
module.exports.UI = require('./ui.js');
|
||||||
module.exports.JQuery = require("./jquery-driver.js");
|
|
||||||
module.exports.Timer = require("./timer-driver.js");
|
module.exports.Timer = require("./timer-driver.js");
|
||||||
module.exports.Reflect = require("./reflect.js");
|
module.exports.Reflect = require("./reflect.js");
|
||||||
module.exports.WakeDetector = require("./wake-detector-driver.js");
|
module.exports.WakeDetector = require("./wake-detector-driver.js");
|
||||||
|
|
|
@ -3,10 +3,19 @@ var randomId;
|
||||||
if ((typeof window !== 'undefined') &&
|
if ((typeof window !== 'undefined') &&
|
||||||
(typeof window.crypto !== 'undefined') &&
|
(typeof window.crypto !== 'undefined') &&
|
||||||
(typeof window.crypto.getRandomValues !== 'undefined')) {
|
(typeof window.crypto.getRandomValues !== 'undefined')) {
|
||||||
randomId = function (byteCount) {
|
randomId = function (byteCount, hexOutput) {
|
||||||
var buf = new Uint8Array(byteCount);
|
var buf = new Uint8Array(byteCount);
|
||||||
window.crypto.getRandomValues(buf);
|
window.crypto.getRandomValues(buf);
|
||||||
|
if (hexOutput) {
|
||||||
|
var encoded = [];
|
||||||
|
for (var i = 0; i < buf.length; i++) {
|
||||||
|
encoded.push("0123456789abcdef"[(buf[i] >> 4) & 15]);
|
||||||
|
encoded.push("0123456789abcdef"[buf[i] & 15]);
|
||||||
|
}
|
||||||
|
return encoded.join('');
|
||||||
|
} else {
|
||||||
return btoa(String.fromCharCode.apply(null, buf)).replace(/=/g,'');
|
return btoa(String.fromCharCode.apply(null, buf)).replace(/=/g,'');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
var crypto;
|
var crypto;
|
||||||
|
@ -15,8 +24,12 @@ if ((typeof window !== 'undefined') &&
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
if ((typeof crypto !== 'undefined') &&
|
if ((typeof crypto !== 'undefined') &&
|
||||||
(typeof crypto.randomBytes !== 'undefined')) {
|
(typeof crypto.randomBytes !== 'undefined')) {
|
||||||
randomId = function (byteCount) {
|
randomId = function (byteCount, hexOutput) {
|
||||||
|
if (hexOutput) {
|
||||||
|
return crypto.randomBytes(byteCount).hexSlice().replace(/=/g,'');
|
||||||
|
} else {
|
||||||
return crypto.randomBytes(byteCount).base64Slice().replace(/=/g,'');
|
return crypto.randomBytes(byteCount).base64Slice().replace(/=/g,'');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
console.warn('No suitable implementation for RandomID.randomId available.');
|
console.warn('No suitable implementation for RandomID.randomId available.');
|
||||||
|
|
|
@ -0,0 +1,360 @@
|
||||||
|
"use strict";
|
||||||
|
// UI (DOM + event) support for Syndicate
|
||||||
|
//
|
||||||
|
// The previous dom-driver.js + jquery-driver.js approach worked kind
|
||||||
|
// of OK, but started to fall down in a couple of areas: Added UI
|
||||||
|
// fragments lacked identity, so would sometimes move around the tree
|
||||||
|
// unexpectedly as they were updated; and there was no convenient
|
||||||
|
// means of scoping event selectors to within a particular UI
|
||||||
|
// fragment, despite various attempts at this.
|
||||||
|
//
|
||||||
|
// The design of this module aims to take these lessons into account.
|
||||||
|
|
||||||
|
var Patch = require("./patch.js");
|
||||||
|
var Trie = require("./trie.js");
|
||||||
|
var DemandMatcher = require('./demand-matcher.js').DemandMatcher;
|
||||||
|
var Struct = require('./struct.js');
|
||||||
|
var RandomID = require('./randomid.js');
|
||||||
|
|
||||||
|
var Dataspace_ = require("./dataspace.js");
|
||||||
|
var Dataspace = Dataspace_.Dataspace;
|
||||||
|
var __ = Dataspace_.__;
|
||||||
|
var _$ = Dataspace_._$;
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Protocol
|
||||||
|
|
||||||
|
// Message. Interest in this causes event listeners to be added for
|
||||||
|
// the given eventType to all nodes matching the given selector *at
|
||||||
|
// the time of the subscription*. As nodes *from this library* come
|
||||||
|
// and go, they will have event handlers installed and removed as
|
||||||
|
// well. WARNING: The simple implementation below currently scans the
|
||||||
|
// whole document anytime a change is signalled; in future, it may not
|
||||||
|
// do such a scan.
|
||||||
|
var globalEvent = Struct.makeConstructor('globalEvent', ['selector', 'eventType', 'event']);
|
||||||
|
|
||||||
|
// Message. Like globalEvent, but applies only within the scope of the
|
||||||
|
// UI fragment identified.
|
||||||
|
var uiEvent = Struct.makeConstructor('uiEvent', ['fragmentId', 'selector', 'eventType', 'event']);
|
||||||
|
|
||||||
|
// Assertion. Causes the setup of DOM nodes corresponding to the given
|
||||||
|
// HTML fragment, as immediate children of all nodes named by the
|
||||||
|
// given selector that exist at the time of assertion.
|
||||||
|
var uiFragment = Struct.makeConstructor('uiFragment', ['fragmentId', 'selector', 'html']);
|
||||||
|
|
||||||
|
// Assertion. Asserted by respondent to a given uiFragment.
|
||||||
|
var uiFragmentExists = Struct.makeConstructor('uiFragmentExists', ['fragmentId']);
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// ID allocators
|
||||||
|
|
||||||
|
var moduleInstance = RandomID.randomId(16, true);
|
||||||
|
|
||||||
|
var nextFragmentIdNumber = 0;
|
||||||
|
function newFragmentId() {
|
||||||
|
return 'ui_' + moduleInstance + '_' + (nextFragmentIdNumber++);
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function spawnUIDriver() {
|
||||||
|
var globalEventProj = globalEvent(_$('selector'), _$('eventType'), __);
|
||||||
|
Dataspace.spawn(
|
||||||
|
new DemandMatcher([Patch.observe(globalEventProj)],
|
||||||
|
[Patch.advertise(globalEventProj)],
|
||||||
|
{
|
||||||
|
onDemandIncrease: function (c) {
|
||||||
|
Dataspace.spawn(new GlobalEventSupply(c.selector, c.eventType));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
Dataspace.spawn(
|
||||||
|
new DemandMatcher([uiFragment(_$('fragmentId'), __, __)],
|
||||||
|
[uiFragmentExists(_$('fragmentId'))],
|
||||||
|
{
|
||||||
|
onDemandIncrease: function (c) {
|
||||||
|
Dataspace.spawn(new UIFragment(c.fragmentId));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function GlobalEventSupply(selector, eventType) {
|
||||||
|
this.selector = selector;
|
||||||
|
this.eventType = eventType;
|
||||||
|
this.demandPat = Patch.observe(globalEvent(this.selector, this.eventType, __));
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalEventSupply.prototype.boot = function () {
|
||||||
|
var self = this;
|
||||||
|
this.handlerClosure = Dataspace.wrap(function(e) { return self.handleDomEvent(e); });
|
||||||
|
this.updateEventListeners(true);
|
||||||
|
|
||||||
|
return Patch.sub(this.demandPat) // track demand
|
||||||
|
.andThen(Patch.sub(uiFragmentExists(__))) // track new fragments
|
||||||
|
.andThen(Patch.pub(globalEvent(this.selector, this.eventType, __))) // indicate our presence
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
|
GlobalEventSupply.prototype.updateEventListeners = function (install) {
|
||||||
|
var nodes = document.querySelectorAll(this.selector);
|
||||||
|
for (var i = 0; i < nodes.length; i++) {
|
||||||
|
var n = nodes[i];
|
||||||
|
// addEventListener and removeEventListener are apparently idempotent.
|
||||||
|
if (install) {
|
||||||
|
n.addEventListener(cleanEventType(this.eventType), this.handlerClosure);
|
||||||
|
} else {
|
||||||
|
n.removeEventListener(cleanEventType(this.eventType), this.handlerClosure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
GlobalEventSupply.prototype.trapexit = function () {
|
||||||
|
console.log('GlobalEventSupply trapexit running', this.selector, this.eventType);
|
||||||
|
this.updateEventListeners(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
GlobalEventSupply.prototype.handleDomEvent = function (event) {
|
||||||
|
Dataspace.send(globalEvent(this.selector, this.eventType, event));
|
||||||
|
return dealWithPreventDefault(this.eventType, event);
|
||||||
|
};
|
||||||
|
|
||||||
|
GlobalEventSupply.prototype.handleEvent = function (e) {
|
||||||
|
this.updateEventListeners(true);
|
||||||
|
// TODO: don't be so crude about this ^. On the one hand, this lets
|
||||||
|
// us ignore uiFragmentExists records coming and going; on the other
|
||||||
|
// hand, we do potentially a lot of redundant work.
|
||||||
|
if (e.type === 'stateChange' && e.patch.project(this.demandPat).hasRemoved()) {
|
||||||
|
Dataspace.exit(); // trapexit will uninstall event listeners
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function UIFragment(fragmentId) {
|
||||||
|
this.fragmentId = fragmentId;
|
||||||
|
this.demandProj = uiFragment(this.fragmentId, _$('selector'), _$('html'));
|
||||||
|
this.eventDemandProj =
|
||||||
|
Patch.observe(uiEvent(this.fragmentId, _$('selector'), _$('eventType'), __));
|
||||||
|
|
||||||
|
this.currentAnchorNodes = [];
|
||||||
|
this.currentSelector = null;
|
||||||
|
this.currentHtml = null;
|
||||||
|
|
||||||
|
this.eventClosures = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
UIFragment.prototype.boot = function () {
|
||||||
|
return Patch.sub(Trie.projectionToPattern(this.demandProj)) // track demand
|
||||||
|
.andThen(Patch.assert(uiFragmentExists(this.fragmentId))) // assert presence
|
||||||
|
.andThen(Patch.sub(Trie.projectionToPattern(this.eventDemandProj)))
|
||||||
|
// ^ track demand for fragment-specific events
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
|
UIFragment.prototype.trapexit = function () {
|
||||||
|
console.log('UIFragment trapexit running', this.fragmentId);
|
||||||
|
this.updateContent(null, null);
|
||||||
|
};
|
||||||
|
|
||||||
|
function brandNode(n, fragmentId, brandValue) {
|
||||||
|
if ('dataset' in n) {
|
||||||
|
// html element nodes etc.
|
||||||
|
n.dataset[fragmentId] = brandValue;
|
||||||
|
} else {
|
||||||
|
// text nodes, svg nodes, etc etc.
|
||||||
|
n[fragmentId] = brandValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBrand(n, fragmentId) {
|
||||||
|
if ('dataset' in n && n.dataset[fragmentId]) return n.dataset[fragmentId];
|
||||||
|
if (n[fragmentId]) return n[fragmentId];
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findInsertionPoint(n, fragmentId) {
|
||||||
|
for (var i = 0; i < n.childNodes.length; i++) {
|
||||||
|
var c = n.childNodes[i];
|
||||||
|
if (getBrand(c, fragmentId)) return c;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function htmlToNodes(html) {
|
||||||
|
var e = document.createElement('arbitrarycontainer');
|
||||||
|
e.innerHTML = html;
|
||||||
|
return Array.prototype.slice.call(e.childNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
UIFragment.prototype.updateContent = function (newSelector, newHtml) {
|
||||||
|
var self = this;
|
||||||
|
var newBrand = '' + (Date.now());
|
||||||
|
|
||||||
|
var newAnchors = (newSelector !== null)
|
||||||
|
? Array.prototype.slice.call(document.querySelectorAll(newSelector))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
newAnchors.forEach(function (anchorNode) {
|
||||||
|
var insertionPoint = findInsertionPoint(anchorNode, self.fragmentId);
|
||||||
|
htmlToNodes(newHtml).forEach(function (newNode) {
|
||||||
|
brandNode(newNode, self.fragmentId, newBrand);
|
||||||
|
anchorNode.insertBefore(newNode, insertionPoint);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
self.currentAnchorNodes.forEach(function (anchorNode) {
|
||||||
|
var insertionPoint = findInsertionPoint(anchorNode, self.fragmentId);
|
||||||
|
while (insertionPoint) {
|
||||||
|
var nextNode = insertionPoint.nextSibling;
|
||||||
|
var b = getBrand(insertionPoint, self.fragmentId);
|
||||||
|
if (!b) break; // we know all our-brand nodes will be adjacent
|
||||||
|
if (b !== newBrand) {
|
||||||
|
insertionPoint.parentNode.removeChild(insertionPoint);
|
||||||
|
}
|
||||||
|
insertionPoint = nextNode;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
self.currentAnchorNodes = newAnchors;
|
||||||
|
self.currentSelector = newSelector;
|
||||||
|
self.currentHtml = newHtml;
|
||||||
|
};
|
||||||
|
|
||||||
|
UIFragment.prototype.handleEvent = function (e) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (e.type === 'stateChange') {
|
||||||
|
var fragmentChanges = e.patch.projectObjects(self.demandProj);
|
||||||
|
fragmentChanges[0].forEach(function (c) { self.updateContent(c.selector, c.html); });
|
||||||
|
fragmentChanges[1].forEach(function (c) {
|
||||||
|
if (c.selector === self.currentSelector && c.html === self.currentHtml) {
|
||||||
|
Dataspace.exit(); // trapexit will remove nodes
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var eventDemand = e.patch.projectObjects(self.eventDemandProj);
|
||||||
|
eventDemand[0].forEach(function (c) { self.updateEventListeners(c, true); })
|
||||||
|
eventDemand[1].forEach(function (c) { self.updateEventListeners(c, false); })
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
UIFragment.prototype.eventClosureKey = function (c) {
|
||||||
|
return c.selector + ' :: ' + c.eventType;
|
||||||
|
};
|
||||||
|
|
||||||
|
UIFragment.prototype.getEventClosure = function (c) {
|
||||||
|
var self = this;
|
||||||
|
var key = self.eventClosureKey(c);
|
||||||
|
if (!(key in self.eventClosures)) {
|
||||||
|
self.eventClosures[key] = Dataspace.wrap(function (e) { return self.handleDomEvent(c, e); });
|
||||||
|
}
|
||||||
|
return self.eventClosures[key];
|
||||||
|
};
|
||||||
|
|
||||||
|
UIFragment.prototype.clearEventClosure = function (c) {
|
||||||
|
delete this.eventClosures[this.eventClosureKey(c)];
|
||||||
|
};
|
||||||
|
|
||||||
|
UIFragment.prototype.updateEventListeners = function (c, install) {
|
||||||
|
var self = this;
|
||||||
|
var handlerClosure = self.getEventClosure(c);
|
||||||
|
|
||||||
|
self.currentAnchorNodes.forEach(function (anchorNode) {
|
||||||
|
var uiNode = findInsertionPoint(anchorNode, self.fragmentId);
|
||||||
|
while (uiNode && getBrand(uiNode, self.fragmentId)) {
|
||||||
|
var nodes = uiNode.querySelectorAll(c.selector);
|
||||||
|
for (var i = 0; i < nodes.length; i++) {
|
||||||
|
var n = nodes[i];
|
||||||
|
// addEventListener and removeEventListener are apparently idempotent.
|
||||||
|
if (install) {
|
||||||
|
n.addEventListener(cleanEventType(c.eventType), handlerClosure);
|
||||||
|
} else {
|
||||||
|
n.removeEventListener(cleanEventType(c.eventType), handlerClosure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uiNode = uiNode.nextSibling;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!install) {
|
||||||
|
self.clearEventClosure(c);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
UIFragment.prototype.handleDomEvent = function (c, e) {
|
||||||
|
Dataspace.send(uiEvent(this.fragmentId, c.selector, c.eventType, e));
|
||||||
|
return dealWithPreventDefault(this.eventType, event);
|
||||||
|
};
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function escapeDataAttributeName(s) {
|
||||||
|
// Per https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset,
|
||||||
|
// the rules seem to be:
|
||||||
|
//
|
||||||
|
// 1. Must not contain a dash immediately followed by an ASCII lowercase letter
|
||||||
|
// 2. Must not contain anything other than:
|
||||||
|
// - letters
|
||||||
|
// - numbers
|
||||||
|
// - dash, dot, colon, underscore
|
||||||
|
//
|
||||||
|
// I'm not implementing this exactly - I'm escaping some things that
|
||||||
|
// don't absolutely need escaping, because it's simpler and I don't
|
||||||
|
// yet need to undo this transformation.
|
||||||
|
|
||||||
|
var result = '';
|
||||||
|
for (var i = 0; i < s.length; i++) {
|
||||||
|
var c = s[i];
|
||||||
|
if (c >= 'a' && c <= 'z') { result = result + c; continue; }
|
||||||
|
if (c >= 'A' && c <= 'Z') { result = result + c; continue; }
|
||||||
|
if (c >= '0' && c <= '9') { result = result + c; continue; }
|
||||||
|
if (c === '.' || c === ':') { result = result + c; continue; }
|
||||||
|
|
||||||
|
c = c.charCodeAt(0);
|
||||||
|
result = result + '_' + c + '_';
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dealWithPreventDefault(eventType, event) {
|
||||||
|
var shouldPreventDefault = eventType.charAt(0) !== '+';
|
||||||
|
if (shouldPreventDefault) event.preventDefault();
|
||||||
|
return !shouldPreventDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanEventType(eventType) {
|
||||||
|
return (eventType.charAt(0) === '+') ? eventType.slice(1) : eventType;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function Anchor(explicitFragmentId) {
|
||||||
|
this.fragmentId =
|
||||||
|
(typeof explicitFragmentId === 'undefined') ? newFragmentId() : explicitFragmentId;
|
||||||
|
this.htmlPattern = uiFragment(this.fragmentId, __, __);
|
||||||
|
this.eventPattern = uiEvent(this.fragmentId, __, __, __);
|
||||||
|
}
|
||||||
|
|
||||||
|
Anchor.prototype.context = function (contextId) {
|
||||||
|
return new Anchor(this.fragmentId + '_' + escapeDataAttributeName(contextId));
|
||||||
|
};
|
||||||
|
|
||||||
|
Anchor.prototype.html = function (selector, html) {
|
||||||
|
return uiFragment(this.fragmentId, selector, html);
|
||||||
|
};
|
||||||
|
|
||||||
|
Anchor.prototype.event = function (selector, eventType, event) {
|
||||||
|
return uiEvent(this.fragmentId, selector, eventType, event);
|
||||||
|
};
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
module.exports.newFragmentId = newFragmentId;
|
||||||
|
module.exports.spawnUIDriver = spawnUIDriver;
|
||||||
|
module.exports.Anchor = Anchor;
|
||||||
|
module.exports.globalEvent = globalEvent;
|
||||||
|
module.exports.uiEvent = uiEvent;
|
||||||
|
module.exports.uiFragment = uiFragment;
|
||||||
|
module.exports.uiFragmentExists = uiFragmentExists;
|
Loading…
Reference in New Issue