Update js examples.
This commit is contained in:
parent
a1189f3ed8
commit
c67b57a445
|
@ -0,0 +1 @@
|
|||
Directory for build products, checked in to the repo for ease-of-use.
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -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();
|
||||
});
|
|
@ -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>
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
|
@ -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>
|
|
@ -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();
|
||||
});
|
|
@ -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
|
|
@ -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>
|
|
@ -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();
|
||||
});
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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();
|
||||
});
|
|
@ -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>
|
|
@ -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, '&');
|
||||
text = text.replace(/</g, '<');
|
||||
text = text.replace(/>/g, '>');
|
||||
text = text.replace(/ /g, ' ');
|
||||
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));
|
||||
};
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
#fieldContents {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.cursor {
|
||||
border-left: solid red 1px;
|
||||
border-right: solid red 1px;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
background-color: yellow;
|
||||
}
|
|
@ -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>
|
|
@ -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, '&');
|
||||
text = text.replace(/</g, '<');
|
||||
text = text.replace(/>/g, '>');
|
||||
text = text.replace(/ /g, ' ');
|
||||
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();
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
#fieldContents {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.cursor {
|
||||
border-left: solid red 1px;
|
||||
border-right: solid red 1px;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
background-color: yellow;
|
||||
}
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue