Current section

Files

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

priv/lib-src/cotonic/examples/chat/index.html

<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://unpkg.com/turretcss@5.2.1/dist/turretcss.min.css">
<link rel="stylesheet" href="style.css">
</head>
<body id="main">
<nav class="nav-inline background-light-100 box-shadow-xxl">
<ul>
<li><a href="/" class="brand">
<img class="logo" src="/doc/favicon/apple-touch-icon.png" />
<span>Cotonic</span>
</a>
</li>
<li>
<span>Chat Example</span>
</li>
</ul>
</nav>
<main style="padding: 30px;">
<div class="container">
<p>
Chat with any tab connected to the same broker and mqtt topic.
</p>
<p>
This example connects the page to <a href="https://mqtt.eclipseprojects.io"><code>mqtt.eclipseprojects.io</code></a>,
a public, test MQTT broker service. And subscribes to topic <code>cotonic/chat</code>. Chat messages
which are published on this topic will be displayed in the chat box.
</p>
<p>
You can open <a href="." target="_blank">another tab</a> and chat with yourself if nobody is online.
Or examine the
<a href="https://github.com/cotonic/cotonic/tree/master/examples/chat" target="chat_example_code">code</a>
</a>
<p>
<hr />
</p>
</div>
<div class="chat-box box-shadow-xxl">
<header class="background-primary-200 padding-xs">
<!-- publish the data of the form on local topic "chat/nick" -->
<form data-onsubmit-topic="chat/nick" data-onsubmit-cancel="preventDefault">
<div class="input-group">
<input id="nick" name="nick" value="" />
<button class="button button-primary" type="submit">Nickname</button>
</div>
</form>
</header>
<div id="bubble-container" class="padding-xs">
</div>
<footer class="background-primary-200 padding-xs">
<!-- publish the data of the form on local topic "chat/message" -->
<form data-onsubmit-topic="chat/message"
data-onsubmit-reset
data-onsubmit-cancel="preventDefault">
<div class="input-group">
<input name="message" placeholder="Type a message.." autofocus />
<button type="submit" class="button button-primary">Send</button>
</div>
</form>
</footer>
</div>
<script type="text/javascript" src="/cotonic.js" data-base-worker-src="/cotonic-worker.js"></script>
<script type="text/javascript">
const messages = [];
const self_id = Math.floor(Math.random() * 1000000);
const broker = "mqtt.eclipseprojects.io";
const broker_path = "/mqtt";
const presetNicks = ["Joe", "Mike", "Robert", "Micky", "Maus", "Donald", "Bruce", "Wayne", "Clark",
"Kent", "Sarah", "Connor", "Mary", "Shelley", "Rosemary", "Wilma", "Louis", "Selina",
"Barbara", "Gordon", "Herbert", "The Count"];
let nick = presetNicks[Math.floor(Math.random() * presetNicks.length)];
document.getElementById("nick").setAttribute("value", nick);
/**
* Register the bubble-container with the user interface composer.
* See: https://cotonic.org/#model.ui
*/
cotonic.broker.publish("model/ui/insert/bubble-container", {
priority: 10,
initialData: view(),
inner: true
});
/**
* Update the view. Publishes the updated bubble view, and scrolls
* it to the bottom.
*/
function updateView() {
cotonic.broker.publish("model/ui/update/bubble-container", view());
}
/**
* Publish a system messages. The will be displayed centered in the
* chat.
*/
function systemMessage(msg) {
messages.push({user_id: "$sys", msg: msg});
updateView();
}
/**
* Scroll the messages to the bottom when the view is updated.
*/
cotonic.broker.subscribe("model/ui/event/dom-updated/bubble-container",
function(m) {
const c = document.getElementById("bubble-container");
c.scrollTop = c.scrollHeight
});
/**
* Subscribe to the local topic "chat/nick", When the user
* submits the form to change the nickname, it will save
* the nickname in the local state of the chat.
* The new nickname will be used for the next chat message.
*/
cotonic.broker.subscribe("chat/nick", function(m) {
nick = m.payload.value.nick;
});
/**
* Subscribe to the local topic "chat/message". When the user
* presses sent, the data in the form will be published on
* this topic, it will then be re-published on the bridge.
* When the bridge is connected, this message will be published
* on the remote broker.
*/
cotonic.broker.subscribe("chat/message", function(m) {
const msg = m.payload.value.message;
cotonic.broker.publish("bridge/broker/cotonic/chat",
{msg: msg, user_id: self_id, nick: nick});
})
/**
* Subscribe to the broker status. Its status will be published on a topic.
* Sends system messages to indicate to the user what state the bridge has.
*/
cotonic.broker.subscribe("$bridge/broker/status", function(m) {
if(m.payload.hasOwnProperty("is_connected")) {
systemMessage(
m.payload.is_connected ? "You are now connected, and subscribed to topic \"cotonic/chat\"."
: "Currently disconnected.");
}
})
/**
* Send a system message we are going to connect to the broker
* The option 'is_ui_state' tells the bridge to publish its state to the ui model so that on the html tag
* the 'ui-state-bridge-connected' class and the attribute data-ui-state-bridge-connection is set.
*/
systemMessage("Connecting to " + broker);
cotonic.mqtt_bridge.newBridge(broker, { name: 'broker', protocol: "wss", controller_path: broker_path, is_ui_state: true });
/**
* Subscribe to the "cotonic/chat" topic via the broker. All messages, including your own
* will be received here. The message will be added to the local chat state, and the
* view will be updated.
*/
cotonic.broker.subscribe("bridge/broker/cotonic/chat",
function(msg, inf) {
/*
* Filter out messages from the local broker. We want messages from
* the remote broker only. These have the packet_id property set, but
* it could be null.
*/
if(!msg.hasOwnProperty("packet_id")) {
if(!msg.payload.msg) return;
messages.push(msg.payload);
updateView();
return;
}
let p = msg.payload;
// Check if this is a chat message.
if(!(p.nick && p.msg))
return;
// When it is a message from ourself, don't display it
// the message was already inserted.
if(p.user_id === self_id)
return;
messages.push(p);
updateView();
});
/**
* Create the message view. Produces an array of tokens which can be used
* by the user interface composer.
*/
function view() {
const tokens = [];
let current_id = null;
const div = {type: "open", tag: "div"}
const close_div = {type: "close", tag: "div"}
const container_self = {type: "open", tag: "div", attributes: ["class", "bubble-container self"]};
const group_self = {type: "open", tag: "div", attributes: ["class", "bubble-group self"]};
const container_other = {type: "open", tag: "div", attributes: ["class", "bubble-container other"]};
const group_other = {type: "open", tag: "div", attributes: ["class", "bubble-group other"]};
const container_sys = {type: "open", tag: "div", attributes: ["class", "bubble-container system"]};
const group_sys = {type: "open", tag: "div", attributes: ["class", "bubble-group system"]};
const close_span = {type: "close", tag: "span"};
for(let i = 0; i < messages.length; i++) {
const m = messages[i];
if(m.user_id !== current_id) {
if(current_id !== null) {
// close the current container
tokens.push(close_div);
tokens.push(close_div);
}
if(m.user_id === self_id) {
tokens.push(container_self);
tokens.push(group_self);
} else if(m.user_id === "$sys") {
tokens.push(container_sys);
tokens.push(group_sys);
} else {
tokens.push(container_other);
tokens.push(group_other);
tokens.push(div);
tokens.push({type: "open", tag: "span", attributes: ["class", "bubble-name"]});
tokens.push({type: "text", data: m.nick});
tokens.push(close_span);
tokens.push(close_div);
}
current_id = m.user_id;
}
if(m.user_id === self_id) {
tokens.push({type: "open", tag: "div", attributes: ["class", "bubble self"]});
} else if (m.user_id === "$sys") {
tokens.push({type: "open", tag: "div", attributes: ["class", "bubble system"]});
} else {
tokens.push({type: "open", tag: "div", attributes: ["class", "bubble other"]});
}
tokens.push({type: "open", tag: "span"});
tokens.push({type: "text", data: m.msg});
tokens.push(close_span);
tokens.push(close_div);
}
return tokens;
}
</script>
</body>
</html>