MagicMirror² 模块开发文档
对原文档的拙劣翻译
本文档介绍了开发MagicMirror²模块的方法
目录:
- 模块结构
- 文件
- 核心模块文件:modulename.js
- 可用的模块实例属性
- 可继承的模块方法
- 模块实例方法
- Node Helper:node_helper.js
- 可用的模块实例属性
- 可继承的模块方法
- 模块实例方法
- MagicMirror 辅助方法
- 模块选择
- MagicMirror 日志记录
建议
随着魔镜项目取得了巨大的关注,可用的第三方模块也越来越多。对于新用户和开发者来说,为了去了解一个模块究竟做了什么、长什么样以及它的依赖,要花费很多时间在众多的项目仓库中浏览。不幸的是,这些信息仍难以获得,除非你先安装好它。因此我们强烈建议你在README文件中包含以下信息。
- 一张你开发的模块的高质量截图
- 用简短的一句话描述它做了什么
- 它调用了哪些API,包括web链接
- API和请求是否需要密钥以及是否有用户限制
当然,这也能帮助你更好地认识和改善你的工作。
模块结构
所有的模块都在modules
文件夹中加载。默认模块都放在modules/default
文件夹下。你的模块也应该放在modules下。注意在moduls文件下的文件会被git忽略,这样的话在升级MagicMirror² 的时候就不会丢失它们。
一个模块可以单独放在一个文件夹下,多个模块也可以分组放在一个子文件夹下。注意模块的名字需要是唯一的,即使将名称相似的模块放在不同的文件夹中,也不能同时加载它们。
文件
- modulename/modulename.js -这是模块的核心文件
- modulename/node_helper.js -这是一个可选的辅助文件,它将被node脚本加载。node helper会和模块的脚本通过socket系统进行通信。
- modulename/public 任何在这个目录下的文件都可以通过浏览器访问
/modulename/filename.ext.
- modulename/anyfileorfolder这里放置会被核心脚本用到的一些文件。例如
modulename/css/modulename.css
就是用来放置样式表的一个很好路径
模块核心文件:modulename.js
模块将在这个脚本中被定义。要使用模块的话,这个脚本不可或缺。最简单的形式,核心文件必须包括:
Module.register("modulename",{});
当然,上面这个模块不会做任何事情,所以还是看一下最简单的一个模块:helloworld:
//helloworld.js:
Module.register("helloworld",{
// Default module config.
defaults: {
text: "Hello World!"
},
// Override dom generator.
getDom: function() {
var wrapper = document.createElement("div");
wrapper.innerHTML = this.config.text;
return wrapper;
}
});
你能够看到,Module.register()
方法接受两个参数:模块的名称和一个描述模块属性的对象。
可用的模块实例属性
在模块被初始化之后,模块实例有一些可用的属性:
实例属性 | 类型 | 描述 |
---|---|---|
this.name |
String | 模块名称 |
this.identifier |
String | 模块实例的唯一标识 |
this.hidden |
Boolean | 这个代表模块现在是否被隐藏 |
this.config |
Boolean ??? | 用户在config.js 中设置的配置。如果这个属性没有被用户配置覆盖的话,这个配置也包括模块的默认配置 |
this.data |
Object | data对象包括一个额外的元数据(见下) |
this.data
包括以下元数据:
-
data.classes
- modules的dom wrapper的class -
data.file
- 模块核心文件的名称 -
data.path
- 模块所在的目录路径 -
data.header
- 加到模块???? -
data.postion
- 模块实例将显示的位置
default:{}
任何在default对象中定义的对象,都会被合并到用户的config.js
中的模块配置中。这里是你设置模块默认配置的最佳位置。所有的模块配置属性都可以通过this.config.propertyName
获取,这会在之后提到。
requireVersion:
版本:2.1.0
定义MagicMirror框架的最低版本。如果它被设置了,系统会把这个版本号和用户的版本进行比较,如果用户版本较低,就不会运行这个模块。确保在Node helper中也设置了这个值。
注意:因为这个检查设置是在2.1.0版本中介绍的,这个操作不会在更老的版本中执行。
Example:
requiresVersion: "2.1.0",
可继承的模块方法
init()
这个放在在模块取得实例时被调用。在多数情况下,你不需要去继承这个方法。
loaded(callback)
版本:2.1.1
此方法在模块被加载后调用。在配置中之后的模块现在还没有被加载。callback
中的函数必须在模块被加载完后被调用。在多数情况下你不需要继承此方法。
Example:
loaded: function(callback) {
this.finishLoading();
Log.log(this.name + ' is loaded!');
callback();
}
start()
这个方法在所有模块都被加载好并且系统准备系统时加载。注意这个时候dom对象还没有被创建。start方法可用来定义模块其他属性。
Example:
start: function() {
this.mySpecialProperty = "So much wow!";
Log.log(this.name + ' is started!');
}
getScript()
返回:Array
getScript方法用来加载任何额外的脚本。这个方法需要返回一个字符串数组。如果你想放回一个module目录下的文件路径,使用this.file('file.name.js')
方法。在任何情况下,加载器只会加载一次文件。它也会检查文件在vendor文件夹下是否存在。
Example
getScripts: function() {
return [
'script.js', // will try to load it from the vendor folder, otherwise it will load is from the module folder.
'moment.js', // this file is available in the vendor folder, so it doesn't need to be available in the module folder.
this.file('anotherfile.js'), // this file will be loaded straight from the module folder.
'https://code.jquery.com/jquery-2.2.3.min.js', // this file will be loaded from the jquery servers.
]
}
注意:如果某个文件不能被加载,魔镜的启动会被阻塞。所以,建议不要使用外部的url。
getStyles()
返回:Array
getStyle方法用来加载额外的样式文件。这个方法需要返回一个字符串数组。如果你想放回一个module目录下的文件路径,使用this.file('file.name.css')
方法。在任何情况下,加载器只会加载一次文件。它也会检查文件在vendor文件夹下是否存在。
Example
getStyles: function() {
return [
'script.css', // will try to load it from the vendor folder, otherwise it will load is from the module folder.
'font-awesome.css', // this file is available in the vendor folder, so it doesn't need to be available in the module folder.
this.file('anotherfile.css'), // this file will be loaded straight from the module folder.
'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css', // this file will be loaded from the bootstrapcdn servers.
]
}
注意:如果某个文件不能被加载,魔镜的启动会被阻塞。所以,建议不要使用外部的url。
getTranslations()
返回:Dictionary
getTranslations方法用来请求需要加载的翻译文件。这个方法需要返回用国家代号做键名的的字典。
如果模块没有特定的语言文件,这个函数会被忽略或者返回false
。
Example:
getTranslations: function() {
return {
en: "translations/en.json",
de: "translations/de.json"
}
}
getDom()
返回:Dom对象
当MagicMirror需要在屏幕上更新信息时(在它启动时,或者因为你的模块使用this.updateDom()
请求刷新时),系统会调用getDom方法。这个方法需要返回一个Dom对象。
Example:
getDom: function() {
var wrapper = document.createElement("div");
wrapper.innerHTML = 'Hello world!';
return wrapper;
}
getHeader()
返回:String
当MagicMirror需要在屏幕上更新信息时(在它启动时,或者因为你的模块使用this.updateDom()
请求刷新时),系统会调用getHeader方法去获取模块的头部信息。这个方法要求返回一个字符串。如果这个方法没有被继承,这个方法将返回用户配置的头部信息。
如果你想使用用户配置的头部信息,参考this.data.header
注意:如果你没有配置默认的头部信息,没有头部信息会被显示,并且这个方法不会被调用。
Example:
getHeader: function() {
return this.data.header + ' Foo Bar';
}
notificationReceived(notification, payload, sender)
MagicMirror核心有能力给模块发送通知,甚至是一个模块可以给另一个模块发送通知。当这个函数被调用后,它有三个参数:
-
notification
- String - 通知的标识 -
payload
- AnyType - 通知的负载 -
sender
- Module - 通知的发送方,如果这个参数是undefined
,那么通知的发送方是核心系统。
Example:
notificationReceived: function(notification, payload, sender) {
if (sender) {
Log.log(this.name + " received a module notification: " + notification + " from sender: " + sender.name);
} else {
Log.log(this.name + " received a system notification: " + notification);
}
}
注意:系统会在启动时发送三个通知。这个通知可能会派上用场。
-
ALL_MODULES_STARTED
- 所有模块被系统,你现在可以给其他模块发送消息。 -
DOM_OBJECTS_CREATED
- 所有的Dom对象被创建,系统现在可以进行视觉上的改变。 -
MODULE_DOM_CREATED
- 这个模块的Dom已经被全部加载,你现在可以访问你的Dom对象。
socketNotificationRceived: function(notification, payload)
当使用node_helper时,node_helper能够给你的模块发送信息,当这个模块被调用时,它有两个参数:
-
notification
- String - 通知的标识 -
payload
- AnyType - 通知的负载
注意1:当node helper发送一个通知时,所有这个模块类型的模块都会收到相同的通知。
注意2:当模块第一次使用sendSocketNotification发送信息时,socket链接就被建立。
Example:
socketNotificationReceived: function(notification, payload) {
Log.log(this.name + " received a socket notification: " + notification + " - Payload: " + payload);
},
suspend()
当一个模块被隐藏时(使用module.hide()
),suspend()
方法会被调用。通过继承这个方法,你可以进行一些像中止更新计时器的操作。
resume()
当一个模块被显示时(使用module.show()
),resume()
方法会被调用。通过继承这个方法,你可以进行一些像重启更新计时器的操作。
模块实例方法
每个模块实例都有一些对构建模块有帮助的便捷方法。
this.file(filename)
filename String - 你创建路径的文件的名称
返回 String
如果你想在你的模块文件夹下创建文件的路径,使用file()
方法。//TODO(Is method comes in handy when configuring the getScripts and getStyles methods.)
this.update(speed)
speed Number - 可选,运动速度 毫秒
当你要更新模块时,调用updateDom(speed)
方法。它向MagicMirror核心请求更新它的dom对象。如果你定义的speed,那么内容更新将是延时运动的,不过要内容真的有变化。
举例,时钟模块每一秒会调用这个方法:
...
start: function() {
var self = this;
setInterval(function() {
self.updateDom(); // no speed defined, so it updates instantly.
}, 1000); //perform every 1000 milliseconds.
}
...
this.sendNotification(notification, payload)
notification String - 通知标识
payload AnyType - 可选,通知负载
如果你想给其他模块发送信息,使用sendNotification(notification, payload)
。所有的模块都会通过notificationReceived方法收到信息。//TODO In that case, the sender is automatically set to the instance calling the sendNotification method.
Example:
this.sendNotification('MYMODULE_READY_FOR_ACTION', {foo:bar});
this.sendSocketNotification(notification, payload)
notification String - 通知标识
payload AnyType - 可选,通知负载
如果你想给node_helper发送通知,使用endSocketNotification(notification, payload)
。只有这个模块的node_helper会收到socket通知。
Example:
this.sendSocketNotification('SET_CONFIG', this.config);
this.hide(speed, callback, options)
- speed Number - 可选(设置callback或option时需要),隐藏的动效速度,毫秒。
- callback -可选,在运动结束后的回调
- options - 可选,隐藏操作的额外选项(见下)。(2.1.0)
你可以使用hide(speed,callback)
方法来隐藏一个模块。你可以在对象实例自身上使用this.hide()
,当然你也可以使用anOtherModule.hide()
来隐藏其他模块。
可配置的选择项:
-
lockString
- String 当设置了lock字符,在没有正确的lockstring时模块无法被显示。这样的话可以阻止一个模块被显示。把你的模块标识(this.identifier)作为lockString的方式是好的.(见下)
注意1:如果隐藏动效被取消,这可能因为在隐藏动画结束前调用了显示动画。回调函数不会被取消。
注意2:如果隐藏动效被hijacked(其他方法在这个模块上调用了隐藏方法),回调函数不会被调用。
注意3:如果dom没有被创建,hide方法不会被调用。等待DOM_OBJECTS_CREATED
通知。
this.shwo(speed,callback,options)
- speed Number - 可选(设置callback或option时需要),显示的动效速度,毫秒。
- callback -可选,在运动结束后的回调
- options - 可选,显示操作的额外选项(见下)。(2.1.0)
你可以使用show(speed,callback)
方法来显示一个模块。你可以在对象实例自身上使用this.show()
,当然你也可以使用anOtherModule.show()
来显示其他模块。
可选配置:
-
lockString
- String 当设置了lock字符,在没有正确的lockstring时模块无法被显示。这样的话可以阻止一个模块被显示。把你的模块标识(this.identifier)作为lockString的方式是好的.(见下) -
force
- Boolean 当force标志为true
时,locking机制会被覆盖。谨慎使用这个选项。
注意1:如果显示动画被取消,这可能因为在显示动画结束前隐藏动画被调用。回调函数不会被调用。
注意2:如果显示动效被hijacked(其他方法在这个模块上调用了显示方法),回调函数不会被调用。
注意3:如果dom没有被创建,show方法不会被调用。等待DOM_OBJECTS_CREATED
通知。
Visibility locking
版本:2.1.0
Visibility locking 帮助系统阻止不想发生的隐藏/显示操作。以下面的情况为例进行介绍:
模块B使模块A隐藏:
moduleA.hide(0, {lockString: "module_b_identifier"});
模块A现在是隐藏的,并且有一个这个的锁:
moduleA.lockStrings == ["module_b_identifier"]
moduleA.hidden == true
模块C使模块A隐藏:
moduleA.hide(0, {lockString: "module_c_identifier"});
模块A现在是隐藏的,并且有一个这个的锁:
moduleA.lockStrings == ["module_b_identifier", "module_c_identifier"]
moduleA.hidden == true
模块B使模块A显示:
moduleA.show(0, {lockString: "module_b_identifier"});
lockString 会从模块A的锁中去除,但因为还有一个有效的锁,所以这个模块还是会保持隐藏的状态:
moduleA.lockStrings == ["module_c_identifier"]
moduleA.hidden == true
模块C使模块A显示:
moduleA.show(0, {lockString: "module_c_identifier"});
lockString会从模块A的锁中去除,这会时得锁为空,所以这个模块会被显示:
moduleA.lockStrings == []
moduleA.hidden == false
注意: locking机制可以被force标志重写覆盖。
moduleA.show(0, {force: true});
这会重置lockString,模块会被展示
moduleA.lockStrings == []
moduleA.hidden == false
谨慎使用force
,查看show
获取更多信息。
this.translate(identifier)
identifier String - 需要翻译的字符串的标识
魔镜项目为国际化提供了便利的封装。基于用户的语言配置,你可以为你的模块提供不同语言的服务。
如果没有找到语言包,会执行如下的回退操作:
-
- 模块语言包中的用户首选语言
-
- 核心语言包中的用户搜选语言
-
- 模块语言包中第一个被定义的语言
-
- 核心语言包中第一个被定义的语言
-
- 语言的标识
当给你的模块添加翻译时,最好查看一下在核心翻译文件中是否存在合适的翻译。
Example:
this.translate("INFO") //Will return a translated string for the identifier INFO
Example json file:
{
"INFO": "Really important information!"
}
注意:虽然JSON文件不支持注释,但MagicMirror允许在解析JSON文件之前删除注释。翻译文件中的注释可以帮助其他译者。
this.translate(identifier,variables)
identifier String - 需要被翻译的字符串的标识。
variables Object - 在翻译中要用到的变量
这种改进的,向后兼容的翻译方法和普通的翻译方法一样并且遵循以上的规则。我们推荐使用这个的格式。它允许翻译者改变翻译句子的单词顺序。
Example:
var timeUntilEnd = moment(event.endDate, "x").fromNow(true);
this.translate("RUNNING", { "timeUntilEnd": timeUntilEnd) }); // Will return a translated string for the identifier RUNNING, replacing `{timeUntilEnd}` with the contents of the variable `timeUntilEnd` in the order that translator intended.
Example English .json file::
{
"RUNNING": "Ends in {timeUntilEnd}",
}
Example Finnish .json file:
{
"RUNNING": "Päättyy {timeUntilEnd} päästä",
}
注意:variable对象有一个特殊的情况:fallback
。它用于支持翻译文件中没有变量的旧翻译。如果你正在升级一个旧模块,并且这个模块的翻译不支持语序,那么我们推荐使用fallback
Example
var timeUntilEnd = moment(event.endDate, "x").fromNow(true);
this.translate("RUNNING", {
"fallback": this.translate("RUNNING") + " {timeUntilEnd}"
"timeUntilEnd": timeUntilEnd
)}); // Will return a translated string for the identifier RUNNING, replacing `{timeUntilEnd}` with the contents of the variable `timeUntilEnd` in the order that translator intended. (has a fallback)
Example Swedish .json file that does not have the variable in it:
{
"RUNNING": "Slutar",
}
在这种情况下,translate方法不会在翻译中找到任何变量,而是寻找fallback
变量,并在可能的情况下使用它来创建翻译。
The Node Helper: node_helper.js
node helper是一个node.js脚本,它可以通过一些后端任务来支持你的模块。对于每一个模块类型,只有一个node helper会被创建。例如:如果你的魔镜有两个日历模块,那么只有一个node helper实例。
node_helper.js的最简单的形式必须包括:
var NodeHelper = require("node_helper");
module.exports = NodeHelper.create({});
当然,上面的node helper不会做任何有用的事情。有了上面的信息,你应该可以让它更加复杂了。
可用的模块实例属性
this.name
String
模块的名称
this.path
String
模块的路径
this.expressApp
Express App Instance
这是一个express实例的链接,允许你定义额外的路由。
Example
start: function() {
this.expressApp.get('/foobar', function (req, res) {
res.send('GET request to /foobar');
});
}
注意:默认情况下,你的模块的public文件夹的路径会被定义:
this.expressApp.use("/" + this.name, express.static(this.path + "/public"));
this.io
Socket IO Instance
这是一个IO实例的链接,它允许你进行一些Socket.IO操作。在多数情况下,你用不到它,因为Node Helper有一些更加方便的方法。
requireVersion
版本:2.1.0
一个描述MagicMirror框架最低版本的字符串。如果它被设置了,系统会把这个版本号和用户的版本进行比较,如果用户版本较低,就不会运行这个模块。
注意:因为这个检查设置是在2.1.0版本中介绍的,这个操作不会在更老的版本中执行。
Example:
requiresVersion: "2.1.0",
可继承的模块方法
init()
当node helper被实例化后这个方法被调用,在多数情况下你不需要继承这个方法。
start()
这个方法在node helper被加载好并且系统准备系统时调用。start方法可以用来定义额外的模块属性。
Example:
start: function() {
this.mySpecialProperty = "So much wow!";
Log.log(this.name + ' is started!');
}
stop()
这个方法在MagicMirror服务接收到信号指令并且开始关闭时执行。这个方法需要包括所有需要的指令去关闭已开启的连接、停止所有的子进程并且使各模块静默退出。
Example:
stop: function() {
console.log("Shutting down MyModule");
this.connection.close();
}
socketNotificationReceived: function(notification, payload)
利用这个方法,你的node helper能够接收来自的你模块的通知。这个模块被调用时,它有两个参数:
-
notification
- String - 通知的标识 -
payload
- AnyType - 通知的负载
注意: socket连接会在模块使用sendSocketNotification发送第一个信息时就创建
Example:
socketNotificationReceived: function(notification, payload) {
Log.log(this.name + " received a socket notification: " + notification + " - Payload: " + payload);
},
模块实例方法
每个node helper都有一些能帮助你更好构建模块的便利方法。
this.sendSocketNotification(notification,payload)
notification String - 通知的标识
payload AnyType - 可选 通知的负载
如果你想给你所有的模块发送通知,使用sendSocketNotification(notification, payload)
,只有你模块类型的模块会接受到socket通知。
注意: 因为所有的模块实例都会收到通知,确保正确的模块响应你的信息是你的任务。
Example:
this.sendSocketNotification('SET_CONFIG', this.config);
MagicMirror辅助方法
MagicMirror核心对象:MM
有一些便携方法能帮助你更好的把控你的模块。大多数的MM
方法都可以在你的模块实例上获取。
模块选择
你的模块唯一的附加方法就是获取其他模块的引用。这可以用来隐藏或显示其他模块。
MM.getModules()
返回 Array - 模块实例的数组
使用MM.getModules()
方法选择所有被加载的模块实例。它会返回一个现在已经被加载的模块实例的数组。返回的数组有一些筛选方法,见下。
.withClass(classnames)
classnames String or Array - 你想要筛选的类名称。
返回 Array - 一个模块实例数组
如果你想基于一个或多个类名称进行筛选,在MM。getModules()
方法后使用withClass方法。它的参数可以是一个数组,或者用空格分割的字符串。
Example:
var modules = MM.getModules().withClass('classname');
var modules = MM.getModules().withClass('classname1 classname2');
var modules = MM.getModules().withClass(['classname1','classname2']);
.exceptWithClass(classname)
classnames String or Array - 你想从结果中移去的模块类名称
返回 Array - 模块实例数组
如果你想基于类名称从选择集合中删去一些模块,在MM。getModules()
方法后使用exceptWithClass方法。它的参数可以是一个数组,或者用空格分割的字符串。
Example:
var modules = MM.getModules().exceptWithClass('classname');
var modules = MM.getModules().exceptWithClass('classname1 classname2');
var modules = MM.getModules().exceptWithClass(['classname1','classname2']);
.exceptModule(module)
module Module Object - 你想从结果中移去的模块的引用。
返回 Array - 模块实例数组
此处原文有误。
如果你想筛选出除了自身的所有模块,这个方法会很有用。
Example:
var modules = MM.getModules().exceptModule(this);
当然,你可以把以上选择器结合起来:
Example:
var modules = MM.getModules().withClass('classname1').exceptwithClass('classname2').exceptModule(aModule);
.enumerate(callback)
callback Function(module) - 每一个实例上的回调
如果你想在所有选择好的模块删执行一个操作,你可以使用enumerate
函数:
MM.getModules().enumerate(function(module) {
Log.log(module.name);
});
Example: 要隐藏除了自身的其他模块,你可以这么写:
Module.register("modulename",{
//...
notificationReceived: function(notification, payload, sender) {
if (notification === 'DOM_OBJECTS_CREATED') {
MM.getModules().exceptModule(this).enumerate(function(module) {
module.hide(1000, function() {
//Module hidden.
});
});
}
},
//...
});
MagicMirror日志记录
MagicMirror项目对log做了方便的封装。目前,这只是对原生的console.log
的简单代理,但是之后可能会增加一些额外的特性。现在,log只在模块核心文件中有效(不包括node_helper)
Example:
Log.info('error');
Log.log('log');
Log.error('info');
博客:blog.emx6.com