Update js examples.

This commit is contained in:
Tony Garnock-Jones 2016-04-06 18:22:30 +02:00
parent a1189f3ed8
commit c67b57a445
24 changed files with 39921 additions and 0 deletions

1
dist/README.md vendored Normal file
View File

@ -0,0 +1 @@
Directory for build products, checked in to the repo for ease-of-use.

30392
dist/syndicate.js vendored Normal file

File diff suppressed because one or more lines are too long

17
dist/syndicate.min.js vendored Normal file

File diff suppressed because one or more lines are too long

8681
dist/syndicatecompiler.js vendored Normal file

File diff suppressed because one or more lines are too long

8
dist/syndicatecompiler.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,45 @@
"use strict";
var DOM = (function() {
var $SyndicateMeta$ = {
label: "DOM",
arguments: ["containerSelector","fragmentClass","spec"]
};
return function DOM(containerSelector, fragmentClass, spec) {
return {
"containerSelector": containerSelector,
"fragmentClass": fragmentClass,
"spec": spec,
"$SyndicateMeta$": $SyndicateMeta$
};
};
})();
var jQuery = (function() {
var $SyndicateMeta$ = {
label: "jQuery",
arguments: ["selector","eventType","event"]
};
return function jQuery(selector, eventType, event) {
return {
"selector": selector,
"eventType": eventType,
"event": event,
"$SyndicateMeta$": $SyndicateMeta$
};
};
})();
$(document).ready(function() {
new Syndicate.Ground(function () {
Syndicate.DOM.spawnDOMDriver();
Syndicate.JQuery.spawnJQueryDriver();
Syndicate.Actor.spawnActor(new Object(), function() {
this.counter = 0;
Syndicate.Actor.createFacet()
.addAssertion((function() { var _ = Syndicate.__; return Syndicate.Patch.assert(DOM('#button-label','',Syndicate.seal(this.counter)), 0); }))
.onEvent(false, "message", (function() { var _ = Syndicate.__; return Syndicate.Patch.sub(jQuery('#counter','click',_), 0); }), (function() { var _ = Syndicate.__; return { assertion: jQuery('#counter','click',_), metalevel: 0 }; }), (function() {
this.counter++;
})).completeBuild();
});
}).startStepping();
});

View File

@ -0,0 +1,22 @@
<!doctype html>
<html>
<head>
<title>Syndicate: Button Example</title>
<meta charset="utf-8">
<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/syndicate.js"></script>
<script type="text/syndicate-js" src="index.js"></script>
</head>
<body>
<h1>Button Example</h1>
<button id="counter"><span id="button-label"></span></button>
<p>
Source code: <a href="index.js">index.js</a>
</p>
<p>
Expanded source code: <a href="index.expanded.js">index.expanded.js</a>
</p>
</body>
</html>

19
examples/button/index.js Normal file
View File

@ -0,0 +1,19 @@
assertion type DOM(containerSelector, fragmentClass, spec);
assertion type jQuery(selector, eventType, event);
$(document).ready(function() {
ground network {
Syndicate.DOM.spawnDOMDriver();
Syndicate.JQuery.spawnJQueryDriver();
actor {
this.counter = 0;
forever {
assert DOM('#button-label', '', Syndicate.seal(this.counter));
on message jQuery('#counter', 'click', _) {
this.counter++;
}
}
}
}
});

16
examples/dom/index.html Normal file
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>

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

