在这个迷你系列的第一部分中 ,我们讨论了我们正在构建的服务的详细信息以及您将学到的知识。 然后,我们介绍了为什么需要服务器以及为什么选择创建RESTful服务。 在讨论如何开发服务器时,我借此机会讨论了如何识别当前操作系统以及如何使用Node.js在其上运行命令。
在本系列的第二部分(也是最后一部分)中,您将发现如何构建客户端部分,以一种很好的方式向用户展示信息。 为了实现此目标,我们应该每X分钟(或几秒钟)更新一次电池的状态,而无需重新加载页面。 此外,我们应该能够暂停/恢复更新,以避免在不需要信息时,甚至在我们不在查看页面时,淹没我们的系统。 为此,我们将:
- 定期安排对我们的后端服务的Ajax调用;
- 使用声明性框架,以响应数据更改自动并有效地更新DOM;
- 使用一些jQuery实用程序函数使我们的生活更轻松;
- 使用一些不错的图像和CSS使仪表板具有视觉吸引力(作为奖励!)。
反应设计
当然,讨论Ajax和异步调用不在本文讨论范围之内(我将在文章末尾提供一些有用的链接)。 出于我们的目的,我们甚至可以将它们视为黑匣子,使我们可以向服务器询问一些数据,并在将数据发送回后执行一些操作。
让我们花一点时间来讨论反应式设计和声明性框架。
HTML页面默认为静态实体。 这意味着对于纯HTML页面,页面上显示的内容每次在浏览器中呈现时都保持不变。 但是,我们知道,使用JavaScript和某些模板库(例如Mustache)可以动态更新它们。
有许多库可帮助开发人员将数据绑定到DOM节点。 它们中的大多数使用JavaScript来描述应将数据转换为的DOM元素,并且要求页面的更新要手动触发(通过JavaScript)。 因此,我们最终依赖于应用程序的逻辑来决定何时应更新可视化效果以及应根据数据更改进行哪些更改。
声明性框架将数据绑定到DOM元素,并在每次数据更改时自动更新DOM。 还使用演示文稿中的模板(HTML标记)而不是JavaScript提供此绑定。
这些框架的附加值可以从几个关键点确定:
- 它们在内容和表示之间实现了更大程度的分离。 这是通过让您在表示层中为数据,事件处理程序乃至视图的结构(如迭代和组合对象,例如表)定义绑定来实现的;
- 它们提供了一种使您的数据模型和演示文稿保持同步的简便方法。
- 他们通常以一种非常有效的方式执行此操作,确保仅对DOM树的最小可能子集进行重排。 在这方面,请记住,重排和重绘通常是客户端浏览器应用程序的瓶颈。
Ractive.js
对于Ractive.js (我们将要使用的库),数据和DOM之间的同步是通过Container对象获得的。 该库创建包裹数据的对象。 这些对象可以访问数据,因此每次您设置或获取任何属性时,库都可以捕获您的操作并将其内部广播给所有订阅者。
动手
既然我们已经了解了Ractive.js的用处,现在是时候将第一个Ractive模板添加到页面中了。 为此,您可以在<body>
内的任意位置添加一个具有您选择的ID的脚本标签。 我建议您明智地选择ID,因为稍后我们会需要它。 我们还需要添加type='text/ractive'
属性:
<script id='meterVizTemplate' type='text/ractive'></script>
type='text/ractive'
实际上对您的浏览器毫无意义,因为除非您也将Ractive的脚本添加到页面中,否则它将忽略脚本:
<script src='http://cdn.ractivejs.org/latest/ractive.js'></script>
现在,在Ractive脚本中,您可以添加HTML标记,模板变量和条件/循环。 Ractive.js将负责评估{{}}
组内的所有内容。
<script id='meterVizTemplate' type='text/ractive'>
{{#batteryState}}
<br>
<div class='battery-div'>
<div class='battery-shell'>
<div class='battery-percent-text'>{{batteryPercent.toFixed(1) + '%'}}</div>
</div>
<div class='battery-level'>
<div class='battery-mask' style="width:{{(100 - batteryPercent) + '%'}};">
</div>
</div>
{{#batteryCharging}}
<div class='battery-plug' intro-outro='fade:1000'></div>
{{/batteryCharging}}
{{#batteryPercent <= batteryRedThreshold}}
<div class='battery-warning' intro-outro='fade:1000'></div>
{{/batteryLife}}
</div>
<br>
<br>
<span class='key'>Battery state:</span> <span class='value {{batteryStateClass(batteryState)}}'>{{batteryState}}</span>
<br>
{{#batteryLife}}
<span class='key'>Time to empty:</span> <span class='value {{batteryLifeClass(batteryPercent)}}'>{{batteryLife}}</span>
{{/batteryLife}}
{{/batteryState}}
{{^batteryState}}
<br>
LOADING...
{{/batteryState}}
</script>
在上面的示例中,您可以看到:
- 变量:
{{batteryState}}
- 条件:
{{#batteryState}}
- 函数调用:
{{batteryStateClass(batteryState)}}
为了使这些东西起作用,我们必须在JavaScript中添加一些绑定。 为此,我们必须创建一个新的Ractive.js对象:
ractive = new Ractive({
el: 'panels',
template: '#meterVizTemplate',
data: {
// Percentage at which the battery goes to 'red' zone (export for Ractive templates)
batteryRedThreshold: BATTERY_RED_THRESHOLD,
// Percentage at which the battery enters 'yellow' zone (export for Ractive templates)
batteryYellowThreshold: BATTERY_YELLOW_THRESHOLD,
// The capacity of the battery, in percentage. Initially empty
batteryPercent: NaN,
// How much more time can the battery last?
batteryLife: "",
// True <=> the update daemon for the battery has been paused
batteryPaused: false,
// True <=> the update daemon for the battery has reported an error at its last try
batteryUpdateError: false,
// Is the battery connected to power?
batteryCharging: false,
batteryStateClass: function (state) {
return state === 'discharging' ? BATTERY_RED_CLASS : BATTERY_GREEN_CLASS;
},
batteryLifeClass: function (percent) {
return percent <= BATTERY_RED_THRESHOLD ? BATTERY_RED_CLASS : (percent <= BATTERY_YELLOW_THRESHOLD ? BATTERY_YELLOW_CLASS : BATTERY_GREEN_CLASS);
}
}
});
我们传递给构造函数的选项非常重要。 首先, el
需要匹配DOM元素的ID,Ractive.js将在其中渲染模板。 在这种情况下,我们需要向HTML页面添加div
:
<div id='panels'></div>
插入此标签的时间很重要。 这将是Ractive.js模板系统呈现的所有元素的父元素。 您必须注意的第二个重要参数是template
。 它的值必须与您页面上的text/ractive
脚本的ID相匹配。 最后,我们为data
分配一个对象,该对象的键是我们在模板中引用的变量名或调用的函数。
使用Ractive.js,我们甚至可以定义库将响应的自定义事件:
ractive.on({
"battery-pause": function () {
clearInterval(batteryUpdateTimerId);
ractive.set('batteryPaused', true);
},
"battery-play": function () {
updateBatteryStatus(); //Checks the status immediately, then starts the daemon
batteryUpdateTimerId = setInterval(updateBatteryStatus, BATTERY_CHECK_INTERVAL);
ractive.set('batteryPaused', false);
}
});
在几行中,我们建立了一种机制来暂停/恢复我们的更新。 但是,我们仍然需要定义updateBatteryStatus()
函数。
异步检索数据
如所承诺的,这是一个负责从REST服务检索数据的功能。 通过使用jQuery Deferred对象 ,我们设置了从服务器接收到某些数据后立即调用的回调。 由于我们还在回调中使用了Ractive.js,因此我们不必了解更新表示层的逻辑。 实际上,我们只是更新模板脚本中使用的变量的值,而Ractive.js会处理所有事情。
我刚刚描述的内容是通过以下报告的代码实现的:
function updateBatteryStatus() {
$.getJSON(BATTERY_SERVICE_URL)
.then(function (battery) {
ractive.set('batteryUpdateError', false);
var batteryLife = battery.timeToEmpty,
batteryState = battery.state;
ractive.animate('batteryPercent', parseInt(battery.percentage, 10), {
easing: 'easeOut'
});
ractive.set('batteryLife', batteryLife);
ractive.set('batteryState', batteryState);
ractive.set('batteryCharging', batteryState !== BATTERY_STATE_DISCHARGING);
}).fail(function () {
ractive.set('batteryUpdateError', true);
});
}
//Start the daemons that will check the battery and networks status...
batteryUpdateTimerId = setInterval(updateBatteryStatus, BATTERY_CHECK_INTERVAL);
放在一起
当然,还有更多的布线可以使所有这些工作协同工作。 我们完全跳过了仪表板UX的设计。 一旦您了解如何使其与模板系统一起使用,最终由您决定! 例如,如果我们可以使用图像和动画通过一些凉爽的电量指示器将充电百分比同时显示为文本和视觉效果,那将有多酷? 使用Ractive.js,这并不难! 看一下最终结果:
结论
我们的多平台电池仪表板现在应该可以使用了。 但这应该是一个起点,而不是最终的结果,我希望您在此过程中学到的要点是:
- 如何使用Node.js设置HTTP服务器
- RESTful API
- 如何在Node.js服务器上运行OS终端命令
- 声明性框架的基础,尤其是Ractive.js
如果您想将其提升到一个新的水平,我的建议是开始尝试使用这些工具并挖掘网络以加深这些领域的知识。 如果您想加深本文中涉及的主题,强烈建议您看一下以下优质资源:
- 架构风格和基于网络的软件架构设计
- 创建RESTful API的准则
- 与本地库相比,使用REST API有哪些优点/缺点?
- 模板方法模式
- JavaScript中的异步请求
- Crockford谈JavaScript –第4集:Ajax的变态 –像往常一样深刻的洞察力,以及关于Ajax术语起源的超级有趣故事,作为奖励!
- jQuery
$.getJSON
方法 - RactiveJs教程
From: https://www.sitepoint.com/creating-a-battery-viz-using-node-js-client/