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节点, 双击进行配置.
URL为访问地址, 不可重复. 配置完成后, 点击完成.

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

如果Template 选择的是 ‘Blank template, no framework’ 即 空白模板, 不使用框架. 则uibuilder对于该节点仅有三个文件
index.html 页面结构index.js 完成对应功能index.css 页面样式(美化)
<!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>为后续显示消息的容器, 绑定id为msg, 后续会根据id查找到该元素进行操作.
其次引入了三个外部的JavaScript文件, socket.io.js用于和Node-RED通信, uibuilderfe.min.js为uibuilder自身依赖提供简单易用接口, 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中较为核心的是两个函数fnSendToNR 与 window.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).

其代码可以在CodePen找到.
首先将index.html的代码进行合并(删除原有button及pre, 新增span, input及p).
<!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>°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);
}


我正在使用i18n从头开始构建一个多语言网络应用程序,虽然我自己可以处理一大堆yml文件,但我说的语言(非常)有限,最终我想寻求外部帮助帮助。我想知道这里是否有人在使用UI插件/gem(与django上的django-rosetta不同)来处理多个翻译器,其中一些翻译器不愿意或无法处理存储库中的100多个文件,处理语言数据。谢谢&问候,安德拉斯(如果您已经在rubyonrails-talk上遇到了这个问题,我们深表歉意) 最佳答案 有一个rails3branchofthetolkgem在github上。您可以通过在Gemfi
我正在尝试设置一个puppet节点,但rubygems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由rubygems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby
我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer
我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢
我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby1.9+ 关于ruby-主要:Objectwhenrun
我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin
我有一个只接受一个参数的方法:defmy_method(number)end如果使用number调用方法,我该如何引发错误??通常,我如何定义方法参数的条件?比如我想在调用的时候报错:my_method(1) 最佳答案 您可以添加guard在函数的开头,如果参数无效则引发异常。例如:defmy_method(number)failArgumentError,"Inputshouldbegreaterthanorequalto2"ifnumbereputse.messageend#=>Inputshouldbegreaterthano
我使用Ember作为我的前端和GrapeAPI来为我的API提供服务。前端发送类似:{"service"=>{"name"=>"Name","duration"=>"30","user"=>nil,"organization"=>"org","category"=>nil,"description"=>"description","disabled"=>true,"color"=>nil,"availabilities"=>[{"day"=>"Saturday","enabled"=>false,"timeSlots"=>[{"startAt"=>"09:00AM","endAt"=>
我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c
我正在阅读一本关于Ruby的书,作者在编写类初始化定义时使用的形式与他在本书前几节中使用的形式略有不同。它看起来像这样:classTicketattr_accessor:venue,:datedefinitialize(venue,date)self.venue=venueself.date=dateendend在本书的前几节中,它的定义如下:classTicketattr_accessor:venue,:datedefinitialize(venue,date)@venue=venue@date=dateendend在第一个示例中使用setter方法与在第二个示例中使用实例变量之间是