Current section

Files

Jump to
zotonic_mod_base priv lib-src cotonic index.html
Raw

priv/lib-src/cotonic/index.html

<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="HandheldFriendly" content="true"/>
<link rel="apple-touch-icon" sizes="180x180" href="./doc/favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="./doc/favicon/32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="./doc/favicon/16x16.png">
<link rel="manifest" href="./doc/favicon/site.webmanifest">
<title>Cotonic</title>
<style>
body {
background: #f4f4f4;
color: #000;
font-family: sans-serif;
}
section#sidebar {
background: #fff;
position: fixed;
top: 0; left: 0; bottom: 0;
width: 200px;
overflow-y: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
padding: 15px 0 30px 30px;
border-right: 1px solid #bbb;
box-shadow: 0 0 20px #ccc;
}
section#documentation {
width: 550px;
margin: 40px 0 50px 260px;
}
h1.logo {
color: #0778B0;
font-size: 48px;
padding-top: 0px;
}
a.toc_title, a.toc_title:visited {
display: block;
color: #0778B0;
font-weight: bold;
margin-top: 15px;
}
ul.toc_section {
font-size: small;
line-height: 100%;
margin: 5px 0 0 0;
padding-left: 0px;
list-style-type: none;
}
ul.toc_section ul {
padding-top: 0.66ex;
padding-left: 2em;
list-style-type: none;
}
.toc_section li {
cursor: pointer;
margin: 0 0 3px 0;
}
.toc_section li a {
text-decoration: none;
color: black;
}
.toc_section li a:hover {
text-decoration: underline;
}
h1, h2, h3, h4, h5, h6 {
padding-top: 20px;
}
.header {
color: #0778B0;
font-weight: bolder;
line-height: 200%;
}
code {
margin-left: 20px;
}
pre {
padding: 2px 0 2px 15px;
border-left: 5px solid #bbb;
margin: 0px 0 30px;
}
.note {
border: 1px solid #ee8;
background-color: #ffc;
padding: 0 10px;
color: #333;
}
@media only screen and (-webkit-min-device-pixel-ratio: 1.5) and (max-width: 640px),
only screen and (-o-min-device-pixel-ratio: 3/2) and (max-width: 640px),
only screen and (min-device-pixel-ratio: 1.5) and (max-width: 640px) {
section#sidebar {
-webkit-overflow-scrolling: initial;
position: relative;
width: 90%;
height: 120px;
left: 0;
top: -7px;
padding: 10px 0 10px 30px;
border: 0;
}
section#documentation {
margin: 0;
width: 100%;
}
pre {
overflow: auto;
}
}
</style>
</head>
<body>
<section id="sidebar">
<div>
<a class="toc_title" href="#">
Cotonic <span class="version">(1.0.6)</span>
</a>
<ul class="toc_section">
<li>&raquo; <a href="https://github.com/cotonic/cotonic">Github Repository</a></li>
<li>&raquo; <a href="#">Introduction</a></li>
<li>&raquo; <a href="#workers">Workers</a></li>
</ul>
<a class="toc_title" href="#functions">
Page Functions
</a>
<ul class="toc_section">
<li> - <a href="#cotonic.spawn">spawn</a>
<li> - <a href="#cotonic.spawn_named">spawn_named</a>
<li> - <a href="#cotonic.exit">exit</a>
<li> - <a href="#cotonic.send">send</a>
<li> - <a href="#cotonic.receive">receive</a>
<li> - <a href="#cotonic.config"><em>Configuration</em></a>
</ul>
<!-- Broker -->
<a href="#cotonic.broker" class="toc_title">
broker
</a>
<ul class="toc_section">
<li> - <a href="#cotonic.broker.find_subscriptions_below">find_subscriptions_below</a>
<li> - <a href="#cotonic.broker.match">match</a>
<li> - <a href="#cotonic.broker.publish">publish</a>
<li> - <a href="#cotonic.broker.subscribe">subscribe</a>
<li> - <a href="#cotonic.broker.unsubscribe">unsubscribe</a>
<li> - <a href="#cotonic.broker.call">call</a>
</ul>
<!-- MQTT -->
<a href="#cotonic.mqtt" class="toc_title">
mqtt
</a>
<ul class="toc_section">
<li> - <a href="#cotonic.mqtt.matches">matches</a>
<li> - <a href="#cotonic.mqtt.fill">fill</a>
<li> - <a href="#cotonic.mqtt.extract">extract</a>
<li> - <a href="#cotonic.mqtt.exec">exec</a>
<li> - <a href="#cotonic.mqtt.remove_named_wildcards">remove_named_wildcards</a>
</ul>
<!-- MQTT Bridge -->
<a href="#cotonic.mqtt_bridge" class="toc_title">
mqtt_bridge
</a>
<ul class="toc_section">
<li> - <a href="#cotonic.mqtt_bridge.newBridge">newBridge</a>
<li> - <a href="#cotonic.mqtt_bridge.newBridge">findBridge</a>
<li> - <a href="#cotonic.mqtt_bridge.deleteBridge">deleteBridge</a>
</ul>
<!-- UI -->
<a href="#cotonic.ui" class="toc_title">
ui
</a>
<ul class="toc_section">
<li> - <a href="#cotonic.ui.insert">insert</a>
<li> - <a href="#cotonic.ui.get">get</a>
<li> - <a href="#cotonic.ui.update">update</a>
<li> - <a href="#cotonic.ui.remove">remove</a>
<li> - <a href="#cotonic.ui.render">render</a>
<li> - <a href="#cotonic.ui.renderId">renderId</a>
<li> - <a href="#cotonic.ui.updateStateData">updateStateData</a>
<li> - <a href="#cotonic.ui.updateStateClass">updateStateClass</a>
<li> - <a href="#cotonic.ui.on">on</a>
</ul>
<!-- Tokenizer -->
<a class="toc_title" href="#cotonic.tokenizer">
tokenizer
</a>
<ul class="toc_section">
<li> - <a href="#cotonic.tokenizer.tokens">tokens</a>
<li> - <a href="#cotonic.tokenizer.charref">charref</a>
</ul>
<!-- Worker -->
<a class="toc_title" href="#worker.functions">
Worker Functions
</a>
<ul class="toc_section">
<li> - <a href="#worker.on_init">on_init</a>
<li> - <a href="#worker.connect">connect</a>
<li> - <a href="#worker.disconnect">disconnect</a>
<li> - <a href="#worker.is_connected">is_connected</a>
<li> - <a href="#worker.whenDependencyProvided">whenDependencyProvided</a>
<li> - <a href="#worker.whenDependenciesProvided">whenDependenciesProvided</a>
<li> - <a href="#worker.provides">provides</a>
<li> - <a href="#worker.subscribe">subscribe</a>
<li> - <a href="#worker.unsubscribe">unsubscribe</a>
<li> - <a href="#worker.publish">publish</a>
<li> - <a href="#worker.call">call</a>
</ul>
<a class="toc_title" href="#models">
Models
</a>
<a class="toc_title" href="#model.window">
model/window
</a>
<ul class="toc_section">
<li> - <a href="#model.window.post.close">post/open</a>
<li> - <a href="#model.window.post.close">post/close</a>
</ul>
<a class="toc_title" href="#model.document">
model/document
</a>
<ul class="toc_section">
<li> - <a href="#model.window.get.all">get/all</a>
<li> - <a href="#model.window.get.intl">get/intl</a>
</ul>
<a class="toc_title" href="#model.location">
model/location
</a>
<ul class="toc_section">
<li> - <a href="#model.location.get.href">get/href</a>
<li> - <a href="#model.location.get.protocol">get/protocol</a>
<li> - <a href="#model.location.get.host">get/host</a>
<li> - <a href="#model.location.get.hostname">get/hostname</a>
<li> - <a href="#model.location.get.hostname">get/origin</a>
<li> - <a href="#model.location.get.port">get/port</a>
<li> - <a href="#model.location.event.search">event/search</a>
<li> - <a href="#model.location.event.pathname">event/pathname</a>
<li> - <a href="#model.location.event.hash">event/hash</a>
<li> - <a href="#model.location.event.ping">event/ping</a>
<li> - <a href="#model.location.config"><em>Configuration</em></a>
</ul>
<a class="toc_title" href="#model.ui">
model/ui
</a>
<ul class="toc_section">
<li> - <a href="#model.ui.insert.key">insert/+key</a>
<li> - <a href="#model.ui.update.key">update/+key</a>
<li> - <a href="#model.ui.delete.key">delete/+key</a>
<li> - <a href="#model.ui.get.key">get/+key</a>
<li> - <a href="#model.ui.render">render</a>
<li> - <a href="#model.ui.render.key">render/+key</a>
<li> - <a href="#model.ui.render-template.key">render-template/+key</a>
<li> - <a href="#model.ui.event.recent-activity">event/recent-activity</a>
<li> - <a href="#model.ui.event.dom-updated">event/dom-updated/+key</a>
<li> - <a href="#model.ui.event.ui-status">event/ui-status</a>
<li> - <a href="#model.ui.event.new-shadow-root.key">event/new-shadow-root/+key</a>
</ul>
<a class="toc_title" href="#model.autofocus">
model/autofocus
</a>
<ul class="toc_section">
<li> - <a href="#model.autofocus.event.focus.key">event/focus/+key</a>
</ul>
<a class="toc_title" href="#model.serviceWorker">
model/serviceWorker
</a>
<ul class="toc_section">
<li> - <a href="#model.serviceWorker.post.broadcast.channel">post/broadcast/+channel</a>
<li> - <a href="#model.serviceWorker.event.broadcast.channel">event/broadcast/+channel</a>
<li> - <a href="#model.serviceWorker.event.ping">event/ping</a>
<li> - <a href="#model.serviceWorker.config"><em>Configuration</em></a>
</ul>
<a class="toc_title" href="#model.localStorage">
model/localStorage
</a>
<ul class="toc_section">
<li> - <a href="#model.localStorage.get.key">get/+key</a>
<li> - <a href="#model.localStorage.post.key">post/+key</a>
<li> - <a href="#model.localStorage.delete.key">delete/+key</a>
<li> - <a href="#model.localStorage.event.key">event/+key</a>
<li> - <a href="#model.localStorage.event.ping">event/ping</a>
</ul>
<a class="toc_title" href="#model.sessionStorage">
model/sessionStorage
</a>
<ul class="toc_section">
<li> - <a href="#model.sessionStorage.get.key">get/+key</a>
<li> - <a href="#model.sessionStorage.get.key.subkey">get/+key/+subkey</a>
<li> - <a href="#model.sessionStorage.post.key">post/+key</a>
<li> - <a href="#model.sessionStorage.post.key.subkey">post/+key/+subkey</a>
<li> - <a href="#model.sessionStorage.delete.key">delete/+key</a>
<li> - <a href="#model.sessionStorage.delete.key">delete/+key/+subkey</a>
<li> - <a href="#model.sessionStorage.event.key">event/+key</a>
<li> - <a href="#model.sessionStorage.event.key.subkey">event/+key/+subkey</a>
<li> - <a href="#model.sessionStorage.event.ping">event/ping</a>
</ul>
</div>
<div>
<a class="toc_title" href="#links">Links</a>
</div>
<div>
<a class="toc_title" href="#changelog">Change Log</a>
</div>
</section>
<section id="documentation">
<p id="introduction">
<h1 class="logo">COTONIC</h1>
</p>
<p>
<a href="https://github.com/cotonic/cotonic">Cotonic</a> is a Javascript
library which makes it possible to split the javascript code of your
page into truly isolated components. By doing this a crash in one component
can never affect another component.
</p>
<p>
Cotonic provides tools to make it possible for these components to cooperate
by providing an MQTT publish/subscribe bus. This makes it possible for
components to communicate via topics.
</p>
<p>
The project is hosted on <a href="https://github.com/cotonic/cotonic">Github</a>.
You can report bugs and discuss features on the
<a href="https://github.com/cotonic/cotonic/issues">issues page</a>.
<p>
<p>
Cotonic is an open-source component of <a href="http://zotonic.org">Zotonic</a>.
</p>
<h3>Installation</h3>
Place the <a href="https://cotonic.org/cotonic.js"><tt>cotonic.js</tt></a>,
<a href="https://cotonic.org/cotonic-worker.js"><tt>cotonic-worker.js</tt></a> and
<a href="https://cotonic.org/cotonic-service-worker.js"><tt>cotonic-service-worker.js</tt></a> scripts on a web-server.
(<em>Right-click and use "Safe as"), or use one of the <a href="#changelog">download links</a></em>.
<br> <br>
Add the following tag to the page:
<br> <br>
<pre>
&lt;script src="//path/to/cotonic.js"
data-base-worker-src="//path/to/cotonic-worker.js"&gt;&lt;/script&gt;</pre>
<h2 id="workers">Workers</h2>
<p>
Cotonic uses Web Workers which all run in separate calling context. This
means that they will not block the main user interface thread. They are
also truly isolated from each other. This means that a crash or another
kind of problem in worker A can't crash worker B.
</p>
<p>
You can run whatever code you like in workers, with some exceptions. You
can't access the DOM, and a lot of things from the window object. This also
makes them more secure because you don't have to worry that worker code
from an external resource can steal the a credit-card number entered
somewhere in the DOM tree.
</p>
<p>
Cotonic adds a MQTT like publish subscribe mechanism to the standard javascript
web worker api. This makes it easy for web workers to communicate with each other.
</p>
<pre>
// worker-a
"use strict";
self.subscribe("some/topic", function(message) {
self.publish("model/ui/update", "&lt;p&gt;Worker A got message&lt;/p&gt;")
});
self.publish("model/ui/insert", "&lt;p&gt;Worker A started&lt;/p&gt;");</pre>
<pre>
// worker-b
"use strict";
self.subscribe("some/topic", function(message) {
self.publish("model/ui/update", "&lt;p&gt;Worker B got message&lt;/p&gt;")
});
self.publish("model/ui/insert", "&lt;p&gt;Worker B started&lt;/p&gt;");</pre>
<pre>
let worker_a = cotonic.spawn("worker-a.js", [1, 2]);
let worker_a = cotonic.spawn("worker-b.js");
cotonic.broker.publish("some/topic", "hello workers!");
</pre>
<h2 id="functions">Page Functions</h2>
<p>
Cotonics main functions are available in the <tt>cotonic</tt> namespace on the main page.
The functions are mostly used to control and allow messaging between workers.
</p>
<!-- spawn -->
<p id="cotonic.spawn">
<strong class="header">spawn</strong> <code>cotonic.spawn(url, [args])</code>
<br>
Spawn a new worker. The code of the worker should be available at <tt>url</tt>. The optional
<tt>args</tt> parameter list will be passed to the worker and can be used to pass information from
the page to the worker. It can be picked up with the <a href="#worker.on_init"><tt>on_init</tt></a>
callback. The structured clone algorithm will be used to send the <tt>args</tt> to the worker. See
<a href="#worker.functions">worker functions</a> for more information on implementing a worker.
Returns the worker-id of the newly created worker process.
</p>
<pre>
cotonic.spawn("/js/example-worker.js");
=> 1
cotonic.spawn("/js/another-worker.js", ["Arg1", 1]);
=> 2</pre>
<!-- spawn_named -->
<p id="cotonic.spawn_named">
<strong class="header">spawn_named</strong> <code>cotonic.spawn_named(name, src_url, [base], [args])</code>
<br>
Spawn a new named worker. Named workers are unique. Use <tt>""</tt> or <tt>undefined</tt> to create a
nameless worker. Return the worker_id of the newly spawned
worker. If the worker was already running, the existing worker_id is returned.
</p>
<pre>
cotonic.spawn_named("example", "example-worker.js");
=> "worker"</pre>
<!-- exit -->
<p id="cotonic.exit">
<strong class="header">exit</strong> <code>cotonic.exit(nameOrwid)</code>
<br>
Exit terminates a worker which was previously spawned and has <tt>nameOrWid</tt> as
worker-id.
</p>
<pre>
const wid = cotonic.spawn("example.worker.js");
cotonic.exit(wid);
=> Worker <tt>wid</tt> is no longer running.</pre>
<!-- send -->
<p id="cotonic.send">
<strong class="header">send</strong> <code>cotonic.send(nameOrWid, message)</code>
<br>
Send message <tt>message</tt> to worker <tt>nameOrWid</tt>. The parameter <tt>nameOrWid</tt>
can either be the name of worker, or a worker id. The structured clone algorithm will
be used to copy the <tt>message</tt> before sending it to the worker.
</p>
<pre>
cotonic.send(worker_id1, "hello");</pre>
<!-- receive -->
<p id="cotonic.receive">
<strong class="header">receive</strong> <code>cotonic.receive(handler)</code>
<br>
Receive messages from workers. The handler should be a function which takes
two parameters, the message, and the worker_id.
</p>
<pre>
cotonic.receive(function(message, worker_id) {
console.log("Received", message, "from worker", worker_id);
});</pre>
<!-- configuration -->
<p id="cotonic.config">
<em class="header">Configuration</em>
<br>
The page function has one configuration option.
<dl>
<dt><tt>base_worker_src</tt></dt>
<dd>The url for the base worker runtime for cotonic. This runtime provides
the required communication primitives.</dd>
</dl>
</p>
<pre>
&lt;script&gt;
window.cotonic = window.cotonic || {};
window.cotonic.config = {
base_worker_src: "/base/worker-bundle.js"
};
&lt;/script&gt;
&lt;script src="cotonic.js"&gt;&lt;/script&gt;
</pre>
<!-- Broker -->
<h3 id="cotonic.broker">Broker</h3>
<p>
The broker module handles all local publish subscribe connections. The subscriptions
are stored in a trie datastructure allowing quick action. They are available in the
<tt>cotonic.broker</tt> namespace.
</p>
<!-- find_subscriptions_below -->
<p id="cotonic.broker.find_subscriptions_below">
<strong class="header">find_subscriptions_below</strong> <code>cotonic.broker.find_subscriptions_below(topic)</code>
<br>
Find all subscribers below a certain topic. Used by the bridge to collect all subscriptions
after a session restart. Returns a list with subscriptions.
</p>
<pre>
cotonic.broker.find_subscriptions_below("truck");
=> [
{type: "page", wid: 0, callback: function, sub: Object, topic: "truck/+/speed"}
{type: "page", wid: 0, callback: function, sub: Object, topic: "#"}]</pre>
<!-- match -->
<p id="cotonic.broker.match">
<strong class="header">match</strong> <code>cotonic.broker.match(topic)</code>
<br>
Collect all subscribers which match the topic. Returns a list with subscriptions.
</p>
<pre>
cotonic.broker.subscribe("truck/+/speed", function(msg) {
console.log("Some trucks speed", msg);
});
cotonic.broker.subscribe("truck/#", function(msg) {
console.log("Some info of a truck", msg);
});
cotonic.broker.subscribe("truck/02/speed", function(msg) {
console.log("Speed of truck 2", msg);
})
cotonic.broker.match("truck/01/speed");
=> [Object, Object] // Returns two subscriptions, truck/+/speed, and truck/#
cotonic.broker.match("truck/01/speed")[0]
=> {type: "page", wid: 0, callback: function, sub: Object, topic: "truck/+/speed"}
cotonic.broker.match("truck/02/speed");
=> [Object, Object, Object] // Returns all subscriptions
cotonic.broker.match("boat/02/speed");
=> [] // Has no subscribers</pre>
<!-- publish -->
<p id="cotonic.broker.publish">
<strong class="header">publish</strong> <code>cotonic.broker.publish(topic, payload, [options])</code>
<br>
Publish the message <tt>payload</tt> on a topic. The possible <tt>options</tt> are:
<dl>
<dt><tt>qos</tt></dt>
<dd>Quality of service. Can be 0, 1, or 2. 0 means at most once, 1 at least once, and 2 exactly once.</dd>
<dt><tt>retain</tt></dt>
<dd>When retain is true, the last message sent will be stored, and delivered immediately when
a new client subscribes to the topic.</dd>
<dt><dt>properties</dt></dt>
<dd>Extra properties which can be attached to the message</dd>
</dl>
</p>
<pre>
cotonic.broker.publish("truck/001/temperature", 88);
=> All subscribers receive the message 88 on the topic.
cotonic.broker.publish("truck/001/speed", 74, {retain: true});
=> All subscribers receive the message 74. New subscribers will immediately receive 74.
</pre>
<!-- subscribe -->
<p id="cotonic.broker.subscribe">
<strong class="header">subscribe</strong> <code>cotonic.broker.subscribe(topics, callback, [options])</code>
<br>
Subscribe to topics. Argument <tt>topics</tt> can either be a single topic, or a list of topics.
The function <tt>callback</tt> will be called when a message which matches
one of the topics is published. It is a function is called with two arguments, <tt>message</tt>
and <tt>info</tt>. Argument <tt>message</tt> is the received mqtt message, and <tt>info</tt>
the returned information object returned by <a href="#cotonic.mqtt.extract">extract</a>.
This makes it possible to easily extract information from the topic.
Parameter <tt>options</tt> can be
<dl>
<dt><tt>wid</tt></dt>
<dd>The worker if used for making the scription. Can be used to differentiate
subscriptions from different components on the page. Defaults to: <tt>0</tt></dd>
<dt><tt>qos</tt></dt>
<dd>The quality-of-service of the subscription. Defaults to: <tt>0</tt></dd>
<dt><tt>retain_handling</tt></dt>
<dd>[todo]</dd>
<dt><tt>retain_as_published</tt></dt>
<dd>[todo]</dd>
<dt><tt>no_local</tt></dt>
<dd>[todo]</dd>
<dt><tt>properties</tt></dt>
<dd>[todo]</dd>
</dl>
</p>
<pre>
cotonic.broker.subscribe("truck/+truck_id/speed",
function(msg, info) {
console.log("Truck", info.truckid, "speed:", msg.payload);
},
{wid: "example"});
=> The function will now be called when a truck publishes its speed.</pre>
<!-- unsubscribe -->
<p id="cotonic.broker.unsubscribe">
<strong class="header">unsubscribe</strong> <code>cotonic.broker.unsubscribe(topics, [options])</code>
<br>
Unsubscribe from the topics. The parameter <tt>topics</tt> can be a single topic as a string,
or a list of topics. The optional parameter <tt>options</tt> is an object which has the following
properties.
<dl>
<dt>wid</dt>
<dd>The worker id from which to unsubscribe from. Defaults to: <tt>0</tt></dd>
</dl>
</p>
<pre>
cotonic.broker.unsubscribe("truck/+truck_id/speed",
{wid: "example"});
=> The subscriptions for topic "truck/+truck_id/speed" for worker "example" will not be called anymore.
</pre>
<!-- call -->
<p id="cotonic.broker.call">
<strong class="header">call</strong> <code>cotonic.broker.call(topic, payload, [options])</code>
<br>
Call is a special kind of publish where the publisher expects an answer back. The <tt>payload</tt>
will be published on <tt>topic</tt> using the <tt>options</tt> as described in
<a href="#cotonic.broker.publish">publish</a>.
The caller will be temporarily subscribed to a reply topic. When an answer is received on this
reply topic, the returned promise will be resolved. When no answer is received, the promise will
be rejected with a reason.
The option parameter can have the following extra options:
<dl>
<dt><tt>timeout</tt></dt>
<dl>
The timeout in milliseconds to use before rejecting
the returned promise. Default: <tt>15000</tt>, or
15 seconds.
</dl>
</dl>
</p>
<pre>
cotonic.broker.call("model/localStorage/get/username", {}, {timeout: 1000})
.then(function(username) {
console.log("The username is:", username");
})
.catch(function(e) {
console.log("Could not get username within 1 second.", e);
});</pre>
<h3 id="cotonic.mqtt">MQTT</h3>
<p>
The mqtt module provides functions to work with mqtt topics. The <a href="#cotonic.broker">broker</a>
uses this module as the basis to provide its functionality. This module also has some utility functions
to easily extract information from topics.
</p>
<!-- More info needed, references to MQTT maybe? -->
<!-- matches -->
<p id="cotonic.mqtt.matches">
<strong class="header">matches</strong> <code>cotonic.mqtt.matches(pattern, topic)</code>
<br>
Returns <tt>true</tt> when the pattern matches the topic, <tt>false</tt> otherwise.
</p>
<pre>
cotonic.mqtt.matches("truck/+/speed", "truck/01/speed");
=> true
cotonic.mqtt.matches("boat/+/speed", "truck/01/speed");
=> false
cotonic.mqtt.matches("+/+/speed", "plane/01/speed");
=> true
cotonic.mqtt.matches("+/+/speed", "plane/01/height");
=> false</pre>
<!-- fill -->
<p id="cotonic.mqtt.fill">
<strong class="header">fill</strong> <code>cotonic.mqtt.fill(pattern, params)</code>
<br>
Fill can use a <tt>pattern</tt> topic, and use the <tt>param</tt> object to create
an mqtt topic. Returns a string with the created topic.
</p>
<pre>
cotonic.mqtt.fill("truck/+truck_id/speed", {truck_id: 100});
=> "truck/100/speed"</pre>
<!-- extract -->
<p id="cotonic.mqtt.extract">
<strong class="header">extract</strong> <code>cotonic.mqtt.extract(pattern, topic)</code>
<br>
Extract values from <tt>topic</tt> into an object. The pattern <tt>+&lt;key&gt;</tt> matches
a single level of the topic path. It places an attribute <tt>key</tt> with as value the
found element in the path in the returned object. The pattern <tt>#&ltkey&gt;</tt>
matches a multi level path. When it is matched it places that part of the path into a list.
Returns an object with the found elements.
</p>
<pre>
cotonic.mqtt.extract("truck/+truck_id/speed", "truck/01/speed");
=> {truck_id: "01"}
cotonic.mqtt.extract("truck/+truck_id/#params", "truck/01/speed");
=> {truck_id: "01", params: ["speed"]}</pre>
<!-- exec -->
<p id="cotonic.mqtt.exec">
<strong class="header">exec</strong> <code>cotonic.mqtt.exec(pattern, topic)</code>
<br>
When the <tt>pattern</tt> <a href="#cotonic.mqtt.matches">matches</a> the <tt>topic</tt>,
<a href="#cotonic.mqtt.extract">extract</a> the values from the topic. Returns the
extracted values when the pattern matches, <tt>null</tt> otherwise.</p>
<pre>
cotonic.mqtt.exec("truck/+truck_id/speed", "truck/01/speed");
=> {truck_id: "01"}
cotonic.mqtt.exec("boat/+truck_id/speed", "truck/01/speed");
=> null</pre>
<!-- remove_named_wildcards -->
<p id="cotonic.mqtt.remove_named_wildcards">
<strong class="header">remove_named_wildcards</strong> <code>cotonic.mqtt.remove_named_wildcards(pattern)</code>
<br>
Remove the special, and non mqtt compliant, wildcards from the pattern and return a compliant
topic.
</p>
<pre>
cotonic.mqtt.remove_named_wildcards("truck/+truck_id/speed");
=> "truck/+/speed"
cotonic.mqtt.remove_named_wildcards("truck/#truck_info");
=> "truck/#"</pre>
<!-- MQTT Bridge -->
<h3 id="cotonic.mqtt_bridge">mqtt_bridge</h3>
<p>
This module makes it possible to bridge the broker on the local page to
an external MQTT broker.
</p>
<p id="cotonic.mqtt_bridge.newBridge">
<strong class="header">newBridge</strong> <code>cotonic.mqtt_bridge.newBridge([remote], [options])</code>
<br>
Create a new bridge. This is the hostname of the mqtt broker to connect to. When set to
<tt>"origin"</tt> the bridge uses the hostname of the document.
When the bridge is connected messages published on the topic matching <tt>bridge/+remote/#</tt> will
be re-published on the remote broker. Subscriptions on the topic <tt>bridge/+remote/#</tt>
will be published locally. This makes it possible to connect and communicate with
all clients connected to remote mqtt brokers.
<em>Note: It is possible to connect to multiple brokers.</em>
<dl>
<dt><tt>protocol</tt></dt>
<dd>The protocol to use to connect to the mqtt broker. Defaults to "ws" when the page is
loaded via "http", "wss" otherwise.
</dd>
<dt><tt>controller_path</tt></dt>
<dd>The pathname to use when connecting the web socket to the broker. Default: "mqtt-transport"</dd>
<dt><tt>connect_delay</tt></dt>
<dd>Default: 20</dd>
<dt><tt>periodic_delay</tt></dt>
<dd>Default: 1000</dd>
<dt><tt>username</tt></dt>
<dd>Default: undefined</dd>
<dt><tt>password</tt></dt>
<dd>Default: undefined</dd>
<dt><tt>mqtt_session</tt></dt>
<dd>The mqtt_session module which should be used. Default: cotonic.mqtt_session.</dd>
</dl>
</p>
<pre>
cotonic.mqtt_bridge.newBridge("test.mosquitto.org:8081",
{protocol: "wss"});
=> Connect the local broker to test.mosquitto.org via a websocket.
const decoder = new TextDecoder("utf-8");
cotonic.broker.subscribe("bridge/test.mosquitto.org:8081/bbc/subtitles/bbc_news24/raw",
function(m, t) {
console.log(decoder.decode(m.payload));
});
=> Subscribe to a local topic, it will be bridged from the server
to the page. This gets the raw subtitles of bbc news24.</pre>
<p id="cotonic.mqtt_bridge.findBridge">
<strong class="header">findBridge</strong> <code>cotonic.mqtt_bridge.findBridge([remote])</code>
<br>
Find bridge <tt>remote</tt>, when remote is not specified, <tt>"origin"</tt> is used.
Returns the bridge, or <tt>undefined</tt> when the bridge is not found.
</p>
<pre>
const b = cotonic.mqtt_bridge.findBridge("test.mosquitto.org:8081");
=> returns the bridge, or undefined
</pre>
<p id="cotonic.mqtt_bridge.deleteBridge">
<strong class="header">deleteBridge</strong> <code>cotonic.mqtt_bridge.deleteBridge([remote])</code>
<br>
Delete bridge <tt>remote</tt>, when remote is not specified, <tt>"origin"</tt> is used.
</p>
<pre>
cotonic.mqtt_bridge.deleteBridge("test.mosquitto.org:8081");</pre>
<!-- [TODO] document bridge api -->
<!-- User interface composer -->
<h3 id="cotonic.ui">ui</h3>
<p>
The user interface composer manages html snippets which can be placed in the DOM tree. When an
updated html snippet is delivered to the composer it will render it by using Google's incremental-dom library.
The updates will be applied <em>incrementally</em> directly to the DOM tree. The updates can be
delivered as html text snippets to the interface composer.
</p>
<!-- insert -->
<p id="cotonic.ui.insert">
<strong class="header">insert</strong> <code>cotonic.ui.insert(targetId, mode, initialHTML, [priority])</code>
<br>
Insert a new html snippet into the user interface composer. The snippet will be stored under the given
targetId. The element will not be placed in the dom-tree immediately. This will happen when one of the
render functions is called. Parameter <tt>mode</tt> supports the following options:
<dl>
<dt><tt>inner</tt> or <tt>true</tt></dt>
<dd>The html-snippets update the innerHTML of the element.</dd>
<dt><tt>outer</tt> or <tt>false</tt></dt>
<dd>The html-snippets update the outerHTML of the element.</dd>
<dt><tt>shadow-open</tt> or <tt>shadow</tt></dt>
<dd>The html-snippets update the shadow DOM of the element. The element will be the shadow host.
The shadow dom will be put in "open" mode. When the shadow dow was not yet initialised, it will
be initialised when the element is rendered the first time. Using the shadow-dom makes it possible
to isolate the component from the css rules which are present on the page.
</dd>
<dt><tt>shadow-closed</tt></dt>
<dd>Like "shadow-open", with the difference that the shadow dom will be initialised in "closed" mode.</dd>
</dl>
The optional priority parameter indicates the render order of the elements.
Elements with a high priority are rendedered before lower priorities. This makes it possible to nest
elements.
</p>
<pre>
cotonic.ui.insert("root", "outer", "&lt;p&gt;Hello World!&lt;/p&gt;");
cotonic.ui.insert("shadow-root", "shadow", "&lt;p&gt;Hello World!&lt;/p&gt;"); </pre>
<!-- get -->
<p id="cotonic.ui.get">
<strong class="header">get</strong> <code>cotonic.ui.get(id)</code>
<br>
Returns the current html snippet registered at <tt>id</tt>.
</p>
<pre>
let currentHTML = cotonic.ui.get("root");
=> "<a href='example.com'>Hello World!</a>"</pre>
<!-- remove -->
<p id="cotonic.ui.remove">
<strong class="header">remove</strong> <code>cotonic.ui.remove(id)</code>
<br>
Remove the html snippet registered at id. Note that this will not remove the element from the dom-tree,
it will only remove it from the user interface composer. When the element must be removed it should first
be updated and set to a blank string and a render operation should be done.
</p>
<pre>
cotonic.ui.remove("root");</pre>
<!-- update -->
<p id="cotonic.ui.update">
<strong class="header">update</strong> <code>cotonic.ui.update(id, htmlOrTokens)</code>
<br>
Update the registered snippet for the registered element with the given id. The new snippet will be visible
after a render operation.
</p>
<pre>
cotonic.ui.update("root", "&lt;p&gt;Hello Everybody!&lt;/p&gt;");
=> The root element on the page will be updated.</pre>
<!-- render -->
<p id="cotonic.ui.render">
<strong class="header">render</strong> <code>cotonic.ui.render()</code>
<br>
Trigger a render of all registered elements.
</p>
<pre>
cotonic.ui.render();
=> All elements will be (re)rendered.</pre>
<!-- renderId -->
<p id="cotonic.ui.renderId">
<strong class="header">renderId</strong> <code>cotonic.ui.renderId(id)</code>
<br>
Just render the element with the given id.
</p>
<pre>
cotonic.ui.renderId("root");</pre>
<!-- updateStateData -->
<p id="cotonic.ui.updateStateData">
<strong class="header">updateStateData</strong> <code>cotonic.ui.updateStateData(model, states)</code>
<br>
Communicate the state of the model to other non-cotonic components on the page. It can
be used to pass model state to SPA's or other modules. It sets a data attribute on the
html tag of the page.
The parameter <tt>model</tt> should be a string, <tt>states</tt> is an object
with values. The values of the states object are set as data attributes
on the html tag like this:
<tt>data-ui-&lt;model&gt;-&lt;key&gt;="&lt;value&gt;"</tt>. When an empty object is
passed all data attributes of the model is cleared.
</p>
<pre>
cotonic.ui.updateStateData("auth", {authorized: true});
=> &lt;html data-ui-state-auth-authorized="true"&gt;
...
cotonic.ui.updateStateData("auth", {});
=> &lt;html"&gt;
...</pre>
<!-- updateStateClass -->
<p id="cotonic.ui.updateStateClass">
<strong class="header">updateStateClass</strong> <code>cotonic.ui.updateStateClass(model, classes)</code>
<br>
Update the class of the html tag. This makes it possible to communicate important
state changes to external components like SPA's. The parameter <tt>model</tt> should be
a string. Parameter <tt>classes</tt> a list of classes which must be set.
The following elements will be added to the class attribute
<tt>ui-state-&lt;model&gt;-&lt;class&gt;</tt>. Passing <tt>[]</tt> will clear all the class
attributes of the model.
</p>
<pre>
cotonic.ui.updateStateClass("auth", ["unauthorized", "pending"]);
=> &lt;html class="ui-state-auth-pending ui-state-auth-unauthorized"&gt;
cotonic.ui.updateStateClass("auth", ["authorized"]);
=> &lt;html class="ui-state-auth-authorized"&gt;</pre>
<!-- on -->
<p id="cotonic.ui.on">
<strong class="header">on</strong> <code>cotonic.ui.on(topic, msg, event, [options])</code>
<br>
Publish a DOM event on the local broker. This allows subscribers to react to user interface
events. Parameter <tt>topic</tt> is the topic on which the event will be published. The parameters
<tt>msg</tt> and <tt>event</tt> are included in the message which is published. The event parameter
is expected to be a DOM event. The <tt>options</tt> parameter is optional, it
can contain a <tt>cancel</tt> property which can be set to <tt>true</tt>, <tt>false</tt> or
<tt>"preventDefault"</tt> to indicate if the event should be cancelled or prevented.
The other options can be the normal options found in
<a href="#cotonic.broker.publish"><tt>publish</tt></a>.
</p>
<pre>
document.addEvenListener("click", function(e) {
const topic = event.target.getAttribute("data-topic");
if(!topic) return;
cotonic.ui.on(topic, {foo: "bar"}, e);
}, {passive: true})
=> When somebody clicks on an element with has a data-topic="a/topic"
attribute, the event will be published on that topic.</pre>
<h3 id="cotonic.tokenizer">tokenizer</h3>
<p>
The tokenizer transforms text to html tokens. The tokenizer is used by the
user interface composer in order to call the incremental-dom api.
It uses incremental-dom to do in-place diffing of the dom-tree.
<br>
<em>Note: The tokenizer does not parse or validate the html. It just
tokenizes the input.</em>
</p>
<!-- tokens -->
<p id="cotonic.tokenizer.tokens">
<strong class="header">tokens</strong> <code>cotonic.tokenizer.tokens(text)</code>
<br>
Transforms a string with html tags into a list of tokens. The tokens are
objects of the form: <tt>{type: "type", [args]}</tt>, with type being one of:
<dl>
<dt><tt>open</tt><dt>
<dd>Represents an open tag. Contains the attribute <tt>tag</tt> which is
set to the tagname of the element, and the attribute <tt>attributes</tt>
which is a list of attributes of the element.
</dd>
<dt><tt>close</tt><dt>
<dd>Represents a close tag. Contains the attribute <tt>tag</tt> which is
set to the tagname of the close element.</dd>
<dt><tt>void</tt><dt>
<dd>Represents a void element. The attribute <tt>tag</tt> is set to the
tagname of the void element.</dd>
<dt><tt>text</tt><dt>
<dd>Represents a test element. The attribute <tt>data</tt> is set to the
text data.</dd>
<dt><tt>doctype</tt><dt>
<dd>Represents a doctype element. The attribute <tt>attributes</tt> is
set to the attributes of the element.</dd>
<dt><tt>pi</tt><dt>
<dd>Represents a processing instruction element. The attribute <tt>tag</tt>
is set to the tagname of the processing instruction. The attribute
<tt>arguments</tt> contains the list of attributes.</dd>
<dt><tt>comment</tt><dt>
<dd>Represents a comment element. The attribute <tt>data</tt> contains
the text in the comment element.</dd>
</dl>
</p>
<pre>
cotonic.tokenizer.tokens("&lt;div class='example'&gt;Tokenizing&lt;br /&gt; is cool&lt/div&gt;");
=> [{type: "open", tag: "div", attributes: ["class", "example"]},
{type: "text", data: "Tokenizing"},
{type: "void", tag: "br", attributes: []},
{type: "text", data: " is cool"},
{type: "close", tag: "div"}] </pre>
<!-- charref -->
<p id="cotonic.tokenizer.charref">
<strong class="header">charref</strong> <code>cotonic.tokenizer.charref(text)</code>
<br>
Transforms a html charref into a character.
</p>
<pre>
cotonic.tokenizer.charref("#128540");
=> "😜"
cotonic.tokenizer.charref("amp");
=> "&"</pre>
<h2 id="worker.functions">Worker Functions</h2>
<p>
Workers are stand alone processes. They have no shared data with the page, nor
with other workers. Their memory and calling context is isolated. They can easily communicate
with other workers, the page, and servers by publising messages on topics, and subscribing
to them. Cotonic provides <a href="#models">models</a>, modules which are loaded and
ready for requests.
</p>
<!-- worker init-->
<p id="worker.on_init">
<strong class="header">on_init</strong> <code>self.on_init(arguments)</code>
<br>
The callback <tt>on_init</tt> is called when the worker receives the initialization
message by the page. It can take multiple arguments. The arguments are passed in via
the <a href="#cotonic.spawn"><tt>spawn</tt></a> <tt>args</tt> argument list.
This function can be used to initialize the worker. The callback is optional.
</p>
<pre>
// Page code.
cotonic.spawn("example_worker", [amount, target]);
// Worker code
let amount = null;
let targetId = null;
self.on_init = function(n, id) {
amount = n;
targetId = id;
}
==> Worker receives the arguments which can be used for initialization.</pre>
<!-- worker connect -->
<p id="worker.connect">
<strong class="header">connect</strong> <code>self.connect(connectOptions)</code>
<br>
Connect the worker to the page. Returns a promise. This promise is resolved when
the worker is connected to the page. When is indicated in the <tt>connectOptions</tt> that
the worker depends on other services the promise is resolved after those dependencies
have become available.
Parameter <tt>connectOptions</tt> supports the following options:
<dl>
<dt><tt>name</tt></dt>
<dd>The name of the worker.</dd>
<dt><tt>depends</tt></dt>
<dd>A list of dependencies on which the worker needs. The connect promise will be
resolved, after the dependencies become available.</dd>
<dt><tt>provides</tt></dt>
<dd>A list of dependencies which is provided by this worker. The are published after
the worker is connected.</dd>
</dl>
</p>
<pre>
self.connect().then(
function() {
console.log("Connected");
}
);
=> The worker is being connected to the page.</pre>
<pre>
self.connect({depends: ["model/ui",
"model/serviceWorker",
"worker/abc"]}).then(
function() {
console.log("Connected, the requested dependencies are available, and worker abc is started.");
}
);
=> The worker is being connected to the page, and the requested dependencies are available.</pre>
<!-- worker disconnect -->
<p id="worker.disconnect">
<strong class="header">disconnect</strong> <code>self.disconnect()</code>
<br>
Disconnects the worker from the page. After this step it is no
longer possible to send and receive messages from the page.
</p>
<pre>
self.disconnect();
=> The worker is disconnected from the page.</pre>
<!-- worker is_connected -->
<p id="worker.is_connected">
<strong class="header">is_connected</strong> <code>self.is_connected()</code>
<br>
Returns <tt>true</tt> iff the worker is connected to the page,
<tt>false</tt> otherwise.
</p>
<pre>
self.is_connected();
=> true</pre>
<!-- worker whenDependencyProvided -->
<p id="worker.whenDependencyProvided">
<strong class="header">whenDependencyProvided</strong> <code>self.whenDependencyProvided(dependency)</code>
<br>
Returns a Promise which is resolved when the external dependency is provided.
</p>
<pre>
self.whenDependencyProvided("model/ui").then(
function() {
worker.publish("model/ui/insert/foo", {..});
});
</pre>
<!-- worker whenDependenciesProvided -->
<p id="worker.whenDependenciesProvided">
<strong class="header">whenDependenciesProvided</strong> <code>self.whenDependenciesProvided(dependencies)</code>
<br>
Returns a Promise which is resolved when all external dependencies are provided.
</p>
<pre>
self.whenDependenciesProvided(["model/ui", "model/x"]).then(
function() {
...
});
==> The promise will be resolved after the ui and x model are provided.</pre>
<!-- worker provides -->
<p id="worker.provides">
<strong class="header">provides</strong> <code>self.provides(modelsAndWorkers)</code>
<br />
Indicate to other workers and the page that this worker provides models or workers.
Other workers waiting on these dependencies will get notified when they become
available. The parameter <tt>modelsAndWorkers</tt> should be a list with model or
worker names this worker provides.
</p>
<pre>
self.provides(["model/a", "model/b", "worker/x"])
==> When the worker connects, it will notify other workers that new models
have become available.</pre>
<!-- worker subscribe -->
<p id="worker.subscribe">
<strong class="header">subscribe</strong> <code>self.subscribe(topics, callback, ack_callback)</code>
<br>
Subscribe the worker to the <tt>topics</tt>. When a message is received, the <tt>callback</tt> is called.
Callback is a function which receives two parameters. The first parameter is the <tt>message</tt>, the second
parameter an <tt>object</tt> returned by <a href="#cotonic.mqtt.extract">extract</a>. This can be used to
easily extract elements from topic paths in an object. The parameter <tt>topics</tt> can be a string, or
a list of strings. The callback <tt>ack_callback</tt> is used when the page is subscribed, or when
there is a problem. Returns nothing.
</p>
<pre>
function logSpeed(msg, args) {
if(args.boat_id) {
console.log("boat", args.boat_id, "is now moving at", msg.payload);
}
if(args.truck_id) {
console.log("truck", args.truck_id, "is now moving at", msg.payload);
}
}
self.subscribe(["truck/+boat_id/speed", "boat/+boat_id/speed"], logSpeed);
=> The function logSpeed will be called when somebody sends a message which
matches the topics.</pre>
<!-- worker unsubscribe -->
<p id="worker.unsubscribe">
<strong class="header">unsubscribe</strong> <code>self.unsubscribe(topics, callback, ack_callback)</code>
<br>
Unsubscribe the worker from page. The worker will no longer receive messages from the specified topics.
</p>
<pre>
self.unsubscribe();</pre>
<!-- worker publish -->
<p id="worker.publish">
<strong class="header">publish</strong> <code>self.publish(topic, message, options)</code>
<br>
Publish <tt>message</tt> on <tt>topic</tt>. The <tt>options</tt> can be used to indicate
the quality of service, or if the message should be retained by the broker.
</p>
<pre>
self.publish("world", "hello", {retain: true});</pre>
<!-- worker call -->
<p id="worker.call">
<strong class="header">call</strong> <code>self.call(topic, message, options)</code>
<br>
Publishes <tt>message</tt> on <tt>topic</tt> and subscribes itself to a reply topic.
Returns a promise which is fulfilled when a message is received on the reply topic. When
no message arrives, the promise is rejected. Returns a promise.
</p>
<pre>
self.call("model/document/get/all")
.then(...)
.reject(...);</pre>
<h2 id="models">Models</h2>
<p>
Because workers run as independent components it is not possible to directly
call api's. Some api's are also not available to web workers. Models are
special modules, or workers, which publish their data, or are ready to
receive calls via mqtt topics.
</p>
<p>
The convention is that models provide their services via the following topic
tree.
<dl>
<dt><tt>model/+modelName/get/+</tt></dt>
<dd>Call topics. When the model receives a publish, it returns the answer
to the reply topic.</dd>
<dt><tt>model/+modelName/post/+</tt></dt>
<dd>Topics used to post updates to the model.</dd>
<dt><tt>model/+modelName/delete/+</tt></dt>
<dd>Topics used to delete items managed by the model.</dd>
<dt><tt>model/+modelName/event/+</tt></dt>
<dd>Topics which the model publishes events on.</dd>
</dl>
</p>
<!-- model window -->
<h3 id="model.window">model/window</h3>
<p>
The window model can be used open and close windows.
</p>
<!-- post/open -->
<p id="model.window.post.open">
<strong class="header">post/open</strong>
<br>
Open a window. The payload of the message can be set t
<dl>
<dt><tt>href</tt></dt>
<dd>The url of the window. See below.</dd>
<dt><tt>data-window</tt></dt>
<dd>A JSON string containing properties described below.</dd>
</dl>
Or
<dl>
<dt><tt>url</tt></dt>
<dd>The url of the page to open.</dd>
<dt><tt>name</tt></dt>
<dd>Specifies the target attribute of the window.
<dl>
<dt><tt>_blank</tt></dt><dd> opens a new window.</dd>
<dt><tt>_parent</tt></dt><dd> loads the url in the parent window.</dd>
<dt><tt>_self</tt></dt><dd> replaces the current page.</dd>
<dt><tt>_top</tt></dt><dd> replaces any frameset that maybe loaded.</dd>
<dt><tt>name</tt></dt><dd>the name of the window to load.</dd>
</dl>
</dd>
<dt><tt>width</tt></dt>
<dd>The width of the window in pixels.</dd>
<dt><tt>height</tt></dt>
<dd>The height of the window in pixels.</dd>
<dt><tt>toolbar</tt></dt>
<dd>Wether or not to display the browsers toolbar.</dd>
<dt><tt>scrollbars</tt></dt>
<dd>Wether or not to display scrollbars.</dd>
<dt><tt>status</tt></dt>
<dd>Wether or not to add a status bar.</dd>
<dt><tt>resizable</tt></dt>
<dd>Wether or not the window is resizable.</dd>
<dt><tt>location</tt></dt>
<dd>Wether or not to display the address field.</dd>
<dt><tt>menubar</tt></dt>
<dd>Wether or not to display the menubar.</dd>
<dt><tt>centerBrowser</tt></dt>
<dd>Indicate wether or not to center the new window on top of the current active browser window.</dd>
<dt><tt>centerScreen</tt></dt>
<dd>Indicate wether or not to center the new window on the screen.</dd>
<dt><tt>top</tt></dt>
<dd>The top position of the window.</dd>
<dt><tt>left</tt></dt>
<dd>The left position of the window.</dd>
</dl>
</p>
<pre>
&lt;a href="https://example.com/logon"
class="btn btn-social"
data-onclick-topic="model/window/post/open"&gt;Logon in with Example.com&lt;/a&gt;
data-window="{'centerBrowser': true}"
==> The click on the link publishes is window open message which will open
a centered logon window.</pre>
<pre>
self.publish("model/window/post/open", {url: "https://example.com/logon",
centerBrowser: true})
==> A worker opens a logon window.</pre>
<p id="model.window.post.close">
<strong class="header">post/close</strong>
<br>
Open a window.
</p>
<pre>
&lt;a href="#" class="btn btn-default"
data-onclick-topic="model/window/post/close"&gt; Close Window &lt;a&gt;
==> Closes the current window.
</pre>
<!-- model document -->
<h3 id="model.document">model/document</h3>
<p>
The document model can be used to retrieve details about the current document.
</p>
<!-- get/all -->
<p id="model.document.get.all">
<strong class="header">get/all</strong>
<br>
Get all information on the current document. Includes screen size, cookies, user agent details.
</p>
<pre>
self.call("model/document/get/all")
.then(function(m) {
console.log(m.payload)
});
=> {screen_width: 1280, screen_height: 800,
inner_width: 1047, inner_height: 292,
is_touch: false, …}</pre>
<!-- get/intl -->
<p id="model.document.get.intl">
<strong class="header">get/intl</strong>
<br>
Returns the internationalization details of the current page.
</p>
<pre>
self.call("model/document/get/intl")
.then(function(m) {
console.log(m.payload)
});
=> {timezone: {cookie: "", user_agent: "Europe/Amsterdam"},
language: {cookie: "", user_agent: "en-US", document: null}}</pre>
<!-- model location -->
<h3 id="model.location">Location</h3>
<p>
The location model can be used to retrieve information on the current location
of the page. It also allows subscription to location changes.
</p>
<!-- get/href -->
<p id="model.location.get.href">
<strong class="header">get/href</strong>
<br>
Get the current href.
</p>
<pre>
self.call("model/location/get/href")
.then(function(m) {
console.log(m.payload)
});
=> "https://cotonic.org/#model.location"</pre>
<!-- get/protocol -->
<p id="model.location.get.protocol">
<strong class="header">get/protocol</strong>
<br>
Get the current protocol
</p>
<pre>
self.call("model/location/get/protocol")
.then(function(m) {
console.log(m.payload)
});
=> "https"</pre>
<!-- get/host -->
<p id="model.location.get.host">
<strong class="header">get/host</strong>
<br>
Get the current host (with port).
</p>
<pre>
self.call("model/location/get/host")
.then(function(m) {
console.log(m.payload)
});
=> "cotonic.org"</pre>
<!-- get/hostname -->
<p id="model.location.get.hostname">
<strong class="header">get/hostname</strong>
<br>
</p>
Get the current hostname (without port).
<pre>
self.call("model/location/get/hostname")
.then(function(m) {
console.log(m.payload)
});
=> "cotonic.org"</pre>
<!-- get/origin -->
<p id="model.location.get.origin">
<strong class="header">get/origin</strong>
<br>
Get the current origin.
</p>
<pre>
self.call("model/location/get/origin")
.then(function(m) {
console.log(m.payload)
});
=> "https://cotonic.org"</pre>
<!-- get/pathname -->
<p id="model.location.get.pathname">
<strong class="header">get/pathname</strong>
<br>
Get the current pathname.
</p>
<pre>
self.call("model/location/get/pathname")
.then(function(m) {
console.log(m.payload)
});
=> "/"</pre>
<!-- get/port -->
<p id="model.location.get.port">
<strong class="header">get/port</strong>
<br>
Get the current port.
</p>
<pre>
cotonic.broker.call("model/location/get/port")
.then(function(m) {
console.log(m.payload)
})
=> "" // The default port.</pre>
<!-- event/search -->
<p id="model.location.event.search">
<strong class="header">event/search</strong>
<br>
A message containing the query string part of the url will be published when it changes.
<em>Note: the message is retained</em>
</p>
<pre>
cotonic.broker.subscribe("model/location/event/search",
function(m, a) {
console.log("query string changed", m.payload);
});</pre>
<!-- event/pathname -->
<p id="model.location.event.pathname">
<strong class="header">event/pathname</strong>
<br>
A message containing the pathname part of the url will be published when it changes.
<em>Note: the message is retained</em>
</p>
<pre>
cotonic.broker.subscribe("model/location/event/pathname",
function(m, a) {
console.log("pathname changed", m.payload);
});</pre>
<!-- event/hash -->
<p id="model.location.event.hash">
<strong class="header">event/hash</strong>
<br>
A message containing the hash part of the url will be published when it changes.
<em>Note: the message is retained</em>
</p>
<pre>
cotonic.broker.subscribe("model/location/event/hash",
function(m, a) {
console.log("hash changed", m.payload);
});</pre>
<!-- event/ping -->
<p id="model.location.event.ping">
<strong class="header">event/ping</strong>
<br>
When the location model is enabled, a retained message is published on this
topic. By subscribing to this topic it is possible to see when the model is
enabled. The payload of the message <tt>pong</tt>.
</p>
<pre>
cotonic.broker.subscribe("model/location/event/ping",
function(m) {
console.log("The location model is enabled", m.payload)
})
=> Logs a message on the console when the location model is enabled.</pre>
<p id="model.location.config">
<strong class="header"><em>Configuration</em></strong>
<br>
The location has one configuration option.
<dl>
<dt><tt>pathname_search</tt></dt>
<dd>Set to the current search parameters of the page. Alternatively this setting
can can be done as <tt>data-cotonic-pathname-search</tt> attribute on the body tag. dd>
</dl>
</p>
<pre>
&ltscript&gt;
window.cotonic = window.cotonic || {};
window.cotonic.config = {
start_service_worker: true,
pathname_search: "q=110&amp;j=yes"
};
&lt;/script&gt;
&ltscript src="cotonic.js"&gt;&lt;/script&gt;
</pre>
<pre>
&lt;body data-cotonic-pathname-search="q=110&amp;j=yes"&gt;
...
&lt;/body&gt;
</pre>
<h3 id="model.ui">model/ui</h3>
<p>
The ui model can be used to update the dom by publishing messages on one of the
<tt>model/ui/#</tt> topics. Elements can be inserted, deleted and updated. When
elements in the dom-tree are updated, the a message
<tt>model/ui/event/dom-updated/+key</tt> is published. This can be used to
react to dom-changes when they happen.
</p>
<p id="model.ui.insert.key">
<strong class="header">insert/+key</strong>
<br>
Insert a new ui snippet named <tt>key</tt> into the user interface composer.
Expects a message with the following properties.
<dl>
<dt><tt>initialData</tt></dt>
<dd>The initial data to place in the dom tree.</dd>
<dt><tt>inner</tt></dt>
<dd>If set to <tt>true</tt>, the ui composer will update the inner html, when set
to <tt>false</tt>, the outer html</dd>
<dt><tt>priority</tt></dt>
<dd>An element with a high priority will be rendered before elements with a low
priority.
<!-- needs example -->
</dd>
</dl>
</p>
<pre>
self.publish("model/ui/insert/root", {
initialData: "&lt;div class='loading'&gt;Loading&lt;/div&gt;",
inner: true
priority: 100
}, {retain: true});
=> When the ui composer receives this message it will add the
loading
</pre>
<p id="model.ui.update.key">
<strong class="header">update/+key</strong>
<br>
Update the contents of ui snippet named <tt>key</tt> previously registered
by an <tt>insert</tt>. Expects a message with the html snippet as message.
</p>
<pre>
self.publish("model/ui/update/root",
"<div class="main-app>" +
"..." +
"</div>");
=> The root element is updated.</pre>
<p id="model.ui.delete.key">
<strong class="header">delete/+key</strong>
<br>
Delete ui snippet named <tt>key</tt> from the user interface composer.
</p>
<pre>
self.publish("model/ui/delete/root");
=> The root element is removed from the composer, but
stays in the DOM tree.</pre>
<p id="model.ui.render">
<strong class="header">render</strong>
<br>
Trigger a render of all registered elements.
</p>
<pre>
self.publish("model/ui/render");</pre>
<p id="model.ui.render.key">
<strong class="header">render/+key</strong>
<br>
Trigger a render of the element <tt>key</tt>
</p>
<pre>
self.publish("model/ui/render.root");</pre>
</pre>
<p id="model.ui.render-template.key">
<strong class="header">render-template/+key</strong>
<br>
Request rendering a template with parameters. A rendering engine must be
available to respond to the render requests. The render result will update
<tt>key</tt> element. Note: The element must first be inserted with
<a href="#cotonic.ui.insert">cotonic.ui.insert</tt></a> or
<a href="#model.ui.insert.key"><tt>model/ui/insert/+key</tt></a>.
The payload of the render request should be:
<dl>
<dt><tt>topic</tt></dt>
<dd>The topic on which the render engine is subscribed. Can be a local or a remote topic.
The response of the renderer will be updated on <tt>key</tt></dd>
<dt><tt>data</tt></dt>
<dd>The data which the render engine can use to render the template.</dd>
<dt><tt>dedup</tt></dt>
<dd>Send the render request via a MQTT QoS 1 message. This means that we can
get multiple responses. Dedup makes sure the result will be rendered only once.</dd>
</dl>
</p>
<pre>
self.publish("model/ui/render-template/root",
{topic: "bridge/origin/model/template/get/render/hello.tpl",
dedup: true,
data: {name: "Bob"}
});
==> The render engine listening at bridge/origin/model/template receives
a render request. The render result will be placed in element root.</pre>
<p id="model.ui.event.recent-activity">
<strong class="header">event/recent-activity</strong>
<br>
A message containing the user's activity status is published regularly
on this topic. The message is an object which contains a
<tt>is_active</tt> boolean property which indicates if the has been
active over the last period between publishes.
</p>
<pre>
// worker example
self.subscribe("model/ui/event/recent-activity",
function(m) {
const a = m.payload.is_active?"active":"not active";
console.log("The user is: ", a);
}
);
=> Displays the user's activity status.</pre>
<p id="model.ui.event.dom-updated">
<strong class="header">event/dom-updated/+key</strong>
<br>
When the dom-tree is updated a message will be published. The
message is fired after the dom-tree is updated.
</p>
<pre>
cotonic.broker.subscribe("model/ui/event/dom-updated/message-container",
function(m) {
const c = document.getElementById("message-container");
c.scrollTop = c.scrollHeight
}
);
=> Scrolls the messages bubble container to the bottom.
</pre>
<!-- ui status -->
<p id="model.ui.event.ui-status">
<strong class="header">event/+model/ui-status</strong>
<br>
Events send to this topic will result in class and data attributes set on
the <tt>html</tt> of the document. The <tt>model</tt> attribute is used in the
data and class attributes. This can be used to signal the state of a model to
the document. This can be used to signal the state of cotonic workers or models to
other ui libraries like <a href="https://elm-lang.org">Elm</a> or
<a href="https://reactjs.org">React</a>. The payload of the message:
<dl>
<dt><tt>status</tt></dt>
<dd>Object with model statuses. Will be set as data attributes.</dd>
<dt><tt>classes</tt></dt>
<dd>List with model statuses. Will be set as classes.</dd>
</dl>
</p>
<pre>
self.publish("model/auth/event/ui-status", {status: {"auth": "anonymous"},
classes: ["anonymous"]});
==> model.ui adds clas "ui-state-auth-anonymous" and
adds data-ui-state-auth-auth="anonymous" to the html element of the document.</pre>
<!-- autofocus model -->
<h3 id="model.autofocus">model/autofocus</h3>
<p>
The autofocus model looks for elements with the <tt>autofocus</tt> attribute
which are just added to the dom tree. When this happens, it auto focusus them.
The reason for this is is that the <tt>autofocus</tt> attribute only works
during document render stage, but it is very handy to have available all the
time.
</p>
<p id="model.autofocus.event.focus.key">
<strong class="header">event/focus/+key</strong>
<br>
The element with <tt>key</tt> has just been focussed.
</p>
<pre></pre>
<!-- serviceWorker model -->
<h3 id="model.serviceWorker">model/serviceWorker</h3>
<p>
The serviceWorker model makes it possible to communicate with other tabs
from the same site. This makes it possible to communicate important state
to all tabs.
</p>
<div class="note">
<p>
<strong class="header">A Note on Security</strong>
<br>
The serviceWorker needs https to work correctly. The <i>Chrome</i> browser
does not accept self-signed certificates for running serviceWorkers.
Either run from localhost, use a real certificate, or follow the instructions
<a href="https://deanhume.com/testing-service-workers-locally-with-self-signed-certificates/" target="_blank">on this page by Dean Hume</a>.
</p>
<p>
Safari and Firefox accept self-signed certificates for
running the serviceWorker.
</p>
</div>
<p id="model.serviceWorker.post.broadcast.channel">
<strong class="header">post/broadcast/+channel</strong>
<br>
Broadcast the message on <tt>channel</tt>. The message can be received
via the <tt>"model/serviceWorker/event/+channel</tt> topic. This works
across all open tabs.
</p>
<pre>
// Publish from a worker
self.publish("model/serviceWorker/post/broadcast/background",
{hue: "blue", brightness: 45});
</pre>
<p id="model.serviceWorker.event.broadcast.channel">
<strong class="header">event/broadcast/+channel</strong>
<br>
Subscribe to the broadcast <tt>channel</tt> of the serviceWorker. Messages
posted from other tabs or workers, including messages sent by the publisher
will be received.
</p>
<pre>
// Subscribe on a page
cotonic.broker.subscribe("model/serviceWorker/event/broadcast/background",
function(m) {
console.log("Setting background to", m.payload");
setBackground(m.payload);
})</pre>
<p id="model.serviceWorker.event.ping">
<strong class="header">event/ping</strong>
<br>
When the serviceWorker model is enabled it publishes a retained <tt>pong</tt>
message on this topic. This makes it possible to check if the model is enabled.
</p>
<pre>
// Subscribe on a page
cotonic.broker.subscribe("model/serviceWorker/event/ping",
function() {
console.log("the serviceWorker model is enabled")
});</pre>
<p id="model.serviceWorker.config">
<strong class="header"><em>Configuration</em></strong>
<br>
The service worker model has two configuration options.
<dl>
<dt><tt>start_service_worker</tt></dt>
<dd>When set to <tt>false</tt>, the service worker will not start.
<br /><strong>default</strong> <tt>true</tt></dd>
<dt><tt>service_worker_src</tt></dt>
<dd>The url of the service worker which should be started.
<br/ ><strong>default</strong>
<tt>/cotonic-service-worker.js</tt></dd>
</dl>
</p>
<pre>
&ltscript&gt;
window.cotonic = window.cotonic || {};
window.cotonic.config = {
start_service_worker: true,
service_worker_src: "/cotonic-service-worker.js"
};
&lt;/script&gt;
&lt;script src="cotonic.js"&gt;&lt;/script&gt;
</pre>
<!-- localStorage model -->
<h3 id="model.localStorage">model/localStorage</h3>
<p id="model.localStorage.get.key">
<strong class="header">get/+key</strong>
<br>
Gets item <tt>key</tt> from localStorage. Returns the content as payload.
</p>
<pre>
cotonic.broker.call("model/localStorage/get/a")
.then(
function(m) {
console.log("a is set to:", m.payload)
}
);</pre>
<p id="model.localStorage.post.key">
<strong class="header">post/+key</strong>
<br>
Update, or insert message under <tt>key</tt> in localStorage.
</p>
<pre>
cotonic.broker.publish("model/localStorage/post/a", "Hello world!");</pre>
<p id="model.localStorage.delete.key">
<strong class="header">delete/+key</strong>
<br>
Delete item stored under <tt>key</tt> from localStorage.
</p>
<pre>
cotonic.broker.publish("model/localStorage/delete/a");</pre>
<p id="model.localStorage.event.key">
<strong class="header">event/+key</strong>
<br>
Subscribe to changes or deletions from localStorage. When the message payload
is <tt>null</tt> the item is delete. Otherwise the payload is set to the
newly updated value.
</p>
<pre>
cotonic.broker.subscribe("model/localStorage/event/+key",
function(m, a) {
if(m.payload === null) {
console.log("localStorage item:", a.key, "deleted");
else {
console.log("localStorage item:", a.key, "changed", m.payload);
}
});
=> Logs update to localStorage elements.</pre>
<p id="model.localStorage.event.ping">
<strong class="header">event/ping</strong>
<br>
When the localStorage model is enabled it publishes a retained message
under the topic <tt>model/localStorage/event/ping</tt>. It makes it
possible to detect the localStorage model is enabled.
</p>
<pre>
cotonic.broker.subscribe("model/localStorage/event/ping",
function() {
console.log("localStorage is enabled");
});</pre>
<h3 id="model.sessionStorage">model.sessionStorage</h3>
<p>
The sessionStorage model provids access to the <tt>sessionStorage</tt> of
the browser. It makes it possible to set and retrieve values via
mqtt topics. For more information about the session storage see:
</p>
<p id="model.sessionStorage.get.key">
<strong class="header">get/+key</strong>
<br>
Get the element stored as <tt>key</tt> from the sessionStorage. Returns the
contents as payload, or <tt>null</tt> if it is not found.
</p>
<pre>
self.call("model/sessionStorage/get/item-1")
.then(function(m) {
console.log("item-1", m.payload);
});
=> Logs the item-1 on the console, or null otherwise.
</pre>
<p id="model.sessionStorage.get.key.subkey">
<strong class="header">get/+key/+subkey</strong>
<br>
Get a sub element stored as <tt>key.subkey</tt> from the sessionStorage.
</p>
<pre>
self.call("model/sessionStorage/get/item-2/a")
.then(function(m) {
console.log("item-2", m.payload);
});
=> Logs the item-2 on the console, or null otherwise.</pre>
<p id="model.sessionStorage.post.key">
<strong class="header">post/+key</strong>
Store a message under <tt>key</tt> in the sessionStorage.
</p>
<pre>
self.publish("model/sessionStorage/post/item-1", "Cucumbers are sometimes green");
=> New value stored.</pre>
<p id="model.sessionStorage.post.key.subkey">
<strong class="header">post/+key/+subkey</strong>
<br>
Store a message under <tt>key.subkey</tt> in the sessionStorage. When no
item is stored under <tt>key</tt> a new object is created, and <tt>subkey</tt>
is added as sub-element. When <tt>key</tt> exists it must be an object, then
<tt>subkey</tt> is added or overwritten.
</p>
<pre>
self.publish("model/sessionStorage/post/item-2/a", "hello");</pre>
<p id="model.sessionStorage.delete.key">
<strong class="header">delete/+key</strong>
<br>
Delete an element stored under <tt>key</tt> from the sessionStorage.
</p>
<pre>
self.publish("model/sessionStorage/delete/item-2");</pre>
<p id="model.sessionStorage.delete.key.subkey">
<strong class="header">delete/+key/+subkey</strong>
<br>
Delete an element stored under <tt>key.subkey</tt> from the sessionStorage.
</p>
<pre>
self.publish("model/sessionStorage/delete/item-2");</pre>
<p id="model.sessionStorage.event.key">
<strong class="header">event/+key</strong>
<br>
Subscribe to sessionStorage updates and deletes. When entry is updated
you will get a notification.
</p>
<pre>
self.subscribe("model/sessionStorage/subscribe/+key",
function(m, a) {
console.log(m, a);
});
self.publish("model/sessionStorage/post/a", "hello");
self.publish("model/sessionStorage/post/b", "world");
=> Logs the update in the console</pre>
<p id="model.sessionStorage.event.ping">
<strong class="header">event/ping</strong>
<br>
When the sessionStorage model is enabled a retained message is published
on the <tt>model/sessionStorage/event/ping</tt> topic.
</p>
<pre>
let storageReady = false;
cotonic.broker.subscribe("model/sessionStorage/event/+key",
function(msg, args) {
switch(args.key)
case "pong":
storageReady = true;
break;
...
});
=> When the storage is ready a pong message will be available</pre>
<h3 id="links">Links</h3>
<p>
<a href="https://medium.com/google-developers/introducing-incremental-dom-e98f79ce2c5f">Introduction to Incremental DOM</a>
</p>
<p>
<a href="https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html">MQTT Version 5.0 - OASIS</a>
</p>
<p>
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API">Web Workers API</a>
</p>
<p>
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage">sessionStorage API</a>
</p>
<p>
<a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM">Using shadow DOM</a>
</p>
<h3 id="changelog">Change Log</h3>
<p id="1.0.6">
<strong class="header">1.0.6</strong> &mdash; <small><i>Mar 15, 2021</i></small> &mdash;
<a href="https://github.com/cotonic/cotonic/compare/1.0.5...1.0.6">Diff</a> &mdash;
<a href="https://ghcdn.rawgit.org/cotonic/cotonic/1.0.6/index.html"l>Docs</a> &mdash;
<a href="https://github.com/cotonic/cotonic/archive/1.0.6.tar.gz" download>Download</a>
<br />
<ul>
<li>
Added <tt>model/ui/event/node-created/+key</tt> and <tt>model/ui/event/node-deleted/+key</tt>
events.
</li>
<li>
Added <tt>model/document/post/cookie/+key</tt> and <tt>model/document/get/cookie/+key</tt>
to set and retrieve cookies.
</li>
<li>
Small fixes.
</li>
</ul>
</p>
<p id="1.0.5">
<strong class="header">1.0.5</strong> &mdash; <small><i>Mar 14, 2021</i></small> &mdash;
<a href="https://github.com/cotonic/cotonic/compare/1.0.4...1.0.5">Diff</a> &mdash;
<a href="https://ghcdn.rawgit.org/cotonic/cotonic/1.0.5/index.html"l>Docs</a> &mdash;
<a href="https://github.com/cotonic/cotonic/archive/1.0.5.tar.gz" download>Download</a>
<br />
<ul>
<li>
More documentation.
</li>
<li>
Autofocus model
</li>
<li>
Extend js configuration api to more easily setup the MQTT bridge.
</li>
<li>
Ability to pre-connect the bridge websocket very early to speed up connecting.
</li>
<li>
Ability to collect click and submit events before the bridge is setup to prevent
the loss of interactivity before everything is setup. After the bridge is connected
the click and submit messages collected will be published on origin.
</li>
<li>
And a lot of little fixes.
</li>
</ul>
</p>
<p id="1.0.4">
<strong class="header">1.0.4</strong> &mdash; <small><i>Nov 2, 2020</i></small> &mdash;
<a href="https://github.com/cotonic/cotonic/compare/1.0.3...1.0.4">Diff</a> &mdash;
<a href="https://ghcdn.rawgit.org/cotonic/cotonic/1.0.4/index.html"l>Docs</a> &mdash;
<a href="https://github.com/cotonic/cotonic/archive/1.0.4.tar.gz" download>Download</a>
<br />
<ul>
<li>
More documentation.
</li>
<li>
A js based configuration api.
</li>
<li>
And a lot of little fixes.
</li>
</ul>
</p>
<p id="1.0.3">
<strong class="header">1.0.3</strong> &mdash; <small><i>Mar 1, 2020</i></small> &mdash;
<a href="https://github.com/cotonic/cotonic/compare/1.0.2...1.0.3">Diff</a> &mdash;
<a href="https://ghcdn.rawgit.org/cotonic/cotonic/1.0.3/index.html"l>Docs</a> &mdash;
<a href="https://github.com/cotonic/cotonic/archive/1.0.3.tar.gz" download>Download</a>
<br />
<ul>
<li>
Added more documentation and an example.
</li>
<li>
Load the base worker library via a standard importScripts, instead of url blob.
</li>
<li>
The bridge now supports connecting to any MQTT server.
</li>
<li>
Removed the embedded polyfills, and use an external polyfill provider when needed.
Mostly for IE11.
</li>
</ul>
</p>
<p id="1.0.2">
<strong class="header">1.0.2</strong> &mdash; <small><i>Feb 10, 2020</i></small> &mdash;
<a href="https://github.com/cotonic/cotonic/compare/1.0.1...1.0.2">Diff</a> &mdash;
<a href="https://ghcdn.rawgit.org/cotonic/cotonic/1.0.2/index.html"l>Docs</a> &mdash;
<a href="https://github.com/cotonic/cotonic/archive/1.0.2.tar.gz" download>Download</a>
<br />
<ul>
<li>
Added documentation, and
</li>
<li>
made it possible to use the bridge to connect to mqtt servers
</li>
</ul>
</p>
<p id="1.0.1">
<strong class="header">1.0.1</strong> &mdash; <small><i>Jan 30, 2020</i></small> &mdash;
<a href="https://github.com/cotonic/cotonic/compare/1.0.0...1.0.1">Diff</a> &mdash;
<a href="https://ghcdn.rawgit.org/cotonic/cotonic/1.0.1/index.html"l>Docs</a> &mdash;
<a href="https://github.com/cotonic/cotonic/archive/1.0.1.tar.gz" download>Download</a>
<br />
<ul>
<li>
Fixed all the tests, and
</li>
<li>
made the documentation more readable on mobile.
</li>
</ul>
</p>
<p id="1.0.0">
<strong class="header">1.0.0</strong> &mdash; <small><i>Jan 23, 2020</i></small> &mdash;
<a href="https://ghcdn.rawgit.org/cotonic/cotonic/1.0.0/index.html">Docs</a> &mdash;
<a href="https://github.com/cotonic/cotonic/archive/1.0.0.tar.gz" download>Download</a>
<br />
<ul>
<li>
First Release!
</li>
</ul>
</p>
</section>
<!-- Load cotonic to be able to play with it in the console -->
<script>
window.cotonic = window.cotonic || {};
window.cotonic.config = {
start_service_worker: false,
base_worker_src: "/cotonic-worker.js"
}
</script>
<script type="text/javascript" src="/cotonic.js"></script>
<script>
console.log("Welcome to COTONIC. You can try the api in the console");
console.log("------------------------------------------------------");
console.log("You can start an example worker by calling");
console.log("cotonic.spawn(\"/examples/empty-worker.js\")");
console.log("With the console you can select the calling");
console.log("context of the worker and run the examples found here.");
</script>
</body>
</html>