diff --git a/lib/mustache-context.js b/lib/mustache-context.js new file mode 100644 index 0000000000..8a0be738ce --- /dev/null +++ b/lib/mustache-context.js @@ -0,0 +1,79 @@ +/* + * Modified from https://github.com/node-red/node-red/blob/master/nodes/core/core/80-template.js + */ + +const mustache = require('mustache'); +const selectn = require('selectn'); + +function parseContext(key) { + var match = /^(flow|global)(\[(\w+)\])?\.(.+)/.exec(key); + if (match) { + var parts = {}; + parts.type = match[1]; + parts.store = match[3] === '' ? 'default' : match[3]; + parts.field = match[4]; + return parts; + } + return undefined; +} + +/** + * Custom Mustache Context capable to collect message property and node + * flow and global context + */ + +function NodeContext(msg, parent, nodeContext, serverName) { + this.msgContext = new mustache.Context(msg, parent); + this.nodeContext = nodeContext; + this.serverName = serverName; +} + +NodeContext.prototype = new mustache.Context(); + +NodeContext.prototype.lookup = function(name) { + // try message first: + try { + const value = this.msgContext.lookup(name); + if (value !== undefined) { + return value; + } + + // try flow/global context: + const context = parseContext(name); + if (context) { + const type = context.type; + const store = context.store; + const field = context.field; + const target = this.nodeContext[type]; + if (target) { + return target.get(field, store); + } + } + + // try state entities + const match = /^states\.(\w+\.\w+)(?:\.(.+))?/.exec(name); + if (match) { + const gHomeAssistant = this.nodeContext.global.get('homeassistant'); + const states = gHomeAssistant[this.serverName].states; + const entityId = match[1]; + const path = match[2] || 'state'; + + return selectn(path, states[entityId]) || ''; + } + + return ''; + } catch (err) { + throw err; + } +}; + +NodeContext.prototype.push = function push(view) { + return new NodeContext( + view, + this.nodeContext, + this.msgContext, + this.serverName + ); +}; + +module.exports = NodeContext; diff --git a/nodes/call-service/call-service.html b/nodes/call-service/call-service.html index 8cb19fda3d..f5b3387b83 100644 --- a/nodes/call-service/call-service.html +++ b/nodes/call-service/call-service.html @@ -30,7 +30,6 @@ } } }, - render_data: { value: false }, mergecontext: { value: null }, output_location: { value: "payload" }, output_location_type: { value: "msg" } @@ -362,11 +361,6 @@ -
- - -
-
@@ -408,9 +402,9 @@

Config

Entity Id string
A comma delimited list of entity ids
data JSON
-
When using templates the top level is a property of the message object: msg.payload would be {{payload}}
-
Render Templates boolean
-
When checked will attempt to render templates within the data element including entity ids
+
JSON object to pass along.
+
Templates
+
You can use templates in the Entity Id and data fields. When using templates the top level is a property of the message object: msg.payload would be {{payload}}. You can also access the flow, global and states contexts {{flow.foobar}} {{global.something}}. For the states context you can use the {{states.domain.entity_id}} to just get the state or drill further down like {{states.light.kitchen.attributes.friendly_name}}. {{states.light.kitchen}} and {{states.light.kitchen.state}} are equivalent.

Inputs

diff --git a/nodes/call-service/call-service.js b/nodes/call-service/call-service.js index ddc0007c2a..fd6cbb99a2 100644 --- a/nodes/call-service/call-service.js +++ b/nodes/call-service/call-service.js @@ -1,6 +1,7 @@ /* eslint-disable camelcase */ const BaseNode = require('../../lib/base-node'); const mustache = require('mustache'); +const Context = require('../../lib/mustache-context'); module.exports = function(RED) { const nodeOptions = { @@ -9,7 +10,6 @@ module.exports = function(RED) { service_domain: {}, service: {}, data: {}, - render_data: {}, mergecontext: {}, name: {}, server: { isNode: true }, @@ -78,11 +78,17 @@ module.exports = function(RED) { ); } - if (this.nodeConfig.render_data) { - apiData = JSON.parse( - mustache.render(JSON.stringify(apiData), message) - ); - } + apiData = JSON.parse( + mustache.render( + JSON.stringify(apiData), + new Context( + message, + null, + this.node.context(), + this.utils.toCamelCase(this.nodeConfig.server.name) + ) + ) + ); this.debug( `Calling Service: ${apiDomain}:${apiService} -- ${JSON.stringify( @@ -158,7 +164,7 @@ module.exports = function(RED) { payloadData = payloadData || {}; configData = configData || {}; - // Cacluate payload to send end priority ends up being 'Config, Global Ctx, Flow Ctx, Payload' with right most winning + // Calculate payload to send end priority ends up being 'Config, Global Ctx, Flow Ctx, Payload' with right most winning if (this.nodeConfig.mergecontext) { const ctx = this.node.context(); let flowVal = ctx.flow.get(this.nodeConfig.mergecontext); diff --git a/nodes/fire-event/fire-event.html b/nodes/fire-event/fire-event.html index 364f9e0425..be91f27803 100644 --- a/nodes/fire-event/fire-event.html +++ b/nodes/fire-event/fire-event.html @@ -11,17 +11,9 @@ return this.name || `Event: ${this.event}`; }, defaults: { - name: { - value: "" - }, - server: { - value: "", - type: "server", - required: true - }, - event: { - value: "" - }, + name: { value: "" }, + server: { value: "", type: "server", required: true }, + event: { value: "" }, data: { value: "", validate: function(v) { @@ -37,10 +29,7 @@ } } }, - render_data: { value: false }, - mergecontext: { - value: null - } + mergecontext: { value: null } }, oneditprepare: function() { const NODE = this; @@ -91,11 +80,6 @@
-
- - -
-
@@ -103,16 +87,26 @@ diff --git a/nodes/fire-event/fire-event.js b/nodes/fire-event/fire-event.js index 297714bb4f..ce461f9c40 100644 --- a/nodes/fire-event/fire-event.js +++ b/nodes/fire-event/fire-event.js @@ -1,5 +1,6 @@ const BaseNode = require('../../lib/base-node'); const mustache = require('mustache'); +const Context = require('../../lib/mustache-context'); module.exports = function(RED) { const nodeOptions = { @@ -10,9 +11,7 @@ module.exports = function(RED) { render_data: {}, mergecontext: {}, name: {}, - server: { - isNode: true - } + server: { isNode: true } } }; @@ -52,11 +51,17 @@ module.exports = function(RED) { this.debug(`Fire Event: ${eventType} -- ${JSON.stringify({})}`); - if (this.nodeConfig.render_data) { - eventData = JSON.parse( - mustache.render(JSON.stringify(eventData), message) - ); - } + eventData = JSON.parse( + mustache.render( + JSON.stringify(eventData), + new Context( + message, + null, + this.node.context(), + this.utils.toCamelCase(this.nodeConfig.server.name) + ) + ) + ); message.payload = { event: eventType, @@ -103,7 +108,7 @@ module.exports = function(RED) { payloadData = payloadData || {}; configData = configData || {}; - // Cacluate payload to send end priority ends up being 'Config, Global Ctx, Flow Ctx, Payload' with right most winning + // Calculate payload to send end priority ends up being 'Config, Global Ctx, Flow Ctx, Payload' with right most winning if (this.nodeConfig.mergecontext) { const ctx = this.node.context(); let flowVal = ctx.flow.get(this.nodeConfig.mergecontext);