Node-RED与uibuilder构建自定义UI

微信ID: Van1sh1ngAct

简介

Node-RED是一款可以进行可视化编程的低代码工具, 在快速构建原型和做小型应用有着较大优势. 在Node-RED中构建图形化(GUI)界面通常使用Dashboard完成, 其UI简约好看, 但其界面无法自定义, 只能使用现有的节点组件, 对于特殊界面无法满足. 因此Node-RED社区推出了uibuilder. 其可以使用HTML/JS/CSS等自定义构建页面, 同时也可以引入其它框架(Vue, React等)和组件库(Vue-Bootstrap等), 在通讯层面则通过封装的socket.io与Node-RED通讯. 在本文中就uibuilder与Node-RED的使用做出简要说明.

uibuilder安装

  1. 点击右上角打开菜单, 进入节点管理页面


    节点管理
  2. 点击安装, 在输入框输入uibuilder, 点击安装
    搜索安装

    [注] 如果安装遇到问题, 切换网络再次尝试, 如果仍有问题, 可以参照官网安装教程

uibuilder初始项目解析

实例化uibuilder

从左侧找到uibuilder节点, 双击进行配置.
URL为访问地址, 不可重复. 配置完成后, 点击完成.


image.png

再点击部署后, uibuilder即可正常使用. 访问对应的URL即可看到如下页面.


image.png

文件结构

如果Template 选择的是 'Blank template, no framework' 即 空白模板, 不使用框架. 则uibuilder对于该节点仅有三个文件

  • index.html 页面结构
  • index.js 完成对应功能
  • index.css 页面样式(美化)
    image.png

页面结构

<!doctype html>
<html lang="en"><head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Blank template - Node-RED uibuilder</title>
    <meta name="description" content="Node-RED uibuilder - Blank template">
    <link rel="icon" href="./images/node-blue.ico">
    <link type="text/css" rel="stylesheet" href="./index.css" media="all">
</head><body class="uib">
    <h1>uibuilder Blank Template</h1>
    <button onclick="fnSendToNR('A message from the sharp end!')">Send a msg back to Node-RED</button>
    <pre id="msg" class="syntax-highlight">Waiting for a message from Node-RED</pre>
    <script src="../uibuilder/vendor/socket.io/socket.io.js"></script>
    <script src="./uibuilderfe.min.js"></script>
    <script src="./index.js"></script>
</body></html>

该HTML分为两部分<head><body>. <head>制定页面的元信息, 标题/ICON/引入样式等.
<body>为页面实际主体, 最主要的是<button><pre>; <button>即为按钮, 其onclick(点击)绑定为fnSendToNR('A message from the sharp end!'). 当点击该按钮时, 会调用函数fnSendToNR, 并且以'A message from the sharp end!'为参数.
<pre>为后续显示消息的容器, 绑定idmsg, 后续会根据id查找到该元素进行操作.
其次引入了三个外部的JavaScript文件, socket.io.js用于和Node-RED通信, uibuilderfe.min.jsuibuilder自身依赖提供简单易用接口, index.js为自定义的JavaScript文件.

页面功能

// Send a message back to Node-RED
window.fnSendToNR = function fnSendToNR(payload) {
    uibuilder.send({
        'topic': 'msg-from-uibuilder-front-end',
        'payload': payload,
    })
}
// run this function when the document is loaded
window.onload = function() {
    // Start up uibuilder - see the docs for the optional parameters
    uibuilder.start()

    // Listen for incoming messages from Node-RED
    uibuilder.onChange('msg', function(msg){
        console.info('[indexjs:uibuilder.onChange] msg received from Node-RED server:', msg)

        // dump the msg as text to the "msg" html element
        const eMsg = document.getElementById('msg')
        eMsg.innerHTML = window.syntaxHighlight(msg)
    })
}

index.js中较为核心的是两个函数fnSendToNRwindow.onload.
index.html中, <button>onclick属性绑定的方法具体实现就在这里. 调用该方法既是调用uibuild.send(该接口来自于uibuilderfe.min.js), 其向Node-RED发送一个对象, 其中payload对应函数的参数, 即index.html中的'A message from the sharp end!'; window.onload为一个回调函数, 当页面加载完成后会调用该函数, 在该函数中, 首先通过uibuilder.start()与Node-RED建立socket.io通信, 之后通过uibuilder.onChange('msg', function(msg){ ... })监听来自Node-RED的数据. 收到数据后, 首先通过document.getElementById获取到放置消息的容器, 之后通过eMsg.innerHTML = window.syntaxHighlight(msg)将收到的数据放入该容器.

页面样式

@import url("./uib-styles.css");

页面样式较为简单, 仅引入了uibuilder公共样式.

案例

