I'm using Node-RED in a Docker environment (v3.0.2) and I'm working on a flow where a user can input an ID into a search bar. Upon entering the ID, the flow triggers the first HTTP GET request, which retrieves some data, including another ID. This second ID is then used in a subsequent HTTP GET request. The data from this final request needs to be extracted and displayed on the dashboard as cards. However, I'm having trouble figuring out how to implement the cards functionality.
Here is the flow:
[
{
"id": "53671ee299b6a523",
"type": "http in",
"z": "da8676b5460203c4",
"name": "",
"url": "/EvaReels",
"method": "get",
"upload": false,
"swaggerDoc": "",
"x": 280,
"y": 620,
"wires": [
[
"c46c70a0a44fd00a"
]
]
},
{
"id": "c46c70a0a44fd00a",
"type": "template",
"z": "da8676b5460203c4",
"name": "CSS",
"field": "payload.style",
"fieldType": "msg",
"format": "css",
"syntax": "mustache",
"template": "@import url(\"https://fonts.googleapis.com/css2?family=Roboto&display=swap\");\n\n :root {\n --font-header: 'Roboto', sans-serif;\n --color-header: #434C5E;\n --size-header: 44px;\n --weight-header: 500;\n\n --font-main: 'Roboto', sans-serif;\n --color-main: #4C566A;\n --size-main: 16px;\n\n --color-bg: #D8DEE9;\n --color-card: #F2F4F8;\n\n --color-btn-main: #494F58;\n --color-btn-main-hover: #272C34;\n --color-btn-font: #D8DEE9;\n --color-btn-sec: #4C566A;\n --color-btn-sec-hover: #79B8CA;\n\n --color-span: #BF616A;\n\n --size-btn: 1em;\n\n --shadow-card: rgb(184 194 215 / 25%) 0px 4px 6px, rgb(184 194 215 / 10%) 0px 5px 7px;\n }\n\n body {\n background: var(--color-bg);\n color: var(--color-main);\n font-family: var(--font-main);\n margin: 0;\n display: flex;\n flex-direction: column;\n min-height: 100vh;\n }\n\n .content {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n }\n\n header > h1 {\n text-align: center;\n font-family: var(--font-header);\n font-size: var(--size-header);\n font-weight: var(--weight-header);\n color: var(--color-header);\n margin-top: 20px;\n }\n\n .container {\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n width: 100%;\n flex: 1;\n }\n\n .card {\n width: 640px;\n border-radius: 8px;\n padding: 20px;\n background: var(--color-card);\n box-shadow: var(--shadow-card);\n font-family: var(--font-main);\n font-weight: 500;\n text-align: left;\n margin-bottom: 20px;\n }\n\n .card button,\n .card select,\n .card input[type=\"text\"] {\n font-family: var(--font-main);\n }\n\n .card select,\n .card input[type=\"text\"] {\n width: calc(100% - 22px);\n border: 3px solid #FFFFFF;\n border-radius: 0.3em;\n padding: 0.75em;\n margin: 10px 0;\n background: #FFFFFF;\n font-size: 16px;\n transition: border 0.3s ease-in-out;\n }\n\n .card input[type=\"text\"]:hover {\n border: 2px solid var(--color-btn-main);\n }\n\n .card button {\n border: none;\n border-radius: 0.25em;\n background: var(--color-btn-main);\n padding: 0.75em 1.5em;\n margin: 10px 0;\n text-decoration: none;\n color: var(--color-btn-font);\n font-size: var(--size-btn);\n transition: background 0.3s ease-in-out;\n cursor: pointer;\n }\n\n .card button:hover {\n background: var(--color-btn-main-hover);\n }\n\n footer.shock-team {\n text-align: center;\n font-family: var(--font-main);\n font-size: 20px;\n padding: 10px;\n background-color: var(--color-bg);\n }\n\n footer.shock-team span {\n color: var(--color-span);\n font-weight: 500;\n };",
"output": "str",
"x": 570,
"y": 620,
"wires": [
[
"142bf4ec6a49281b",
"e40e14e87e1a6be3"
]
]
},
{
"id": "c67721a4f329f29f",
"type": "template",
"z": "da8676b5460203c4",
"name": "HTML",
"field": "payload",
"fieldType": "msg",
"format": "html",
"syntax": "mustache",
"template": "\n<html>\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <script>\n {{{payload.script}}}\n </script>\n <title>test title</title>\n <link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css2?family=Roboto&display=swap\">\n <style>\n {{{payload.style}}}\n </style>\n</head>\n<body>\n <header>\n <h1>test</h1>\n </header>\n <div class=\"content\">\n <div class=\"container\">\n <div class=\"card\" id=\"input-card\">\n <input type=\"text\" id=\"unit-id\" placeholder=\"Enter Unit ID Here\">\n <button id=\"send-data\">Send data</button>\n </div>\n </div>\n <div id=\"cardsContainer\"></div>\n </div>\n</body>\n</html>\n\n",
"output": "str",
"x": 850,
"y": 620,
"wires": [
[
"91330581920bebec",
"9b54acede1a484a1"
]
]
},
{
"id": "91330581920bebec",
"type": "http response",
"z": "da8676b5460203c4",
"name": "",
"statusCode": "",
"headers": {},
"x": 990,
"y": 600,
"wires": []
},
{
"id": "aa2b7f1aa8a80f9b",
"type": "http in",
"z": "da8676b5460203c4",
"name": "First Get",
"url": "/generate-url",
"method": "post",
"upload": false,
"swaggerDoc": "",
"x": 300,
"y": 380,
"wires": [
[
"d9a097e7ff9c30e4",
"8de1e5931c7ec4ad"
]
]
},
{
"id": "8de1e5931c7ec4ad",
"type": "function",
"z": "da8676b5460203c4",
"name": "Generate URL",
"func": "\nvar unitId = msg.payload.unitId;\nvar baseUrl = \"test\";\nvar encodedUnitId = encodeURIComponent(unitId);\nvar url = baseUrl + encodedUnitId + \",SHAPE\";\n\nconst newMsg = {\n url: url,\n rejectUnauthorized: false\n};\n\n\nmsg.payload = { url: url };\nnode.send(newMsg);\n\nreturn msg;\n",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 540,
"y": 380,
"wires": [
[
"8c330e24799a51fc",
"d9a097e7ff9c30e4"
]
]
},
{
"id": "8c330e24799a51fc",
"type": "http request",
"z": "da8676b5460203c4",
"name": "",
"method": "GET",
"ret": "obj",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"credentials": {},
"x": 730,
"y": 380,
"wires": [
[
"5c4e96b74aab3460",
"0d793378040edb65"
]
]
},
{
"id": "5c4e96b74aab3460",
"type": "debug",
"z": "da8676b5460203c4",
"name": "",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 910,
"y": 360,
"wires": []
},
{
"id": "d9a097e7ff9c30e4",
"type": "debug",
"z": "da8676b5460203c4",
"name": "",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 490,
"y": 340,
"wires": []
},
{
"id": "79ef36103caf3445",
"type": "http in",
"z": "da8676b5460203c4",
"name": "Second Get",
"url": "/generate-url-testplan",
"method": "post",
"upload": false,
"swaggerDoc": "",
"x": 210,
"y": 480,
"wires": [
[
"0d793378040edb65"
]
]
},
{
"id": "0d793378040edb65",
"type": "function",
"z": "da8676b5460203c4",
"name": "Generate URL",
"func": "var testPlanId = msg.payload.data.material.materialId;\nvar baseUrl = \"test\";\nvar url = baseUrl + testPlanId + \",SVC_LOP_SN/testdata?testdataFilter=newest\";\n\n const newMsg = {\n url:url,\n \"rejectUnauthorized\": false}\n\nmsg.payload = { url: url };\nnode.send(newMsg);\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 500,
"y": 480,
"wires": [
[
"b2ad5373238be773",
"3062a896452cd191"
]
]
},
{
"id": "b2ad5373238be773",
"type": "http request",
"z": "da8676b5460203c4",
"name": "",
"method": "GET",
"ret": "obj",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"credentials": {},
"x": 710,
"y": 480,
"wires": [
[
"67ca2d8c51630316",
"754beff7eeaf8551"
]
]
},
{
"id": "67ca2d8c51630316",
"type": "debug",
"z": "da8676b5460203c4",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 710,
"y": 520,
"wires": []
},
{
"id": "3062a896452cd191",
"type": "debug",
"z": "da8676b5460203c4",
"name": "testplanfunction",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 520,
"y": 520,
"wires": []
},
{
"id": "9daed210cc565903",
"type": "debug",
"z": "da8676b5460203c4",
"name": "JavaScript",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 850,
"y": 660,
"wires": []
},
{
"id": "9b54acede1a484a1",
"type": "debug",
"z": "da8676b5460203c4",
"name": "HTML",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1010,
"y": 660,
"wires": []
},
{
"id": "754beff7eeaf8551",
"type": "function",
"z": "da8676b5460203c4",
"name": "",
"func": "if (msg.payload && Array.isArray(msg.payload.data) && msg.payload.data.length > 0) {\n let data = msg.payload.data\n .map(item => item.text)\n .filter(text => text !== undefined);\n\n return { payload: { texts: data } };\n} else {\n return { payload: { error: \"Data array is missing or empty\" } };\n}",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 900,
"y": 480,
"wires": [
[
"79c284f596ed6885",
"c46c70a0a44fd00a"
]
]
},
{
"id": "79c284f596ed6885",
"type": "debug",
"z": "da8676b5460203c4",
"name": "debug 16",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1080,
"y": 460,
"wires": []
},
{
"id": "142bf4ec6a49281b",
"type": "debug",
"z": "da8676b5460203c4",
"name": "CSS",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 690,
"y": 660,
"wires": []
},
{
"id": "e40e14e87e1a6be3",
"type": "template",
"z": "da8676b5460203c4",
"name": "JS",
"field": "payload.script",
"fieldType": "msg",
"format": "javascript",
"syntax": "mustache",
"template": "\n var testdata= [{{{ payload.data[0].text}}}] \nconsole.log(testdata);\ndocument.addEventListener(\"DOMContentLoaded\", function () {\n console.log(\"DOM fully loaded and parsed\");\n \n let cardsAdded = false;\n\n function createCards(testdata) {\n console.log(\"createCards function called with data:\", testdata);\n const container = document.querySelector(\".container\");\n const inputCard = document.getElementById(\"input-card\");\n\n if (!testdata || !Array.isArray(testdata) || testdata.length === 0) {\n handleError(\"No valid text data received.\");\n return;\n }\n\n console.log(\"Text from array:\", testdata);\n\n const cardEntries = testdata\n .split(\";\")\n .map((entry) => entry.trim())\n .filter((entry) => entry);\n\n console.log(\"Card Entries:\", cardEntries);\n\n // Clear existing cards except inputCard\n if (cardsAdded) {\n const existingCards = Array.from(container.querySelectorAll(\".card\"));\n existingCards.forEach((card) => {\n if (card !== inputCard) {\n container.removeChild(card);\n }\n });\n }\n\n // Create and append new cards\n cardEntries.forEach((entry) => {\n const newCard = document.createElement(\"div\");\n newCard.classList.add(\"card\");\n newCard.innerHTML = `<p>${entry}</p>`;\n container.insertBefore(newCard, inputCard.nextSibling);\n });\n\n cardsAdded = true;\n }\n\n function handleError(message) {\n const error = document.getElementById(\"error\");\n if (error) {\n error.textContent = message;\n }\n console.error(message);\n }\n\n function fetchData(unitId) {\n console.log(`Fetching data for unitId: ${unitId}`);\n fetch(\"/generate-url\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ unitId: unitId }),\n })\n .then((response) => {\n console.log(\"Response received:\", response);\n return response.json();\n })\n .then((data) => {\n console.log(\"Data received:\", data);\n if (data.payload && data.payload.texts && data.payload.texts.length > 0) {\n createCards(data.payload.texts);\n } else {\n handleError(\"No valid text data received.\");\n }\n })\n .catch((error) => {\n handleError(\"Failed to fetch data: \" + error.message);\n });\n }\n\n document.getElementById(\"send-data\").addEventListener(\"click\", function () {\n console.log(\"Send Data button clicked\");\n const unitId = document.getElementById(\"unit-id\").value;\n if (unitId) {\n fetchData(unitId);\n } else {\n alert(\"Please enter a Unit ID.\");\n }\n });\n\n document.getElementById(\"unit-id\").addEventListener(\"keypress\", function (e) {\n if (e.key === \"Enter\") {\n console.log(\"Enter key pressed\");\n const unitId = document.getElementById(\"unit-id\").value;\n if (unitId) {\n fetchData(unitId);\n } else {\n alert(\"Please enter a Unit ID.\");\n }\n e.preventDefault();\n }\n });\n});",
"output": "str",
"x": 710,
"y": 620,
"wires": [
[
"c67721a4f329f29f",
"9daed210cc565903"
]
]
}
]
Here is the data that should be placed in the cards:
Any help would be much appreciated.