├── .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 | ![counter-loop](./examples/counterloop-example.png) 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 | ![array-loop](./examples/arrayloop-example.png) 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 | ![while-loop](./examples/whileloop-example.png) 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 | [![NPM](https://nodei.co/npm/node-red-contrib-loop-processing.png)](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 | --------------------------------------------------------------------------------