├── .eslintrc.json
├── .github
└── ISSUE_TEMPLATE
│ └── custom.md
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── array-loop.html
├── array-loop.js
├── counter-loop.html
├── counter-loop.js
├── examples
├── arrayloop-example.png
├── counterloop-example.png
├── example.json
└── whileloop-example.png
├── icons
└── loop.png
├── locales
└── en-US
│ ├── array-loop.json
│ ├── counter-loop.json
│ └── while-loop.json
├── package.json
├── while-loop.html
└── while-loop.js
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["eslint:recommended"],
3 | "plugins": ["node"],
4 | "env": {
5 | "es6": true,
6 | "node": true
7 | },
8 | "globals": {
9 | "msg": true
10 | },
11 | "rules": {}
12 | }
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/custom.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Custom issue template
3 | about: Any
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | (If you would like, please provide the information.)
11 |
12 | ## Environment
13 |
14 | - OS:
15 | - Node.js version:
16 | - node-red-contrib-loop-processing version:
17 | - Node-RED Flow([Export from your editor](https://nodered.org/docs/user-guide/editor/workspace/import-export)):
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
63 | package-lock.json
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Changelog
2 | ==
3 |
4 | ## [0.5.1](https://github.com/s1r-J/node-red-contrib-loop-processing/tree/0.5.0) (2021/02/27)
5 | [Full Changelog](https://github.com/s1r-J/node-red-contrib-loop-processing/compare/0.5.0...0.5.1)
6 |
7 | - Remove template literal #17
8 |
9 | ## [0.5.0](https://github.com/s1r-J/node-red-contrib-loop-processing/tree/0.5.0) (2021/02/27)
10 | [Full Changelog](https://github.com/s1r-J/node-red-contrib-loop-processing/compare/0.4.0...0.5.0)
11 |
12 | - Add loop time limitation to while-loop #15
13 | - Update docs
14 | - how to break out of loop
15 | - Fix typo in README #15
16 |
17 | ## [0.4.0](https://github.com/s1r-J/node-red-contrib-loop-processing/tree/0.4.0) (2020/03/21)
18 | [Full Changelog](https://github.com/s1r-J/node-red-contrib-loop-processing/compare/0.3.1...0.4.0)
19 |
20 | - Add operators to counter-loop
21 | - less than or equal
22 | - greater than
23 | - greater than or equal
24 | - equal
25 |
26 | ## [0.3.1](https://github.com/s1r-J/node-red-contrib-loop-processing/tree/0.3.0) (2019/04/12)
27 | [Full Changelog](https://github.com/s1r-J/node-red-contrib-loop-processing/compare/0.3.0...0.3.1)
28 |
29 | - Fix specified flow context falling back to msg in array-loop #8
30 |
31 | ## [0.3.0](https://github.com/s1r-J/node-red-contrib-loop-processing/tree/0.3.0) (2019/04/12)
32 | [Full Changelog](https://github.com/s1r-J/node-red-contrib-loop-processing/compare/0.2.0...0.3.0)
33 |
34 | - Add feature of resetting counter variable in counter-loop
35 | - Add feature of resetting key variable in array-loop
36 | - Update example flow
37 | - Update description of the usage of flow and global context in while-loop
38 | - Fix typo in README.md #3
39 |
40 | ## [0.2.0](https://github.com/s1r-J/node-red-contrib-loop-processing/tree/0.2.0) (2019/02/15)
41 | [Full Changelog](https://github.com/s1r-J/node-red-contrib-loop-processing/compare/0.1.2...0.2.0)
42 |
43 | - Fix typo in counter-loop.html
44 | - Add new while-loop node
45 | - Add while-loop node example flow
46 |
47 | ## [0.1.2](https://github.com/s1r-J/node-red-contrib-loop-processing/tree/0.1.2) (2019/02/13)
48 | [Full Changelog](https://github.com/s1r-J/node-red-contrib-loop-processing/compare/0.1.1...0.1.2)
49 |
50 | - Fix typo in README.md
51 | - Add repository property to package.json for fixing images on npm website
52 |
53 | ## [0.1.1](https://github.com/s1r-J/node-red-contrib-loop-processing/tree/0.1.1) (2019/02/12)
54 | [Full Changelog](https://github.com/s1r-J/node-red-contrib-loop-processing/compare/0.1.0...0.1.1)
55 |
56 | - Fix example image links in README.md
57 |
58 | ## [0.1.0](https://github.com/s1r-J/node-red-contrib-loop-processing/tree/0.1.0) (2019/02/12)
59 |
60 | First release
61 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | node-red-contrib-loop-processing
2 | ====
3 |
4 | Node-RED nodes to help a flow looping.
5 |
6 | ## Description
7 |
8 | This module has 3 nodes.
9 |
10 | - [counter-loop](#counter-loop)
11 | - [array-loop](#array-loop)
12 | - [while-loop](#while-loop)
13 |
14 | ### counter-loop
15 |
16 | Using a counter variable, a flow loops like a for-loop.
17 |
18 | Set the following inputs:
19 | - property using as the counter variable
20 | - initial value
21 | - terminal value (and operator)
22 | - increment value
23 |
24 | If the conditions is true, a flow is sent to the lower output port ('true' outputLabel).
25 | If false, the flow is sent to the upper output port ('false' outputLabel).
26 |
27 | When the flow exits the loop, the counter variable can reset by setting to `null`, `undefined` or empty string. This is useful for creating a multi-loop.
28 |
29 | 
30 |
31 | ### array-loop
32 |
33 | Until the end of an array, a flow loops. This node is similar to forEach or for-of,
34 | but this node cannot handle an associated array.
35 |
36 | Set the following inputs:
37 | - property using as the key variable
38 | - array
39 |
40 | If the conditions is true, a flow is sent to the lower output port.
41 | If false, the flow is sent to the upper output port ('end loop' outputLabel).
42 |
43 | When the flow exits the loop, the key variable can reset by setting to `null`, `undefined` or empty string. This is useful for creating a multi-loop.
44 |
45 | 
46 |
47 | ### while-loop
48 |
49 | *since 0.2.0*
50 |
51 | Using a condition expression, a flow loops like a while loop.
52 |
53 | If the expression is true, a flow is sent to the lower output port ('true' outputLabel).
54 | If false, the flow is sent to the upper output port ('false' outputLabel).
55 |
56 | 
57 |
58 | ## Usage
59 |
60 | Example flow is in examples/example.json.
61 |
62 | Drag & drop in your Node-RED editor.
63 |
64 | ## Install
65 |
66 | [](https://nodei.co/npm/node-red-contrib-loop-processing/)
67 |
68 | ## Changelog
69 |
70 | [Changelog](https://github.com/s1r-J/node-red-contrib-loop-processing/blob/master/CHANGELOG.md)
71 |
72 | ## Licence
73 |
74 | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.html)
75 |
76 | ## Author
77 |
78 | [s1r-J](https://github.com/s1r-J)
--------------------------------------------------------------------------------
/array-loop.html:
--------------------------------------------------------------------------------
1 |
42 |
43 |
63 |
64 |
137 |
--------------------------------------------------------------------------------
/array-loop.js:
--------------------------------------------------------------------------------
1 | module.exports = function (RED) {
2 | 'use strict';
3 |
4 | function setProperty(node, msg, name, type, value) {
5 | if (type === 'msg') {
6 | msg[name] = value;
7 | } else if (type === 'flow') {
8 | node.context().flow.set(name, value);
9 | } else if (type === 'global') {
10 | node.context().global.set(name, value);
11 | }
12 | }
13 |
14 | function sendErrorMessage(node, text) {
15 | node.status({
16 | fill: 'red',
17 | shape: 'ring',
18 | text: text
19 | });
20 | node.error(text, msg);
21 | }
22 |
23 | function ArrayLoopNode(n) {
24 | RED.nodes.createNode(this, n);
25 | var node = this;
26 |
27 | node.key = n.key;
28 | node.keyType = n.keyType;
29 | node.array = n.array;
30 | node.arrayType = n.arrayType;
31 | node.reset = n.reset;
32 | node.resetValue = n.resetValue;
33 |
34 | this.on('input', function (msg) {
35 | let key = RED.util.evaluateNodeProperty(node.key, node.keyType, node, msg);
36 | let array = RED.util.evaluateNodeProperty(node.array, node.arrayType, node, msg);
37 |
38 | if (!Array.isArray(array)) {
39 | let text = RED._('array-loop.errors.arraynotarray') + array;
40 | sendErrorMessage(node, text);
41 | }
42 |
43 | if (key === void 0 || key === null || key === '') {
44 | // initialize
45 | key = 0;
46 | } else {
47 | key += 1;
48 | }
49 |
50 | setProperty(node, msg, node.key, node.keyType, key);
51 | setProperty(node, msg, node.array, node.arrayType, array);
52 |
53 | let isLoop = array.length > key;
54 | if (isLoop) {
55 | // run in loop
56 | node.status({
57 | fill: 'blue',
58 | shape: 'dot',
59 | text: 'loop'
60 | });
61 | msg.payload = array[key];
62 | node.send([null, msg]);
63 | } else {
64 | // exit loop
65 | if (node.reset) {
66 | let resetValue = null;
67 | switch (node.resetValue) {
68 | case 'value-null':
69 | resetValue = null;
70 | break;
71 | case 'value-undefined':
72 | resetValue = undefined;
73 | break;
74 | case 'value-empty':
75 | resetValue = '';
76 | break;
77 | default:
78 | // not come here
79 | }
80 | setProperty(node, msg, node.key, node.keyType, resetValue);
81 | }
82 | node.status({
83 | fill: 'grey',
84 | shape: 'ring',
85 | text: 'exit loop'
86 | });
87 | node.send(msg);
88 | }
89 | });
90 |
91 | this.on('close', function () {
92 | node.status({});
93 | });
94 | }
95 |
96 | RED.nodes.registerType('array-loop', ArrayLoopNode);
97 | }
98 |
--------------------------------------------------------------------------------
/counter-loop.html:
--------------------------------------------------------------------------------
1 |
61 |
62 |
82 |
83 |
204 |
--------------------------------------------------------------------------------
/counter-loop.js:
--------------------------------------------------------------------------------
1 | module.exports = function (RED) {
2 | 'use strict';
3 |
4 | function setProperty(node, msg, name, type, value) {
5 | if (type === 'msg') {
6 | msg[name] = value;
7 | } else if (type === 'flow') {
8 | node.context().flow.set(name, value);
9 | } else if (type === 'global') {
10 | node.context().global.set(name, value);
11 | }
12 | }
13 |
14 | function sendErrorMessage(node, text) {
15 | node.status({
16 | fill: 'red',
17 | shape: 'ring',
18 | text: text
19 | });
20 | node.error(text, msg);
21 | }
22 |
23 | function CounterLoopNode(n) {
24 | RED.nodes.createNode(this, n);
25 | var node = this;
26 |
27 | node.counter = n.counter;
28 | node.counterType = n.counterType;
29 | node.initial = n.initial;
30 | node.initialType = n.initialType;
31 | node.operator = n.operator;
32 | node.termination = n.termination;
33 | node.terminationType = n.terminationType;
34 | node.increment = n.increment;
35 | node.incrementType = n.incrementType;
36 | node.reset = n.reset;
37 | node.resetValue = n.resetValue;
38 |
39 | this.on('input', function (msg) {
40 | let counter = RED.util.evaluateNodeProperty(node.counter, node.counterType, node, msg);
41 | let initial = RED.util.evaluateNodeProperty(node.initial, node.initialType, node, msg);
42 | let termination = RED.util.evaluateNodeProperty(node.termination, node.terminationType, node, msg);
43 | let increment = RED.util.evaluateNodeProperty(node.increment, node.incrementType, node, msg);
44 |
45 | if (typeof initial !== 'number') {
46 | let text = RED._('counter-loop.errors.initialnotnumber') + initial;
47 | sendErrorMessage(node, text);
48 | }
49 | if (typeof termination !== 'number') {
50 | let text = RED._('counter-loop.errors.terminationnotnumber') + termination;
51 | sendErrorMessage(node, text);
52 | }
53 | if (typeof increment !== 'number') {
54 | let text = RED._('counter-loop.errors.incrementnotnumber') + increment;
55 | sendErrorMessage(node, text);
56 | }
57 |
58 | if (counter === void 0 || counter === null || counter === '') {
59 | // initialize
60 | counter = initial;
61 | } else {
62 | counter += increment;
63 | }
64 |
65 | setProperty(node, msg, node.counter, node.counterType, counter);
66 | setProperty(node, msg, node.initial, node.initialType, initial);
67 | setProperty(node, msg, node.termination, node.terminationType, termination);
68 | setProperty(node, msg, node.increment, node.incrementType, increment);
69 |
70 | let isLoop = false;
71 | switch (node.operator) {
72 | case 'lt':
73 | isLoop = counter < termination;
74 | break;
75 | case 'lte':
76 | isLoop = counter <= termination;
77 | break;
78 | case 'gt':
79 | isLoop = counter > termination;
80 | break;
81 | case 'gte':
82 | isLoop = counter >= termination;
83 | break;
84 | case 'eq':
85 | isLoop = counter == termination;
86 | break;
87 | default:
88 | sendErrorMessage(node, RED._('counter-loop.errors.invalidoperator'));
89 | }
90 | if (isLoop) {
91 | // run in loop
92 | node.status({
93 | fill: 'blue',
94 | shape: 'dot',
95 | text: 'loop'
96 | });
97 | node.send([null, msg]);
98 | } else {
99 | // exit loop
100 | if (node.reset) {
101 | let resetValue = null;
102 | switch (node.resetValue) {
103 | case 'value-null':
104 | resetValue = null;
105 | break;
106 | case 'value-undefined':
107 | resetValue = undefined;
108 | break;
109 | case 'value-empty':
110 | resetValue = '';
111 | break;
112 | default:
113 | // not come here
114 | }
115 | setProperty(node, msg, node.counter, node.counterType, resetValue);
116 | }
117 | node.status({
118 | fill: 'grey',
119 | shape: 'ring',
120 | text: 'exit loop'
121 | });
122 | node.send(msg);
123 | }
124 | });
125 |
126 | this.on('close', function () {
127 | node.status({});
128 | });
129 | }
130 | RED.nodes.registerType('counter-loop', CounterLoopNode);
131 | }
132 |
--------------------------------------------------------------------------------
/examples/arrayloop-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s1r-J/node-red-contrib-loop-processing/adedad704b15bd77e620d967f529bbb40590c61e/examples/arrayloop-example.png
--------------------------------------------------------------------------------
/examples/counterloop-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s1r-J/node-red-contrib-loop-processing/adedad704b15bd77e620d967f529bbb40590c61e/examples/counterloop-example.png
--------------------------------------------------------------------------------
/examples/example.json:
--------------------------------------------------------------------------------
1 | [{"id":"fd2d7768.345b98","type":"inject","z":"9d509964.2610d","name":"msg.payload = \"Red\"","topic":"","payload":"Red","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":145,"y":150,"wires":[["320b54d6.e604d4"]]},{"id":"676a128f.8a03dc","type":"debug","z":"9d509964.2610d","name":"msg.result = RedRedRedRedRed","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"result","targetType":"msg","x":635,"y":150,"wires":[]},{"id":"5d19cb24.ac3444","type":"change","z":"9d509964.2610d","name":"msg.result = msg.result + msg.payload","rules":[{"t":"set","p":"result","pt":"msg","to":"$join([msg.result, msg.payload])","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":450,"y":225,"wires":[["320b54d6.e604d4"]]},{"id":"1a60a19a.718526","type":"array-loop","z":"9d509964.2610d","name":"array-loop","key":"al1a60a19a718526","keyType":"msg","reset":false,"resetValue":"value-null","array":"array","arrayType":"msg","x":645,"y":345,"wires":[["2a06fe8f.883d92"],["a9176a45.c66998"]]},{"id":"122377f3.e507f8","type":"inject","z":"9d509964.2610d","name":"msg.payload = \"A, B, C, D, E\"","topic":"","payload":"A, B, C, D, E","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":165,"y":345,"wires":[["c129d4e9.65ec6"]]},{"id":"2a06fe8f.883d92","type":"debug","z":"9d509964.2610d","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":815,"y":345,"wires":[]},{"id":"c129d4e9.65ec6","type":"change","z":"9d509964.2610d","name":"split msg.payload by \", \"","rules":[{"t":"set","p":"array","pt":"msg","to":"$split(msg.payload, ', ')","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":425,"y":345,"wires":[["1a60a19a.718526"]]},{"id":"a72c351c.98ba48","type":"function","z":"9d509964.2610d","name":"return msg","func":"\nreturn msg;","outputs":1,"noerr":0,"x":805,"y":420,"wires":[["548c8549.cc9904","1a60a19a.718526"]]},{"id":"548c8549.cc9904","type":"debug","z":"9d509964.2610d","name":"1st: \"A\", 2nd: \"B\", 3rd: \"C\", 4th: \"D\", 5th: \"E\"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":1100,"y":420,"wires":[]},{"id":"a9176a45.c66998","type":"delay","z":"9d509964.2610d","name":"","pauseType":"delay","timeout":"1","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":645,"y":420,"wires":[["a72c351c.98ba48"]]},{"id":"320b54d6.e604d4","type":"counter-loop","z":"9d509964.2610d","name":"counter-loop","counter":"il320b54d6e604d4","counterType":"msg","reset":false,"resetValue":"value-null","initial":"0","initialType":"num","operator":"lt","termination":"5","terminationType":"num","increment":1,"incrementType":"num","x":370,"y":150,"wires":[["676a128f.8a03dc"],["5d19cb24.ac3444"]]},{"id":"1c8465fc.10e082","type":"while-loop","z":"9d509964.2610d","name":"while-loop","condi":"msg.payload % 3 !== 0","x":315,"y":555,"wires":[["b887c6d1.43c668"],["24b7b946.fcc9b6"]]},{"id":"7e4cca20.588e7c","type":"inject","z":"9d509964.2610d","name":"msg.payload = 5","topic":"","payload":"5","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":125,"y":555,"wires":[["1c8465fc.10e082"]]},{"id":"b887c6d1.43c668","type":"debug","z":"9d509964.2610d","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":550,"y":555,"wires":[]},{"id":"24b7b946.fcc9b6","type":"change","z":"9d509964.2610d","name":"msg.payload--","rules":[{"t":"set","p":"payload","pt":"msg","to":"$number(msg.payload) -1","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":335,"y":630,"wires":[["a72010f2.69313","64d645c5.fe848c"]]},{"id":"64d645c5.fe848c","type":"debug","z":"9d509964.2610d","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":520,"y":675,"wires":[]},{"id":"a72010f2.69313","type":"delay","z":"9d509964.2610d","name":"","pauseType":"delay","timeout":"1","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":510,"y":630,"wires":[["1c8465fc.10e082"]]},{"id":"d5600b9a.fd9288","type":"comment","z":"9d509964.2610d","name":"msg.payload % 3 !== 0","info":"","x":355,"y":510,"wires":[]}]
2 |
--------------------------------------------------------------------------------
/examples/whileloop-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s1r-J/node-red-contrib-loop-processing/adedad704b15bd77e620d967f529bbb40590c61e/examples/whileloop-example.png
--------------------------------------------------------------------------------
/icons/loop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s1r-J/node-red-contrib-loop-processing/adedad704b15bd77e620d967f529bbb40590c61e/icons/loop.png
--------------------------------------------------------------------------------
/locales/en-US/array-loop.json:
--------------------------------------------------------------------------------
1 | {
2 | "array-loop": {
3 | "label": {
4 | "name": "Name",
5 | "key": "Key Variable",
6 | "array": "Array"
7 | },
8 | "errors": {
9 | "arraynotarray": "Array is not array:"
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/locales/en-US/counter-loop.json:
--------------------------------------------------------------------------------
1 | {
2 | "counter-loop": {
3 | "label": {
4 | "name": "Name",
5 | "counter": "Counter Variable",
6 | "initial": "Initialization",
7 | "termination": "Termination",
8 | "increment": "Increment"
9 | },
10 | "errors": {
11 | "initialnotnumber": "Initialization field is not number:",
12 | "terminationnotnumber": "Termination field is not number:",
13 | "incrementnotnumber": "Increment field is not number:",
14 | "invalidoperator": "Operator is invalid"
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/locales/en-US/while-loop.json:
--------------------------------------------------------------------------------
1 | {
2 | "while-loop": {
3 | "label": {
4 | "name": "Name",
5 | "condi": "Condition Expression",
6 | "time": "Time Variable"
7 | },
8 | "errors": {
9 | "inputListener": "Cannot add listener to 'input' event within while-loop",
10 | "non-message-returned": "while-loop tried to send a message of type __type__"
11 | },
12 | "messages": {
13 | "overLimitTime": "Loop time exceeded the specified value: __limit__"
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-red-contrib-loop-processing",
3 | "version": "0.5.1",
4 | "description": "Node-RED nodes to help flow looping.",
5 | "main": "node.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "lint": "eslint ./array-loop.js ./counter-loop.js ./while-loop.js --fix"
9 | },
10 | "node-red": {
11 | "nodes": {
12 | "index-loop": "counter-loop.js",
13 | "array-loop": "array-loop.js",
14 | "while-loop": "while-loop.js"
15 | }
16 | },
17 | "dependencies": {
18 | "util": "^0.11.1",
19 | "vm": "^0.1.0"
20 | },
21 | "devDependencies": {
22 | "eslint": "^5.13.0",
23 | "eslint-plugin-node": "^8.0.1"
24 | },
25 | "keywords": [
26 | "node-red",
27 | "loop",
28 | "repeat",
29 | "iterator",
30 | "array"
31 | ],
32 | "author": "s1r-J",
33 | "license": "Apache-2.0",
34 | "repository": {
35 | "type": "git",
36 | "url": "https://github.com/s1r-J/node-red-contrib-loop-processing.git"
37 | },
38 | "bugs": {
39 | "url": "https://github.com/s1r-J/node-red-contrib-loop-processing/issues"
40 | },
41 | "homepage": "https://github.com/s1r-J/node-red-contrib-loop-processing#readme"
42 | }
43 |
--------------------------------------------------------------------------------
/while-loop.html:
--------------------------------------------------------------------------------
1 |
30 |
31 |
47 |
48 |
100 |
--------------------------------------------------------------------------------
/while-loop.js:
--------------------------------------------------------------------------------
1 | module.exports = function (RED) {
2 | 'use strict';
3 | const util = require('util');
4 | const vm = require('vm');
5 |
6 | function sendResults(node, _msgid, msgs) {
7 | if (msgs == null) {
8 | return;
9 | } else if (!util.isArray(msgs)) {
10 | msgs = [msgs];
11 | }
12 | var msgCount = 0;
13 | for (var m = 0; m < msgs.length; m++) {
14 | if (msgs[m]) {
15 | if (!util.isArray(msgs[m])) {
16 | msgs[m] = [msgs[m]];
17 | }
18 | for (var n = 0; n < msgs[m].length; n++) {
19 | var msg = msgs[m][n];
20 | if (msg !== null && msg !== undefined) {
21 | if (typeof msg === 'object' && !Buffer.isBuffer(msg) && !util.isArray(msg)) {
22 | msg._msgid = _msgid;
23 | msgCount++;
24 | } else {
25 | var type = typeof msg;
26 | if (type === 'object') {
27 | type = Buffer.isBuffer(msg) ? 'Buffer' : (util.isArray(msg) ? 'Array' : 'Date');
28 | }
29 | node.error(RED._("while-loop.errors.non-message-returned", {
30 | type: type
31 | }));
32 | }
33 | }
34 | }
35 | }
36 | }
37 | if (msgCount > 0) {
38 | node.send(msgs);
39 | }
40 | }
41 |
42 | function setProperty(node, msg, name, type, value) {
43 | if (type === 'msg') {
44 | msg[name] = value;
45 | } else if (type === 'flow') {
46 | node.context().flow.set(name, value);
47 | } else if (type === 'global') {
48 | node.context().global.set(name, value);
49 | }
50 | }
51 |
52 | function WhileLoopNode(n) {
53 | RED.nodes.createNode(this, n);
54 | var node = this;
55 | node.condi = n.condi;
56 | node.time = n.time;
57 | node.timeType = n.timeType;
58 | node.limit = n.limit;
59 | node.limitTime = n.limitTime;
60 |
61 | var sandbox = {
62 | isLoop: false,
63 | console: console,
64 | util: util,
65 | Buffer: Buffer,
66 | Date: Date,
67 | RED: {
68 | util: RED.util
69 | },
70 | __node__: {
71 | id: node.id,
72 | name: node.name,
73 | log: function () {
74 | node.log.apply(node, arguments);
75 | },
76 | error: function () {
77 | node.error.apply(node, arguments);
78 | },
79 | warn: function () {
80 | node.warn.apply(node, arguments);
81 | },
82 | debug: function () {
83 | node.debug.apply(node, arguments);
84 | },
85 | trace: function () {
86 | node.trace.apply(node, arguments);
87 | },
88 | send: function (id, msgs) {
89 | sendResults(node, id, msgs);
90 | },
91 | on: function () {
92 | if (arguments[0] === "input") {
93 | throw new Error(RED._("while-loop.errors.inputListener"));
94 | }
95 | node.on.apply(node, arguments);
96 | },
97 | status: function () {
98 | node.status.apply(node, arguments);
99 | }
100 | },
101 | context: {
102 | set: function () {
103 | node.context().set.apply(node, arguments);
104 | },
105 | get: function () {
106 | return node.context().get.apply(node, arguments);
107 | },
108 | keys: function () {
109 | return node.context().keys.apply(node, arguments);
110 | },
111 | get global() {
112 | return node.context().global;
113 | },
114 | get flow() {
115 | return node.context().flow;
116 | }
117 | },
118 | flow: {
119 | set: function () {
120 | node.context().flow.set.apply(node, arguments);
121 | },
122 | get: function () {
123 | return node.context().flow.get.apply(node, arguments);
124 | },
125 | keys: function () {
126 | return node.context().flow.keys.apply(node, arguments);
127 | }
128 | },
129 | global: {
130 | set: function () {
131 | node.context().global.set.apply(node, arguments);
132 | },
133 | get: function () {
134 | return node.context().global.get.apply(node, arguments);
135 | },
136 | keys: function () {
137 | return node.context().global.keys.apply(node, arguments);
138 | }
139 | }
140 | };
141 |
142 | this.on('input', function (msg) {
143 | let isLoop = true;
144 | if (node.limit) {
145 | let time = RED.util.evaluateNodeProperty(node.time, node.timeType, node, msg);
146 | if (time === void 0 || time === null || time === '') {
147 | // initialize
148 | time = 0;
149 | } else {
150 | time++;
151 | }
152 | setProperty(node, msg, node.time, node.timeType, time);
153 |
154 | isLoop = time < node.limitTime;
155 | if (!isLoop) {
156 | // exit loop
157 | node.status({
158 | fill: 'grey',
159 | shape: 'ring',
160 | text: RED._('while-loop.messages.overLimitTime', {'limit': node.limitTime})
161 | });
162 | node.send(msg);
163 | }
164 | }
165 |
166 | if (isLoop) {
167 | sandbox.msg = msg;
168 | vm.createContext(sandbox);
169 | vm.runInContext('isLoop = (' + node.condi + ');', sandbox);
170 |
171 | if (sandbox.isLoop) {
172 | // run in loop
173 | node.status({
174 | fill: 'blue',
175 | shape: 'dot',
176 | text: 'loop'
177 | });
178 | node.send([null, msg]);
179 | } else {
180 | // exit loop
181 | node.status({
182 | fill: 'grey',
183 | shape: 'ring',
184 | text: 'exit loop'
185 | });
186 | node.send(msg);
187 | }
188 | }
189 | });
190 |
191 | this.on('close', function () {
192 | node.status({});
193 | });
194 | }
195 |
196 | RED.nodes.registerType('while-loop', WhileLoopNode);
197 | }
198 |
--------------------------------------------------------------------------------