This is my first JS project. So I'm sure there is lots of things to improve. The goal of the script is to ease communication with the cloud as well as to be simple and extensible. It will be used to control a few particle photons.The used API is documented here. A short summary of the used functions is:particle.login: -> returns a token for communication, this token is good for multiple sessions, therefore I store it in a cookie (is this a security concern?)particle.listDevices: -> Returns a list of devices as well as their (connection) statusparticle.callFunction: -> Call a function at the cloud with an argument. I use this to communicate with the cloud, resp individual devices.particle.readVariable: -> Reads a variable from a device in JSON formFor each device I differentiate between read-only values and settable values which each also have a read-back. The idea is that I can connect more and more devices to this script without needing to change it due to the fact that the html is build by js on the ground of what the device returns.A few more things:I have the feeling I'm using the promises incorrectly. They feel clumsy  this way.I do have an infinite recursion going on since I couldn't find a javascript sleep function. Is there a better way?I probaply used a fair bit of python/C++ idioms which I pressed into JSAnd lots more so please don't hold back...<!DOCTYPE html><html lang=en><head><meta charset=utf-8><script type=text/javascript src=http://cdn.jsdelivr.net/particle-api-js/5/particle.min.js></script><style>ul {    list-style-type: none;    padding: 0;    overflow: hidden;    background-color: #A0FFA0;} ul.disconnected {    background-color: #FFA0A0;}li {    float: left;} li.active {    color: #F0C0C0}a {    display: inline-block;    color: Black;    text-align: center;    padding: 14px 16px;    text-decoration: none;} a.select {    background-color: #A0C0A0;    font-weight: bold;} a.DeviceTitle {    background-color: black;    color: white;    font-weight: bold;}table {    table-layout:fixed;    width:300px;    height:20px;    font-size: 20px;} table.hidden {    display: none;}td {    width: 100px;} td.double {    width: 200px;}textarea {    font-size: 20px;}input {    font-size: 20px;    width: 100px;    border-style: solid;    background-color: transparent;    border-width: 0px 0px 1px 0px;} input:focus {    background-color: #CCCCCC;    outline: 0;} input.button {    width: 200px;    background-color: #CCCCCC;}</style></head><body><table id=loginTable class=hidden>    <tr><td>Username:</td><td><input id=username /></td></tr>    <tr><td>Password:</td><td><input id=password, type=password /></td></tr>    <tr><td><input class=button type=button onclick=login() value=Submit></td></tr></table><div id='root'></div></body><script>'use strict';// I just let this one in for debug purposesif(typeof console === undefined) {console = {log: function() {}};}/******* Manage the display stuff *******/function panic(device) {    // We killed the readback, we still leave write capabilities intact.    // In order to inform the user we show navbar disconnect.    device.navBar.classList.add('disconnected');}function createNavBarOff(device, root) {    // Create a navbar just to show that we have a disconnected device.    // Nothing we can do with it.    var navbar = root.appendChild(document.createElement('ul'));    navbar.innerHTML = '<li><a class=DeviceTitle>' + device.name + '</a></li>';    navbar.classList.add('disconnected');}function createNavBar(device, root) {    // We create a nice navigation bar showing a title block and diffent modes    // Modes are defined by keys in readWrite    var navbar = root.appendChild(document.createElement('ul'));    navbar.innerHTML = '<li><a class=DeviceTitle>' + device.name + '</a></li>';    device.navBar = navbar;    device.modeElements = {};    var callback = function () {        send(device, this.mode);    };    for (var mode in device.readWrite) {        var li = navbar.appendChild(document.createElement('li'))        var a = li.appendChild(document.createElement('a'));        a.onclick = callback.bind({mode: mode});        a.innerHTML = mode;        a.href='#';        device.modeElements[mode] = a;    }}function createRead(device, root) {    // Creates the read table, which consists of two cols (name/readback)    var table = root.appendChild(document.createElement('table'));    device.readElements = {};    for (var key in device.read) {        var row = table.appendChild(document.createElement('tr'));        row.innerHTML = '<td>' + key + '</td>';        device.readElements[key] = row.appendChild(document.createElement('td'));        device.readElements[key].classList = 'double';    }}function navCallback(device, mode) {    // First we disselect the old topic (if there was one) and then we select    // the new one. This means mark the selected and create a new read/write    for (var key in device.modeElements) {        if (key == mode) {            device.readWriteTable[key].classList = [];            device.modeElements[key].classList = 'select'        } else {            device.readWriteTable[key].classList = 'hidden';            device.modeElements[key].classList = [];        }    }}function createReadWriteTable(device, mode, table) {    // Create a single readWrite table    var element = {}    var callback = function(event) {        // Cmd will be send as mode.key,value        event.preventDefault();        if (event.keyCode == 13) {            send(device, mode + '.' + this.key + ',' + event.srcElement.value);        }    };    for (var key in device.readWrite[mode]) {        var row = table.appendChild(document.createElement('tr'));        row.innerHTML = '<td>' + key + '</td>';        element[key] = row.appendChild(document.createElement('td'));        var write = row.appendChild(document.createElement('td'));        var input = write.appendChild(document.createElement('input'));        input.addEventListener(keyup, callback.bind({key: key}));    }    return element;}function createReadWriteTables(device, root) {    // Create readWrite table for all the modes.    device.readWriteElements = {}    device.readWriteTable = {}    for (var mode in device.readWrite) {        var table = root.appendChild(document.createElement('table'));        device.readWriteTable[mode] = table;        device.readWriteElements[mode] =            createReadWriteTable(device, mode, table);    }}function propagateReadBack(device) {    // Update all visible readbacks.    // We also change topic depending on what's set on device - This can come    // from the client or the photon.    for (var key in device.read) {        device.readElements[key].innerHTML = device.read[key];    }    navCallback(device, device.read.selected);    var readWrite = device.readWrite[device.read.selected];    var element = device.readWriteElements[device.read.selected];    for (var key in readWrite) {        element[key].innerHTML = readWrite[key];    }}/*** particle communication ***/function setup_devices(token) {    // Iterate over all devices, read them once and create the needed graphics    // elements. It also starts a read timer which calls itself.    // Note that devices which are not connected stay so until page refresh.    // In case we fail with the setup we assume that our token expired, so    // lets get a new one.    particle.listDevices({auth: token}).then(function(data) {        var root = document.getElementById('root');        for (var i = 0; i < data.body.length; i++) {            var device = data.body[i];            if (device.connected) {                console.log(Add connected device, device);                device.auth = token;                read(device, function(device) {                    createNavBar(device, root);                    createRead(device, root);                    createReadWriteTables(device, root);                    root.appendChild(document.createElement(br));                    propagateReadBack(device);                    reload(device);                });            } else {                console.log(Not connected, device);                createNavBarOff(device, root);                root.appendChild(document.createElement(br));            }        }        }, function(err) {            console.log('Device setup error: ', err);            document.getElementById('loginTable').classList = [];        });}function reload(device) {    // !!! Infinite recursion of read !!! seems to be manageble for this small    // scale. Due to network delays this isn't a busy loop.    read(device, function() {        propagateReadBack(device);        setTimeout(function() {reload(device);}, 10);    });}function send(device, cmd, fun_name='set') {    // Send a command to a device. In case we fail, we report and leave it be.    particle.callFunction({        'deviceId': device.id,        'name': fun_name,        'argument': cmd,        'auth': device.auth}).then(function (data) {        console.log(device.name + ' received: ' + cmd, data);    }, function(err) {        console.log(device.name + ' didn\'t receive: ', err);    });}function read(device, lambda, arg_name='status') {    // Read string from device. We expect the result to be in json format of    // the following form: [readonly, readWrite] =    // [{ReadOnly1: 1, ...}, {Mode1:{ReadWrite1: 1, ...}, ...}]    // After we succesfully read something we call the supplied lambda    // function. Note that in case of failure stop retries    if (STOP) {return;}    particle.getVariable({        'deviceId': device.id,        'name': arg_name,        'auth': device.auth }).then(function (data) {        var raw = JSON.parse(data.body.result);        console.log('Read data from ' + device.name, raw);        [device.read, device.readWrite] = raw;        lambda(device);    }, function(err) {        console.log('Read error from: ', err);        panic(device);    });}function login() {    // Login into the page and create a cookie with our token. Tokens should    // be good for about 90d. In case of login failure (for whatever reason)    // we reiterate.    document.getElementById('loginTable').classList = 'hidden';    particle.login({        username: document.getElementById('username').value,        password: document.getElementById('password').value}).then(function(data) {        document.cookie = 'token=' + data.body.access_token;        console.log('Login success, token=' + data.body.access_token);        main();    }, function (err) {        document.getElementById('loginTable').classList = [];        console.log('Login failure ', err);    });}function main() {    // Tries to load the cookie with the appropriate Token. In case no cookie    // is found, we redirect to login and recursively call ourself.    // In case of sucess we start creating the page.    var token = document.cookie.match('token=[^;]+')    if (token === null) {        document.getElementById('loginTable').classList = [];    } else {        setup_devices(token[0].split('=')[1]);    }}var particle = new Particle();var STOP = false; // Used to stop the read functionmain();</script></html>
Simple JS control script
javascript;beginner