为了更完整的介绍uibuilder使用, 这里通过一个小案例引入. 假如我们需要实现一个显示当前温度的页面, 如下图(项目来自于CodePen).

image.png

其代码可以在CodePen找到.
首先将index.html的代码进行合并(删除原有buttonpre, 新增span, inputp).

<!doctype html>
<html lang="en"><head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Blank template - Node-RED uibuilder</title>
    <meta name="description" content="Node-RED uibuilder - Blank template">
    <link rel="icon" href="./images/node-blue.ico">
    <link type="text/css" rel="stylesheet" href="./index.css" media="all">
</head><body class="uib">
    <span class="emoji" role="img" aria-label="happy face">😊</span>
    <input type="range" class="slider" min="0" max="40" value="20" aria-label="temperature in degrees celsius">
    <p class="temperature"><span class="temperature-output">20</span>&deg;C</p>
    <script src="../uibuilder/vendor/socket.io/socket.io.js"></script>
    <script src="./uibuilderfe.min.js"></script>
    <script src="./index.js"></script>
</body></html>

修改index.js

document.addEventListener("DOMContentLoaded", () => {
    const emoji = document.querySelector('.emoji'),
        slider = document.querySelector('.slider'),
        tempOutput = document.querySelector('.temperature-output'),
        displayTemp = temperature => {
            //Display temperature
            tempOutput.textContent = temperature;

            //Display emoji
            if (temperature >= 0 && temperature <= 8) {
                emoji.textContent = '🥶';
                emoji.setAttribute('aria-label', 'freezing face');
            } else if (temperature > 8 && temperature <= 16) {
                emoji.textContent = '😬';
                emoji.setAttribute('aria-label', 'cold face');
            } else if (temperature > 16 && temperature <= 24) {
                emoji.textContent = '😊';
                emoji.setAttribute('aria-label', 'happy face');
            } else if (temperature > 24 && temperature <= 32) {
                emoji.textContent = '😅';
                emoji.setAttribute('aria-label', 'warm face');
            } else {
                emoji.textContent = '🥵';
                emoji.setAttribute('aria-label', 'hot face');
            }

            uibuilder.send({
                'topic': 'msg-from-uibuilder-front-end',
                'payload': temperature,
            })
        }

    // Start up uibuilder - see the docs for the optional parameters
    uibuilder.start()

    // Listen for incoming messages from Node-RED
    uibuilder.onChange('msg', function (msg) {
        console.info('[indexjs:uibuilder.onChange] msg received from Node-RED server:', msg)

        // dump the msg as text to the "msg" html element

        displayTemp(msg.payload);
        slider.value = msg.payload;
        
        // const eMsg = document.getElementById('msg')
        // eMsg.innerHTML = window.syntaxHighlight(msg)
    })
    slider.addEventListener('input', () => displayTemp(slider.value));
});

// Send a message back to Node-RED
window.fnSendToNR = function fnSendToNR(payload) {
    uibuilder.send({
        'topic': 'msg-from-uibuilder-front-end',
        'payload': payload,
    })
}

替换index.css

:root {
    font-size: 20vmin;
}
body {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    height: 100vh;
}
.emoji {
    font-size: 1em;
    margin-bottom: 0.3em;
    text-align: center;
    text-shadow: 0.02em 0.02em 0.02em rgba(0, 0, 0, 0.3);
}
.slider {
    font:inherit;
    width: 4em;
    height: 0.2em;
    border-radius: 1em;  
    background-image: linear-gradient(90deg, #384bdc, #33994a, #df3b33); 
    box-shadow: inset 0 0 0.05em rgba(0, 0, 0, 0.6);
    -webkit-appearance: none;
       -moz-appearance: none;
            appearance: none;
}
.slider::-webkit-slider-thumb {
    position: relative;
    width: 0.25em;
    height: 0.38em;
    border-radius: 0.08em; 
    background-image: radial-gradient(#eee, #ccc);    
    filter: drop-shadow(0.02em 0.02em 0.02em rgba(0, 0, 0, 0.5));
    cursor: pointer;
    -webkit-appearance: none;
            appearance: none;
}
.slider::-moz-range-thumb {
    position: relative;
    width: 0.25em;
    height: 0.38em;
    border-radius: 0.08em; 
    background-image: radial-gradient(#eee, #bbb);    
    filter: drop-shadow(0.02em 0.02em 0.02em rgba(0, 0, 0, 0.5));
    cursor: pointer;
    border: none;
    -moz-appearance: none;
         appearance: none;
}
.temperature {
    font-family: 'Open Sans', Arial, sans-serif;
    font-size: 0.5em;
    font-weight: 400;
    margin-top: 0.45em;
    color: #111;
    text-shadow: 0.02em 0.02em 0.02em rgba(0, 0, 0, 0.1);
}
image.png

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容