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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//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:

1
requiresVersion: "2.1.0",

可继承的模块方法

init()

这个放在在模块取得实例时被调用。在多数情况下,你不需要去继承这个方法。

loaded(callback)

版本:2.1.1
此方法在模块被加载后调用。在配置中之后的模块现在还没有被加载。callback中的函数必须在模块被加载完后被调用。在多数情况下你不需要继承此方法。
Example:

1
2
3
4
5
loaded: function(callback) {
this.finishLoading();
Log.log(this.name + ' is loaded!');
callback();
}

start()

这个方法在所有模块都被加载好并且系统准备系统时加载。注意这个时候dom对象还没有被创建。start方法可用来定义模块其他属性。
Example:

1
2
3
4
start: function() {
this.mySpecialProperty = "So much wow!";
Log.log(this.name + ' is started!');
}

getScript()

返回:Array
getScript方法用来加载任何额外的脚本。这个方法需要返回一个字符串数组。如果你想放回一个module目录下的文件路径,使用this.file('file.name.js')方法。在任何情况下,加载器只会加载一次文件。它也会检查文件在vendor文件夹下是否存在。
Example

1
2
3
4
5
6
7
8
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

1
2
3
4
5
6
7
8
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:

1
2
3
4
5
6
getTranslations: function() {
return {
en: "translations/en.json",
de: "translations/de.json"
}
}

getDom()

返回:Dom对象
当MagicMirror需要在屏幕上更新信息时(在它启动时,或者因为你的模块使用this.updateDom()请求刷新时),系统会调用getDom方法。这个方法需要返回一个Dom对象。
Example:

1
2
3
4
5
getDom: function() {
var wrapper = document.createElement("div");
wrapper.innerHTML = 'Hello world!';
return wrapper;
}

getHeader()

返回:String
当MagicMirror需要在屏幕上更新信息时(在它启动时,或者因为你的模块使用this.updateDom()请求刷新时),系统会调用getHeader方法去获取模块的头部信息。这个方法要求返回一个字符串。如果这个方法没有被继承,这个方法将返回用户配置的头部信息。
如果你想使用用户配置的头部信息,参考this.data.header
注意:如果你没有配置默认的头部信息,没有头部信息会被显示,并且这个方法不会被调用。
Example:

1
2
3
getHeader: function() {
return this.data.header + ' Foo Bar';
}

notificationReceived(notification, payload, sender)

MagicMirror核心有能力给模块发送通知,甚至是一个模块可以给另一个模块发送通知。当这个函数被调用后,它有三个参数:

  • notification - String - 通知的标识
  • payload- AnyType - 通知的负载
  • sender - Module - 通知的发送方,如果这个参数是undefined,那么通知的发送方是核心系统。
    Example:
1
2
3
4
5
6
7
 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:

1
2
3
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,那么内容更新将是延时运动的,不过要内容真的有变化。
举例,时钟模块每一秒会调用这个方法:

1
2
3
4
5
6
7
8
...
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:

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
2
moduleA.lockStrings == ["module_b_identifier"]
moduleA.hidden == true

模块C使模块A隐藏:

1
moduleA.hide(0, {lockString: "module_c_identifier"});

模块A现在是隐藏的,并且有一个这个的锁:

1
2
moduleA.lockStrings == ["module_b_identifier", "module_c_identifier"]
moduleA.hidden == true

模块B使模块A显示:

1
moduleA.show(0, {lockString: "module_b_identifier"});

lockString 会从模块A的锁中去除,但因为还有一个有效的锁,所以这个模块还是会保持隐藏的状态:

1
2
moduleA.lockStrings == ["module_c_identifier"]
moduleA.hidden == true

模块C使模块A显示:

1
moduleA.show(0, {lockString: "module_c_identifier"});

lockString会从模块A的锁中去除,这会时得锁为空,所以这个模块会被显示:

1
2
moduleA.lockStrings == []
moduleA.hidden == false

注意: locking机制可以被force标志重写覆盖。

1
moduleA.show(0, {force: true});

