+// Open Websocket back to ControlPi we were loaded from:
+const websocket = new WebSocket("ws://" + window.location.host)
+
+// When Websocket is ready request all clients from bus:
+websocket.addEventListener('open', function (event) {
+ websocket.send(JSON.stringify({target: '', command: 'get clients'}))
+})
+
+// When receiving message from ControlPi through Websocket:
+websocket.addEventListener('message', function (event) {
+ const message = JSON.parse(event.data)
+ if (message['sender'] == '') {
+ processBusMessage(message)
+ } else {
+ processClientMessage(message)
+ }
+})
+
+// Remove (if unregistered) or create (if not existent) elements for
+// clients and update interface information:
+function processBusMessage(message) {
+ if (message['event'] == 'unregistered') {
+ // On deregistration delete client element if it exists:
+ const clientElement = document.getElementById(message['client'])
+ if (clientElement != null) {
+ clientElement.remove()
+ }
+ } else {
+ // On registration or 'get clients' answer:
+ const clientElement = document.getElementById(message['client'])
+ if (clientElement == null) {
+ // Create element for client if not existent:
+ const main = document.getElementById('ControlPi Debug')
+ main.appendChild(createClient(message['client'],
+ message['plugin']))
+ }
+ // Crate message elements for receives interface:
+ const receiveContainer = document.getElementById(message['client'] + ' Receives')
+ receiveContainer.innerHTML = ''
+ for (const template of message['receives']) {
+ if (template['command'] != null) {
+ receiveContainer.appendChild(createForCommand(template))
+ } else {
+ receiveContainer.appendChild(createObject(template, 'template'))
+ }
+ }
+ // Create message elements for sends interface:
+ const sendContainer = document.getElementById(message['client'] + ' Sends')
+ sendContainer.innerHTML = ''
+ for (const template of message['sends']) {
+ sendContainer.appendChild(createObject(template, 'template'))
+ }
+ }
+}
+
+// Create element for client (if not existent)
+// and update last received message:
+function processClientMessage(message) {
+ const clientElement = document.getElementById(message['sender'])
+ if (clientElement == null) {
+ // Create section for client if not existent:
+ const main = document.getElementById('ControlPi Debug')
+ main.appendChild(createClient(message.sender, ''))
+ }
+ // Update last received message:
+ const lastContainer = document.getElementById(message['sender'] + ' Last')
+ lastContainer.innerHTML = ''
+ lastContainer.appendChild(createObject(message, 'message'))
+}
+
// Create section for client:
-function createForClient(client, plugin) {
+function createClient(client, plugin) {
const section = document.createElement('section')
section.setAttribute('id', client)
section.setAttribute('class', 'client')
const headingH2 = document.createElement('h2')
headingH2.appendChild(document.createTextNode(client))
heading.appendChild(headingH2)
- const headingH3 = document.createElement('h3')
- headingH3.appendChild(document.createTextNode(plugin))
- heading.appendChild(headingH3)
+ const headingH4 = document.createElement('h4')
+ headingH4.appendChild(document.createTextNode(plugin))
+ heading.appendChild(headingH4)
section.appendChild(heading)
const receiveOuter = document.createElement('div')
receiveOuter.setAttribute('class', 'interfacecontainer')
return section
}
-// Create div and table for template:
-function createForTemplate(template) {
- const div = document.createElement('div')
- div.setAttribute('class', 'message')
- if (Object.keys(template).length === 0) {
- // Create span with '*' for empty templates:
- const span = document.createElement('span')
- const spanContent = document.createTextNode('*')
- span.appendChild(spanContent)
- div.appendChild(span)
+// Create structure for arbirary JSON element:
+function create(thing, parentType) {
+ if (typeof(thing) == 'object') {
+ if (Array.isArray(thing)) {
+ return createArray(thing, parentType)
+ } else if (parentType == 'template') {
+ if ('const' in thing) {
+ return create(thing['const'])
+ }
+ if ('type' in thing) {
+ if (thing['type'] == 'object') {
+ return createObject(thing, type)
+ } else {
+ const valueDiv = document.createElement('div')
+ valueDiv.setAttribute('class', 'value')
+ valueDiv.appendChild(document.createTextNode(
+ '<' + thing['type'] + '>'))
+ return valueDiv
+ }
+ }
+ } else {
+ type = parentType
+ if (parentType == 'message') {
+ type = ''
+ }
+ return createObject(thing, type)
+ }
} else {
- const table = document.createElement('table')
- for (const key in template) {
- // Append table row for key-value pair:
- const tr = document.createElement('tr')
- const keyTd = document.createElement('td')
- const keyTdContent = document.createTextNode(key + ':')
- keyTd.appendChild(keyTdContent)
- tr.appendChild(keyTd)
- const valueTd = document.createElement('td')
- schema = template[key]
- value = ''
- if ('const' in schema) {
- value = JSON.stringify(schema['const'])
- } else {
- value = JSON.stringify(schema)
+ const valueDiv = document.createElement('div')
+ valueDiv.setAttribute('class', 'value')
+ valueDiv.appendChild(document.createTextNode(JSON.stringify(thing)))
+ return valueDiv
+ }
+}
+
+// Create structure for array:
+function createArray(array, parentType) {
+ arrayDiv = document.createElement('div')
+ arrayDiv.setAttribute('class', 'array')
+ arrayDiv.appendChild(document.createTextNode('['))
+ var counter = array.length
+ for (const item of array) {
+ arrayDiv.appendChild(create(item, parentType))
+ if (--counter) {
+ arrayDiv.appendChild(document.createTextNode(', '))
+ }
+ }
+ arrayDiv.appendChild(document.createTextNode(']'))
+}
+
+// Create structure for object:
+function createObject(object, type) {
+ const objectDiv = document.createElement('div')
+ objectDiv.setAttribute('class', 'object')
+ if (type == 'message') {
+ // Current (receive) time as heading:
+ const time = new Date().toLocaleTimeString()
+ const h4 = document.createElement('h4')
+ const h4Content = document.createTextNode(time)
+ h4.appendChild(h4Content)
+ objectDiv.appendChild(h4)
+ }
+ if (type == 'template') {
+ if (Object.keys(object).length === 0) {
+ // Create span with '*' for empty templates:
+ const wildcard = document.createElement('h2')
+ wildcard.appendChild(document.createTextNode('*'))
+ objectDiv.appendChild(wildcard)
+ }
+ }
+ for (const key in object) {
+ if (type == 'message') {
+ if (key == 'sender') {
+ // Ignore 'sender' for message
+ // (information redundantly present in client heading):
+ continue
+ } else if (key == 'state') {
+ // Set background according to state:
+ if (object[key] === true) {
+ objectDiv.classList.add('green')
+ } else if (object[key] === false) {
+ objectDiv.classList.add('red')
+ }
}
- const valueTdContent = document.createTextNode(value)
- valueTd.appendChild(valueTdContent)
- tr.appendChild(valueTd)
- table.appendChild(tr)
}
- div.appendChild(table)
+ // Create property div:
+ const propertyDiv = document.createElement('div')
+ propertyDiv.setAttribute('class', 'property')
+ // Create key div and append to property div:
+ const keyDiv = document.createElement('div')
+ keyDiv.setAttribute('class', 'key')
+ keyDiv.appendChild(document.createTextNode(key + ':'))
+ propertyDiv.appendChild(keyDiv)
+ // Create value element and append to property div:
+ propertyDiv.appendChild(create(object[key], type))
+ // Append property div to object div:
+ objectDiv.append(propertyDiv)
}
- return div
+ return objectDiv
}
-// Resize input fields to current input:
-function resizeInput() {
- const tmp = document.createElement('span')
- // HACK: Should get this from CSS rather than hardcode it:
- tmp.setAttribute('style', 'font-size: 0.8rem;')
- var value = this.value
- // HACK: 'number' fields for floats do not return trailing dot in
- // value, so we add one unconditionally:
- if (this.type == 'number' && this.step == 'any' && !value.includes('.')) {
- value += '.'
+// Create div, form and table for command template:
+function createForCommand(template) {
+ const div = document.createElement('div')
+ div.setAttribute('class', 'object')
+ const form = document.createElement('form')
+ form.addEventListener("submit", function(e) {
+ // Convert names and values of all form elements to key-value pairs
+ // and send as JSON over websocket:
+ e.preventDefault();
+ var data = {};
+ for (const element of form.elements) {
+ if (element.type == 'text' || element.type == 'hidden') {
+ data[element.name] = element.value
+ } else if (element.type == 'number') {
+ data[element.name] = element.valueAsNumber
+ } else if (element.type == 'select-one') {
+ if (element.value == 'true') {
+ data[element.name] = true
+ } else if (element.value == 'false') {
+ data[element.name] = false
+ } else {
+ data[element.name] = element.value
+ }
+ }
+ }
+ websocket.send(JSON.stringify(data))
+ })
+ const table = document.createElement('table')
+ for (const key in template) {
+ // Append table row for key-value pair:
+ const tr = document.createElement('tr')
+ const keyTd = document.createElement('td')
+ const keyTdContent = document.createTextNode(key + ':')
+ keyTd.appendChild(keyTdContent)
+ tr.appendChild(keyTd)
+ const valueTd = document.createElement('td')
+ for (const element of inputsForKeyValue(key, template[key])) {
+ valueTd.appendChild(element)
+ }
+ tr.appendChild(valueTd)
+ table.appendChild(tr)
}
- const tmpContent = document.createTextNode(value)
- tmp.appendChild(tmpContent)
- document.body.appendChild(tmp);
- const width = tmp.getBoundingClientRect().width
- document.body.removeChild(tmp);
- this.style.width = width + 'px'
+ form.appendChild(table)
+ div.appendChild(form)
+ return div
}
// Create form input (or select) for key-value pairs in receive templates:
return result
}
-// Create div, form and table for command template:
-function createForCommand(template) {
- const div = document.createElement('div')
- div.setAttribute('class', 'message')
- const form = document.createElement('form')
- form.addEventListener("submit", function(e) {
- // Convert names and values of all form elements to key-value pairs
- // and send as JSON over websocket:
- e.preventDefault();
- var data = {};
- for (const element of form.elements) {
- if (element.type == 'text' || element.type == 'hidden') {
- data[element.name] = element.value
- } else if (element.type == 'number') {
- data[element.name] = element.valueAsNumber
- } else if (element.type == 'select-one') {
- if (element.value == 'true') {
- data[element.name] = true
- } else if (element.value == 'false') {
- data[element.name] = false
- } else {
- data[element.name] = element.value
- }
- }
- }
- websocket.send(JSON.stringify(data))
- })
- const table = document.createElement('table')
- for (const key in template) {
- // Append table row for key-value pair:
- const tr = document.createElement('tr')
- const keyTd = document.createElement('td')
- const keyTdContent = document.createTextNode(key + ':')
- keyTd.appendChild(keyTdContent)
- tr.appendChild(keyTd)
- const valueTd = document.createElement('td')
- for (const element of inputsForKeyValue(key, template[key])) {
- valueTd.appendChild(element)
- }
- tr.appendChild(valueTd)
- table.appendChild(tr)
- }
- form.appendChild(table)
- div.appendChild(form)
- return div
-}
-
-// Remove (if unregistered) or create (if not existent) elements for
-// clients and update interface information:
-function processBusMessage(message) {
- if (message['event'] == 'unregistered') {
- // On deregistration delete client element if it exists:
- const clientElement = document.getElementById(message['client'])
- if (clientElement != null) {
- clientElement.remove()
- }
- } else {
- // On registration or 'get clients' answer:
- const clientElement = document.getElementById(message['client'])
- if (clientElement == null) {
- // Create element for client if not existent:
- const main = document.getElementById('ControlPi Debug')
- main.appendChild(createForClient(message['client'],
- message['plugin']))
- }
- // Crate message elements for receives interface:
- const receiveContainer = document.getElementById(message['client'] + ' Receives')
- receiveContainer.innerHTML = ''
- for (const template of message['receives']) {
- if (template['command'] != null) {
- receiveContainer.appendChild(createForCommand(template))
- } else {
- receiveContainer.appendChild(createForTemplate(template))
- }
- }
- // Create message elements for sends interface:
- const sendContainer = document.getElementById(message['client'] + ' Sends')
- sendContainer.innerHTML = ''
- for (const template of message['sends']) {
- sendContainer.appendChild(createForTemplate(template))
- }
- }
-}
-
-// Recursively create div structure for arrays, objects and primitive
-// values:
-function createForMessageValue(value) {
- var result = ''
- if (typeof(value) == 'object') {
- if (Array.isArray(value)) {
- result = document.createElement('div')
- result.setAttribute('class', 'array')
- var counter = value.length
- for (const child of value) {
- result.appendChild(createForMessageValue(child))
- if (--counter) {
- result.appendChild(document.createTextNode(', '))
- }
- }
- } else {
- result = document.createElement('div')
- result.setAttribute('class', 'object')
- for (const key in value) {
- const property = document.createElement('div')
- property.setAttribute('class', 'property')
- const keyDiv = document.createElement('div')
- keyDiv.setAttribute('class', 'key')
- keyDiv.appendChild(document.createTextNode(key + ':'))
- property.appendChild(keyDiv)
- const valueDiv = document.createElement('div')
- valueDiv.setAttribute('class', 'value')
- valueDiv.appendChild(createForMessageValue(value[key]))
- property.appendChild(valueDiv)
- result.appendChild(property)
- }
- }
- } else {
- result = document.createTextNode(JSON.stringify(value))
- }
- return result
-}
-
-// Create div and table for message:
-function createForMessage(message) {
- const div = document.createElement('div')
- div.setAttribute('class', 'message')
- // Current (receive) time as heading:
- const time = new Date().toLocaleTimeString()
- const h4 = document.createElement('h4')
- const h4Content = document.createTextNode(time)
- h4.appendChild(h4Content)
- div.appendChild(h4)
- const table = document.createElement('table')
- for (const key in message) {
- if (key == 'sender') {
- // Ignore 'sender' for last received messages
- // (information redundantly present in client heading):
- continue
- } else if (key == 'state') {
- // Set background according to state:
- if (message[key] === true) {
- div.classList.add('green')
- } else if (message[key] === false) {
- div.classList.add('red')
- }
- }
- // Append table row for key-value pair:
- const tr = document.createElement('tr')
- const keyTd = document.createElement('td')
- const keyTdContent = document.createTextNode(key + ':')
- keyTd.appendChild(keyTdContent)
- tr.appendChild(keyTd)
- const valueTd = document.createElement('td')
- valueTd.appendChild(createForMessageValue(message[key]))
- tr.appendChild(valueTd)
- table.appendChild(tr)
- }
- div.appendChild(table)
- return div
-}
-
-// Create element for client (if not existent)
-// and update last received message:
-function processClientMessage(message) {
- const clientElement = document.getElementById(message['sender'])
- if (clientElement == null) {
- // Create section for client if not existent:
- const main = document.getElementById('ControlPi Debug')
- main.appendChild(createForClient(message.sender, ''))
+// Resize input fields to current input:
+function resizeInput() {
+ const tmp = document.createElement('span')
+ tmp.setAttribute('class', 'inputsizespan')
+ // Space
+ var value = this.value.replace(/ /g, '\u00a0')
+ // HACK: 'number' fields for floats do not return trailing dot in
+ // value, so we add one unconditionally:
+ if (this.type == 'number' && this.step == 'any' && !value.includes('.')) {
+ value += '.'
}
- // Update last received message:
- const lastContainer = document.getElementById(message['sender'] + ' Last')
- lastContainer.innerHTML = ''
- lastContainer.appendChild(createForMessage(message))
+ const tmpContent = document.createTextNode(value)
+ tmp.appendChild(tmpContent)
+ document.body.appendChild(tmp);
+ const width = tmp.getBoundingClientRect().width
+ document.body.removeChild(tmp);
+ this.style.width = width + 'px'
}
-
-// Open Websocket back to ControlPi we were loaded from:
-const websocket = new WebSocket("ws://" + window.location.host)
-
-// When Websocket is ready request all clients from bus:
-websocket.addEventListener('open', function (event) {
- websocket.send(JSON.stringify({target: '', command: 'get clients'}))
-})
-
-// When receiving message from ControlPi through Websocket:
-websocket.addEventListener('message', function (event) {
- const message = JSON.parse(event.data)
- if (message['sender'] == '') {
- processBusMessage(message)
- } else {
- processClientMessage(message)
- }
-})