@ -0,0 +1,56 @@
var G;
$(document).ready(function () {
var Network = Syndicate.Network;
var sub = Syndicate.sub;
var assert = Syndicate.assert;
var retract = Syndicate.retract;
var seal = Syndicate.seal;
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",
seal(["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",
seal(["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();
});

78
examples/index.md Normal file
View File

@ -0,0 +1,78 @@
---
---
# Syndicate/js Examples
This page describes some of the example programs that are part of the
JavaScript implementation of Syndicate.
## Clickable Button
This is a simple clickable button; each time the button is clicked,
the number on the face of the button is incremented.
The actor maintaining the counter also maintains the button's label
and listens to click events. It uses the Syndicate/js DOM driver to
publish the button's label text based on its internal state, and the
Syndicate/js jQuery driver to subscribe to button click events.
- [DEMO](button/)
- [Source code](button/index.js) using the Syndicate/js DSL
## DOM example
This example demonstrates two actors, each using the Syndicate/js DOM
driver to display user interface, and the jQuery driver to receive
events from it. The first actor presents a button to the user, which
when clicked sends a message to the other actor. The second actor
receives messages from the first, updates its internal state, and
reflects its new internal state in its visible UI.
- [DEMO](dom/)
- [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
This is a simple text entry GUI control, following a design of
[Hesam Samimi](http://www.hesam.us/cs/cooplangs/textfield.pdf).
Samimi's design proceeds in two stages. In the first, it calls for two
components: one representing the *model* of a text field, including
its contents and cursor position, and one acting as the *view*,
responsible for drawing the widget and interpreting keystrokes. In the
second stage, a *search* component is added, responsible for searching
the current content of the model for a pattern and collaborating with
the view to highlight the results.
This Syndicate solution naturally has an actor for each of the three
components. The model actor maintains the current contents and cursor
position as assertions in the shared dataspace. The view actor
observes these assertions and, when they change, updates the display.
It also subscribes to keystroke events and translates them into
messages understandable to the model actor. The addition of the search
actor necessitates no changes to the model actor. The search actor
observes the assertion of the current content of the field in the same
way the view actor does. If it finds a matching substring, it asserts
this fact. The view actor must observe these assertions and highlight
any corresponding portion of text.
There are two implementations, one using Syndicate events and actions
directly, and one using the high-level Syndicate DSL.
- High-level DSL implementation
- [DEMO](textfield-dsl/)
- [Source code](textfield-dsl/index.js) using the Syndicate/js DSL
- Low-level implementation
- [DEMO](textfield/)
- [Source code](textfield/index.js) in plain JavaScript

View File

@ -0,0 +1,16 @@
<!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>

31
examples/jquery/index.js vendored Normal file
View File

@ -0,0 +1,31 @@
"use strict";
var G;
$(document).ready(function () {
var Network = Syndicate.Network;
var sub = Syndicate.sub;
var __ = Syndicate.__;
var _$ = Syndicate._$;
G = new Syndicate.Ground(function () {
console.log('starting ground boot');
Syndicate.JQuery.spawnJQueryDriver();
Network.spawn({
boot: function () {
return sub(['jQuery', '#clicker', 'click', __]);
},
handleEvent: function (e) {
if (e.type === 'message' && e.message[0] === 'jQuery' && e.message[1] === '#clicker') {
var r = $('#result');
r.html(Number(r.html()) + 1);
}
}
});
});
G.network.onStateChange = function (mux, patch) {
$("#spy-holder").text(Syndicate.prettyTrie(mux.routingTable));
};
G.startStepping();
});

View File

@ -0,0 +1,14 @@
<!doctype html>
<html>
<head>
<title>Syndicate: Smoketest with DSL</title>
<meta charset="utf-8">
<script src="../../third-party/jquery-2.2.0.min.js"></script>
<script src="../../dist/syndicatecompiler.js"></script>
<script src="../../dist/syndicate.js"></script>
<script type="text/syndicate-js" src="index.js"></script>
</head>
<body>
<h1>Smoketest</h1>
</body>
</html>

View File

@ -0,0 +1,31 @@
assertion type beep(counter);
ground network {
console.log('starting ground boot');
actor {
until {
case asserted Syndicate.observe(beep(_)) {
var counter = 0;
state {
init {
:: beep(counter++);
}
on message beep(_) {
:: beep(counter++);
}
} until {
case (counter >= 10);
}
}
}
}
actor {
forever {
on message $m {
console.log("Got message:", m);
}
}
}
}

View File

@ -0,0 +1,13 @@
<!doctype html>
<html>
<head>
<title>Syndicate: Smoketest</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>Smoketest</h1>
</body>
</html>

View File

@ -0,0 +1,33 @@
"use strict";
var G;
$(document).ready(function () {
var Network = Syndicate.Network;
var sub = Syndicate.sub;
var __ = Syndicate.__;
var _$ = Syndicate._$;
G = new Syndicate.Ground(function () {
console.log('starting ground boot');
Network.spawn({
counter: 0,
boot: function () {},
handleEvent: function (e) {},
step: function () {
Network.send(["beep", this.counter++]);
return this.counter <= 10;
}
});
Network.spawn({
boot: function () { return sub(["beep", __]); },
handleEvent: function (e) {
if (e.type === 'message') {
console.log("beep!", e.message[1]);
}
}
});
});
G.startStepping();
});

View File

@ -0,0 +1,23 @@
<!doctype html>
<html>
<head>
<title>Syndicate: Textfield Example (DSL variation)</title>
<meta charset="utf-8">
<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/syndicate.js"></script>
<script type="text/syndicate-js" src="index.js"></script>
</head>
<body>
<h1>Textfield Example (DSL variation)</h1>
<p>
After <a href="http://www.hesam.us/cooplangs/textfield.pdf">Hesam
Samimi's paper</a>.
</p>
<p id="inputRow" tabindex="0">Field contents: <span id="fieldContents"></span></p>
<h2>Search</h2>
<input type="text" id="searchBox" value="iti">
<pre id="spy-holder"></pre>
</body>
</html>

View File

@ -0,0 +1,169 @@
///////////////////////////////////////////////////////////////////////////
// GUI
assertion type jQuery(selector, eventType, event);
assertion type fieldCommand(detail);
assertion type fieldContents(text, pos);
assertion type highlight(state);
function escapeText(text) {
text = text.replace(/&/g, '&amp;');
text = text.replace(/</g, '&lt;');
text = text.replace(/>/g, '&gt;');
text = text.replace(/ /g, '&nbsp;');
return text;
}
function piece(text, pos, lo, hi, cls) {
return "<span class='"+cls+"'>"+
((pos >= lo && pos < hi)
? (escapeText(text.substring(lo, pos)) +
"<span class='cursor'></span>" +
escapeText(text.substring(pos, hi)))
: escapeText(text.substring(lo, hi)))
+ "</span>";
}
function spawnGui() {
actor {
this.text = '';
this.pos = 0;
this.highlightState = false;
this.updateDisplay = function () {
var text = this.text;
var pos = this.pos;
var highlight = this.highlightState;
var hLeft = highlight ? highlight.get(0) : 0;
var hRight = highlight ? highlight.get(1) : 0;
$("#fieldContents")[0].innerHTML = highlight
? piece(text, pos, 0, hLeft, "normal") +
piece(text, pos, hLeft, hRight, "highlight") +
piece(text, pos, hRight, text.length + 1, "normal")
: piece(text, pos, 0, text.length + 1, "normal");
};
forever {
on message jQuery("#inputRow", "+keypress", $event) {
var keycode = event.keyCode;
var character = String.fromCharCode(event.charCode);
if (keycode === 37 /* left */) {
:: fieldCommand("cursorLeft");
} else if (keycode === 39 /* right */) {
:: fieldCommand("cursorRight");
} else if (keycode === 9 /* tab */) {
// ignore
} else if (keycode === 8 /* backspace */) {
:: fieldCommand("backspace");
} else if (character) {
:: fieldCommand(["insert", character]);
}
}
on asserted fieldContents($text, $pos) {
this.text = text;
this.pos = pos;
this.updateDisplay();
}
on asserted highlight($state) {
this.highlightState = state;
this.updateDisplay();
}
}
}
}
///////////////////////////////////////////////////////////////////////////
// Textfield Model
function spawnModel() {
actor {
this.fieldContents = "initial";
this.cursorPos = this.fieldContents.length; /* positions address gaps between characters */
forever {
assert fieldContents(this.fieldContents, this.cursorPos);
on message fieldCommand("cursorLeft") {
this.cursorPos--;
if (this.cursorPos < 0)
this.cursorPos = 0;
}
on message fieldCommand("cursorRight") {
this.cursorPos++;
if (this.cursorPos > this.fieldContents.length)
this.cursorPos = this.fieldContents.length;
}
on message fieldCommand("backspace") {
if (this.cursorPos > 0) {
this.fieldContents =
this.fieldContents.substring(0, this.cursorPos - 1) +
this.fieldContents.substring(this.cursorPos);
this.cursorPos--;
}
}
on message fieldCommand(["insert", $newText]) {
this.fieldContents =
this.fieldContents.substring(0, this.cursorPos) +
newText +
this.fieldContents.substring(this.cursorPos);
this.cursorPos += newText.length;
}
}
}
}
///////////////////////////////////////////////////////////////////////////
// Search engine
function spawnSearch() {
actor {
this.fieldContents = "";
this.highlight = false;
this.search = function () {
var searchtext = $("#searchBox")[0].value;
if (searchtext) {
var pos = this.fieldContents.indexOf(searchtext);
this.highlight = (pos !== -1) && [pos, pos + searchtext.length];
} else {
this.highlight = false;
}
};
forever {
assert highlight(this.highlight);
on message jQuery("#searchBox", "input", $event) {
this.search();
}
on asserted fieldContents($text, _) {
this.fieldContents = text;
this.search();
}
}
}
}
///////////////////////////////////////////////////////////////////////////
// Main
$(document).ready(function () {
ground network G {
Syndicate.JQuery.spawnJQueryDriver();
Syndicate.DOM.spawnDOMDriver();
spawnGui();
spawnModel();
spawnSearch();
}
G.network.onStateChange = function (mux, patch) {
$("#spy-holder").text(Syndicate.prettyTrie(mux.routingTable));
};
});

View File

@ -0,0 +1,12 @@
#fieldContents {
font-family: monospace;
}
.cursor {
border-left: solid red 1px;
border-right: solid red 1px;
}
.highlight {
background-color: yellow;
}

View File

@ -0,0 +1,22 @@
<!doctype html>
<html>
<head>
<title>Syndicate: Textfield Example</title>
<meta charset="utf-8">
<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="index.js"></script>
</head>
<body>
<h1>Textfield Example</h1>
<p>
After <a href="http://www.hesam.us/cooplangs/textfield.pdf">Hesam
Samimi's paper</a>.
</p>
<p id="inputRow" tabindex="0">Field contents: <span id="fieldContents"></span></p>
<h2>Search</h2>
<input type="text" id="searchBox" value="iti">
<pre id="spy-holder"></pre>
</body>
</html>

206
examples/textfield/index.js Normal file
View File

@ -0,0 +1,206 @@
///////////////////////////////////////////////////////////////////////////
// GUI
var Network = Syndicate.Network;
var Route = Syndicate.Route;
var Patch = Syndicate.Patch;
var __ = Syndicate.__;
var _$ = Syndicate._$;
function escapeText(text) {
text = text.replace(/&/g, '&amp;');
text = text.replace(/</g, '&lt;');
text = text.replace(/>/g, '&gt;');
text = text.replace(/ /g, '&nbsp;');
return text;
}
function piece(text, pos, lo, hi, cls) {
return "<span class='"+cls+"'>"+
((pos >= lo && pos < hi)
? (escapeText(text.substring(lo, pos)) +
"<span class='cursor'></span>" +
escapeText(text.substring(pos, hi)))
: escapeText(text.substring(lo, hi)))
+ "</span>";
}
function spawnGui() {
Network.spawn({
field: { text: '', pos: 0 },
highlight: { state: false },
boot: function () {
return Patch.sub(["jQuery", "#inputRow", "+keypress", __])
.andThen(Patch.sub(["fieldContents", __, __]))
.andThen(Patch.sub(["highlight", __]));
},
fieldContentsProjection: Route.compileProjection(["fieldContents", _$("text"), _$("pos")]),
highlightProjection: Route.compileProjection(["highlight", _$("state")]),
handleEvent: function (e) {
var self = this;
switch (e.type) {
case "message":
var event = e.message[3];
var keycode = event.keyCode;
var character = String.fromCharCode(event.charCode);
if (keycode === 37 /* left */) {
Network.send(["fieldCommand", "cursorLeft"]);
} else if (keycode === 39 /* right */) {
Network.send(["fieldCommand", "cursorRight"]);
} else if (keycode === 9 /* tab */) {
// ignore
} else if (keycode === 8 /* backspace */) {
Network.send(["fieldCommand", "backspace"]);
} else if (character) {
Network.send(["fieldCommand", ["insert", character]]);
}
break;
case "stateChange":
Route.projectObjects(e.patch.added, this.fieldContentsProjection).forEach(function (c) {
self.field = c;
});
Route.projectObjects(e.patch.added, this.highlightProjection).forEach(function (c) {
self.highlight = c;
});
this.updateDisplay();
break;
}
},
updateDisplay: function () {
var text = this.field ? this.field.text : "";
var pos = this.field ? this.field.pos : 0;
var highlight = this.highlight ? this.highlight.state : false;
var hLeft = highlight ? highlight.get(0) : 0;
var hRight = highlight ? highlight.get(1) : 0;
$("#fieldContents")[0].innerHTML = highlight
? piece(text, pos, 0, hLeft, "normal") +
piece(text, pos, hLeft, hRight, "highlight") +
piece(text, pos, hRight, text.length + 1, "normal")
: piece(text, pos, 0, text.length + 1, "normal");
}
});
}
///////////////////////////////////////////////////////////////////////////
// Textfield Model
function spawnModel() {
var initialContents = "initial";
Network.spawn({
fieldContents: initialContents,
cursorPos: initialContents.length, /* positions address gaps between characters */
boot: function () {
this.publishState();
return Patch.sub(["fieldCommand", __]);
},
handleEvent: function (e) {
if (e.type === "message" && e.message[0] === "fieldCommand") {
var command = e.message[1];
if (command === "cursorLeft") {
this.cursorPos--;
if (this.cursorPos < 0)
this.cursorPos = 0;
} else if (command === "cursorRight") {
this.cursorPos++;
if (this.cursorPos > this.fieldContents.length)
this.cursorPos = this.fieldContents.length;
} else if (command === "backspace" && this.cursorPos > 0) {
this.fieldContents =
this.fieldContents.substring(0, this.cursorPos - 1) +
this.fieldContents.substring(this.cursorPos);
this.cursorPos--;
} else if (command.constructor === Array && command[0] === "insert") {
var newText = command[1];
this.fieldContents =
this.fieldContents.substring(0, this.cursorPos) +
newText +
this.fieldContents.substring(this.cursorPos);
this.cursorPos += newText.length;
}
this.publishState();
}
},
publishState: function () {
Network.stateChange(
Patch.retract(["fieldContents", __, __])
.andThen(Patch.assert(["fieldContents", this.fieldContents, this.cursorPos])));
}
});
}
///////////////////////////////////////////////////////////////////////////
// Search engine
function spawnSearch() {
Network.spawn({
fieldContents: "",
highlight: false,
boot: function () {
this.publishState();
return Patch.sub(["jQuery", "#searchBox", "input", __])
.andThen(Patch.sub(["fieldContents", __, __]));
},
fieldContentsProjection: Route.compileProjection(["fieldContents", _$("text"), _$("pos")]),
handleEvent: function (e) {
var self = this;
if (e.type === "message" && e.message[0] === "jQuery") {
this.search();
}
if (e.type === "stateChange") {
Route.projectObjects(e.patch.added, this.fieldContentsProjection).forEach(function (c) {
self.fieldContents = c.text;
});
this.search();
}
},
publishState: function () {
Network.stateChange(
Patch.retract(["highlight", __])
.andThen(Patch.assert(["highlight", this.highlight])));
},
search: function () {
var searchtext = $("#searchBox")[0].value;
var oldHighlight = this.highlight;
if (searchtext) {
var pos = this.fieldContents.indexOf(searchtext);
this.highlight = (pos !== -1) && [pos, pos + searchtext.length];
} else {
this.highlight = false;
}
if (JSON.stringify(oldHighlight) !== JSON.stringify(this.highlight)) {
this.publishState();
}
}
});
}
///////////////////////////////////////////////////////////////////////////
// Main
var G;
$(document).ready(function () {
G = new Syndicate.Ground(function () {
Syndicate.JQuery.spawnJQueryDriver();
Syndicate.DOM.spawnDOMDriver();
spawnGui();
spawnModel();
spawnSearch();
});
G.network.onStateChange = function (mux, patch) {
$("#spy-holder").text(Syndicate.prettyTrie(mux.routingTable));
};
G.startStepping();
});

View File

@ -0,0 +1,12 @@
#fieldContents {
font-family: monospace;
}
.cursor {
border-left: solid red 1px;
border-right: solid red 1px;
}
.highlight {
background-color: yellow;
}

4
third-party/jquery-2.2.0.min.js vendored Normal file

File diff suppressed because one or more lines are too long