这会重置lockString,模块会被展示

1
2
moduleA.lockStrings == []
moduleA.hidden == false

谨慎使用force,查看show获取更多信息。

this.translate(identifier)

identifier String - 需要翻译的字符串的标识

魔镜项目为国际化提供了便利的封装。基于用户的语言配置,你可以为你的模块提供不同语言的服务。

如果没有找到语言包,会执行如下的回退操作:

    1. 模块语言包中的用户首选语言
    1. 核心语言包中的用户搜选语言
    1. 模块语言包中第一个被定义的语言
    1. 核心语言包中第一个被定义的语言
    1. 语言的标识

当给你的模块添加翻译时,最好查看一下在核心翻译文件中是否存在合适的翻译。

Example:

1
this.translate("INFO") //Will return a translated string for the identifier INFO

Example json file:

1
2
3
{
"INFO": "Really important information!"
}

注意:虽然JSON文件不支持注释,但MagicMirror允许在解析JSON文件之前删除注释。翻译文件中的注释可以帮助其他译者。

this.translate(identifier,variables)

identifier String - 需要被翻译的字符串的标识。
variables Object - 在翻译中要用到的变量
这种改进的,向后兼容的翻译方法和普通的翻译方法一样并且遵循以上的规则。我们推荐使用这个的格式。它允许翻译者改变翻译句子的单词顺序。
Example:

1
2
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::

1
2
3
{
"RUNNING": "Ends in {timeUntilEnd}",
}

Example Finnish .json file:

1
2
3
{
"RUNNING": "Päättyy {timeUntilEnd} päästä",
}

注意:variable对象有一个特殊的情况:fallback。它用于支持翻译文件中没有变量的旧翻译。如果你正在升级一个旧模块,并且这个模块的翻译不支持语序,那么我们推荐使用fallback
Example

1
2
3
4
5
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:

1
2
3
{
"RUNNING": "Slutar",
}

在这种情况下,translate方法不会在翻译中找到任何变量,而是寻找fallback变量,并在可能的情况下使用它来创建翻译。

The Node Helper: node_helper.js

node helper是一个node.js脚本,它可以通过一些后端任务来支持你的模块。对于每一个模块类型,只有一个node helper会被创建。例如:如果你的魔镜有两个日历模块,那么只有一个node helper实例。

node_helper.js的最简单的形式必须包括:

1
2
var NodeHelper = require("node_helper");
module.exports = NodeHelper.create({});

当然,上面的node helper不会做任何有用的事情。有了上面的信息,你应该可以让它更加复杂了。

可用的模块实例属性

this.name

String
模块的名称

this.path

String
模块的路径

this.expressApp

Express App Instance
这是一个express实例的链接,允许你定义额外的路由。
Example

1
2
3
4
5
start: function() {
this.expressApp.get('/foobar', function (req, res) {
res.send('GET request to /foobar');
});
}

**注意:**默认情况下,你的模块的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
2
3
4
start: function() {
this.mySpecialProperty = "So much wow!";
Log.log(this.name + ' is started!');
}

stop()

这个方法在MagicMirror服务接收到信号指令并且开始关闭时执行。这个方法需要包括所有需要的指令去关闭已开启的连接、停止所有的子进程并且使各模块静默退出。

Example:

1
2
3
4
stop: function() {
console.log("Shutting down MyModule");
this.connection.close();
}

socketNotificationReceived: function(notification, payload)

利用这个方法,你的node helper能够接收来自的你模块的通知。这个模块被调用时,它有两个参数:

  • notification - String - 通知的标识
  • payload - AnyType - 通知的负载

注意: socket连接会在模块使用sendSocketNotification发送第一个信息时就创建

Example:

1
2
3
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:

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
2
3
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:

1
2
3
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:

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
2
3
MM.getModules().enumerate(function(module) {
Log.log(module.name);
});

Example: 要隐藏除了自身的其他模块,你可以这么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
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:

1
2
3
Log.info('error');
Log.log('log');
Log.error('info');