MagicMirror²模块开发文档
MagicMirror²模块开发文档
余生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
模块将在这个脚本中被定义。要使用模块的话,这个脚本不可或缺。最简单的形式,核心文件必须包括:
1 | Module.register("modulename",{}); |
当然,上面这个模块不会做任何事情,所以还是看一下最简单的一个模块:helloworld:
1 | //helloworld.js: |
你能够看到,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的classdata.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:
1 | requiresVersion: "2.1.0", |
可继承的模块方法
init()
这个放在在模块取得实例时被调用。在多数情况下,你不需要去继承这个方法。
loaded(callback)
版本:2.1.1
此方法在模块被加载后调用。在配置中之后的模块现在还没有被加载。callback
中的函数必须在模块被加载完后被调用。在多数情况下你不需要继承此方法。
Example:
1 | loaded: function(callback) { |
start()
这个方法在所有模块都被加载好并且系统准备系统时加载。注意这个时候dom对象还没有被创建。start方法可用来定义模块其他属性。
Example:
1 | start: function() { |
getScript()
返回:Array
getScript方法用来加载任何额外的脚本。这个方法需要返回一个字符串数组。如果你想放回一个module目录下的文件路径,使用this.file('file.name.js')
方法。在任何情况下,加载器只会加载一次文件。它也会检查文件在vendor文件夹下是否存在。
Example
1 | getScripts: function() { |
注意:如果某个文件不能被加载,魔镜的启动会被阻塞。所以,建议不要使用外部的url。
getStyles()
返回:Array
getStyle方法用来加载额外的样式文件。这个方法需要返回一个字符串数组。如果你想放回一个module目录下的文件路径,使用this.file('file.name.css')
方法。在任何情况下,加载器只会加载一次文件。它也会检查文件在vendor文件夹下是否存在。
Example
1 | getStyles: function() { |
注意:如果某个文件不能被加载,魔镜的启动会被阻塞。所以,建议不要使用外部的url。
getTranslations()
返回:Dictionary
getTranslations方法用来请求需要加载的翻译文件。这个方法需要返回用国家代号做键名的的字典。
如果模块没有特定的语言文件,这个函数会被忽略或者返回false
。
Example:
1 | getTranslations: function() { |
getDom()
返回:Dom对象
当MagicMirror需要在屏幕上更新信息时(在它启动时,或者因为你的模块使用this.updateDom()
请求刷新时),系统会调用getDom方法。这个方法需要返回一个Dom对象。
Example:
1 | getDom: function() { |
getHeader()
返回:String
当MagicMirror需要在屏幕上更新信息时(在它启动时,或者因为你的模块使用this.updateDom()
请求刷新时),系统会调用getHeader方法去获取模块的头部信息。这个方法要求返回一个字符串。如果这个方法没有被继承,这个方法将返回用户配置的头部信息。
如果你想使用用户配置的头部信息,参考this.data.header
注意:如果你没有配置默认的头部信息,没有头部信息会被显示,并且这个方法不会被调用。
Example:
1 | getHeader: function() { |
notificationReceived(notification, payload, sender)
MagicMirror核心有能力给模块发送通知,甚至是一个模块可以给另一个模块发送通知。当这个函数被调用后,它有三个参数:
notification
- String - 通知的标识payload
- AnyType - 通知的负载sender
- Module - 通知的发送方,如果这个参数是undefined
,那么通知的发送方是核心系统。
Example:
1 | notificationReceived: function(notification, payload, sender) { |
**注意:**系统会在启动时发送三个通知。这个通知可能会派上用场。
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:
1 | socketNotificationReceived: function(notification, 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,那么内容更新将是延时运动的,不过要内容真的有变化。
举例,时钟模块每一秒会调用这个方法:
1 | ... |
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:
1 | 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:
1 | 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隐藏:
1 | moduleA.hide(0, {lockString: "module_b_identifier"}); |
模块A现在是隐藏的,并且有一个这个的锁:
1 | moduleA.lockStrings == ["module_b_identifier"] |
模块C使模块A隐藏:
1 | moduleA.hide(0, {lockString: "module_c_identifier"}); |
模块A现在是隐藏的,并且有一个这个的锁:
1 | moduleA.lockStrings == ["module_b_identifier", "module_c_identifier"] |
模块B使模块A显示:
1 | moduleA.show(0, {lockString: "module_b_identifier"}); |
lockString 会从模块A的锁中去除,但因为还有一个有效的锁,所以这个模块还是会保持隐藏的状态:
1 | moduleA.lockStrings == ["module_c_identifier"] |
模块C使模块A显示:
1 | moduleA.show(0, {lockString: "module_c_identifier"}); |
lockString会从模块A的锁中去除,这会时得锁为空,所以这个模块会被显示:
1 | moduleA.lockStrings == [] |
注意: locking机制可以被force标志重写覆盖。
1 | moduleA.show(0, {force: true}); |
这会重置lockString,模块会被展示
1 | moduleA.lockStrings == [] |
谨慎使用force
,查看show
获取更多信息。
this.translate(identifier)
identifier String - 需要翻译的字符串的标识
魔镜项目为国际化提供了便利的封装。基于用户的语言配置,你可以为你的模块提供不同语言的服务。
如果没有找到语言包,会执行如下的回退操作:
-
- 模块语言包中的用户首选语言
-
- 核心语言包中的用户搜选语言
-
- 模块语言包中第一个被定义的语言
-
- 核心语言包中第一个被定义的语言
-
- 语言的标识
当给你的模块添加翻译时,最好查看一下在核心翻译文件中是否存在合适的翻译。
Example:
1 | this.translate("INFO") //Will return a translated string for the identifier INFO |
Example json file:
1 | { |
注意:虽然JSON文件不支持注释,但MagicMirror允许在解析JSON文件之前删除注释。翻译文件中的注释可以帮助其他译者。
this.translate(identifier,variables)
identifier String - 需要被翻译的字符串的标识。
variables Object - 在翻译中要用到的变量
这种改进的,向后兼容的翻译方法和普通的翻译方法一样并且遵循以上的规则。我们推荐使用这个的格式。它允许翻译者改变翻译句子的单词顺序。
Example:
1 | var timeUntilEnd = moment(event.endDate, "x").fromNow(true); |
Example English .json file::
1 | { |
Example Finnish .json file:
1 | { |
注意:variable对象有一个特殊的情况:fallback
。它用于支持翻译文件中没有变量的旧翻译。如果你正在升级一个旧模块,并且这个模块的翻译不支持语序,那么我们推荐使用fallback
Example
1 | var timeUntilEnd = moment(event.endDate, "x").fromNow(true); |
Example Swedish .json file that does not have the variable in it:
1 | { |
在这种情况下,translate方法不会在翻译中找到任何变量,而是寻找fallback
变量,并在可能的情况下使用它来创建翻译。
The Node Helper: node_helper.js
node helper是一个node.js脚本,它可以通过一些后端任务来支持你的模块。对于每一个模块类型,只有一个node helper会被创建。例如:如果你的魔镜有两个日历模块,那么只有一个node helper实例。
node_helper.js的最简单的形式必须包括:
1 | var NodeHelper = require("node_helper"); |
当然,上面的node helper不会做任何有用的事情。有了上面的信息,你应该可以让它更加复杂了。
可用的模块实例属性
this.name
String
模块的名称
this.path
String
模块的路径
this.expressApp
Express App Instance
这是一个express实例的链接,允许你定义额外的路由。
Example
1 | start: function() { |
**注意:**默认情况下,你的模块的public文件夹的路径会被定义:
1 | 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:
1 | requiresVersion: "2.1.0", |
### 可继承的模块方法
init()
当node helper被实例化后这个方法被调用,在多数情况下你不需要继承这个方法。
start()
这个方法在node helper被加载好并且系统准备系统时调用。start方法可以用来定义额外的模块属性。
Example:
1 | start: function() { |
stop()
这个方法在MagicMirror服务接收到信号指令并且开始关闭时执行。这个方法需要包括所有需要的指令去关闭已开启的连接、停止所有的子进程并且使各模块静默退出。
Example:
1 | stop: function() { |
socketNotificationReceived: function(notification, payload)
利用这个方法,你的node helper能够接收来自的你模块的通知。这个模块被调用时,它有两个参数:
notification
- String - 通知的标识payload
- AnyType - 通知的负载
注意: socket连接会在模块使用sendSocketNotification发送第一个信息时就创建
Example:
1 | socketNotificationReceived: function(notification, payload) { |
### 模块实例方法
每个node helper都有一些能帮助你更好构建模块的便利方法。
#### this.sendSocketNotification(notification,payload)
notification String - 通知的标识
payload AnyType - 可选 通知的负载
如果你想给你所有的模块发送通知,使用sendSocketNotification(notification, payload)
,只有你模块类型的模块会接受到socket通知。
注意: 因为所有的模块实例都会收到通知,确保正确的模块响应你的信息是你的任务。
Example:
1 | 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:
1 | var modules = MM.getModules().withClass('classname'); |
.exceptWithClass(classname)
classnames String or Array - 你想从结果中移去的模块类名称
返回 Array - 模块实例数组
如果你想基于类名称从选择集合中删去一些模块,在MM。getModules()
方法后使用exceptWithClass方法。它的参数可以是一个数组,或者用空格分割的字符串。
Example:
1 | var modules = MM.getModules().exceptWithClass('classname'); |
##### .exceptModule(module)
module Module Object - 你想从结果中移去的模块的引用。
返回 Array - 模块实例数组
此处原文有误。
如果你想筛选出除了自身的所有模块,这个方法会很有用。
Example:
1 | var modules = MM.getModules().exceptModule(this); |
当然,你可以把以上选择器结合起来:
Example:
1 | var modules = MM.getModules().withClass('classname1').exceptwithClass('classname2').exceptModule(aModule); |
##### .enumerate(callback)
callback Function(module) - 每一个实例上的回调
如果你想在所有选择好的模块删执行一个操作,你可以使用enumerate
函数:
1 | MM.getModules().enumerate(function(module) { |
Example: 要隐藏除了自身的其他模块,你可以这么写:
1 | Module.register("modulename",{ |
MagicMirror日志记录
MagicMirror项目对log做了方便的封装。目前,这只是对原生的console.log
的简单代理,但是之后可能会增加一些额外的特性。现在,log只在模块核心文件中有效(不包括node_helper)
Example:
1 | Log.info('error'); |