Editable table (CRUD) using flow context as a database
About
This flow uses the html template and js-grid to display, create, remove, update and delete data rows.
No database is required - it utilises flow context only.
If you want to persist the data, you can enable file storage for context (see the node-red docs)
Instructions
- Import the flow & deploy it
- Open a new tab on your browser at the address
http://<node-red>:<port>/index
.
Demo
Flow
Credit
This flow is an adaption of the great flow developed by rishanaziz
[{"id":"9568c2b2.ae4098","type":"http in","z":"38c5e956.a8e786","name":"","url":"/update","method":"put","swaggerDoc":"","x":198,"y":784,"wires":[["de0d507c.2e3c6"]]},{"id":"80b7d10e.159df","type":"http response","z":"38c5e956.a8e786","name":"","x":930,"y":784,"wires":[]},{"id":"a8dde467.e5dcd8","type":"template","z":"38c5e956.a8e786","name":"Web Template","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<!DOCTYPE html>\n<html lang=\"en-GB\">\n<head>\n <title>Users</title>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css\">\n <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/bootstrap-timepicker/0.5.2/css/bootstrap-timepicker.min.css\" />\n <script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js\"></script>\n <script src=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js\"></script>\n <link type=\"text/css\" rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/jsgrid/1.5.3/jsgrid.min.css\" />\n <link type=\"text/css\" rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/jsgrid/1.5.3/jsgrid-theme.min.css\" />\n <script type=\"text/javascript\" src=\"https://cdnjs.cloudflare.com/ajax/libs/jsgrid/1.5.3/jsgrid.min.js\"></script>\n\n <script type=\"text/javascript\">\n $(function () {\n console.log(\"crud demo\");\n\n var db = {{#payload}}{{{.}}}{{/payload}};\n\n $(\"#jsgrid\").jsGrid({\n width: \"100%\",\n inserting: true,\n editing: true,\n sorting: true,\n paging: true,\n\n data: db,\n\n fields: [\n { title:\"ID\", name: \"id\", type: \"number\", width: 20, readOnly: true },\n { title:\"User ID\", name: \"userID\", type: \"text\", width: 50 },\n { title:\"First Name\", name: \"foreName\", type: \"text\", width: 50 },\n { title:\"Last Name\", name: \"sirName\", type: \"text\", width: 50 },\n { title:\"Age\", name: \"age\", type:\"number\", width: 25},\n { type: \"control\" }\n ],\n \n controller: {\n insertItem: function(item) {\n return $.ajax({\n type: \"POST\",\n url: \"/insert\",\n data: item\n });\n },\n updateItem: function(item) {\n return $.ajax({\n type: \"PUT\",\n url: \"/update\",\n data: item\n });\n },\n deleteItem: function(item) {\n return $.ajax({\n type: \"DELETE\",\n url: \"/delete\",\n data: item\n });\n }\n } \n });\n });\n \n </script>\n</head>\n<body class=\"container\">\n <section class=\"row\">\n \n <div class=\"col-md-6\"></div>\n <div class=\"col-md-6\" id=\"jsgrid\">\n </div>\n </section>\n</body>\n</html>\n\n","x":720,"y":720,"wires":[["e4bd0337.229be8"]]},{"id":"3fa8dafa.229966","type":"http in","z":"38c5e956.a8e786","name":"","url":"/index","method":"get","upload":false,"swaggerDoc":"","x":188,"y":720,"wires":[["5a339947.2e2cf8"]]},{"id":"e4bd0337.229be8","type":"http response","z":"38c5e956.a8e786","name":"","x":930,"y":720,"wires":[]},{"id":"66d97ed7.39f1c","type":"http response","z":"38c5e956.a8e786","name":"","x":930,"y":848,"wires":[]},{"id":"a60ce791.6f5ae8","type":"http in","z":"38c5e956.a8e786","name":"","url":"/insert","method":"post","swaggerDoc":"","x":198,"y":848,"wires":[["9ca4b651.6f2aa8"]]},{"id":"5f79d9fd.25c4f","type":"http in","z":"38c5e956.a8e786","name":"","url":"/delete","method":"delete","swaggerDoc":"","x":208,"y":912,"wires":[["fe020366.04d91"]]},{"id":"17b5b5c4.a8a7b2","type":"http response","z":"38c5e956.a8e786","name":"","x":930,"y":912,"wires":[]},{"id":"a2dfdc4a.dc161","type":"function","z":"38c5e956.a8e786","name":"Generate dummy data","func":"const firstNames = [\"Liam\",\"Noah\",\"Oliver\",\"William\",\"Elijah\",\"James\",\"Benjamin\",\"Lucas\",\"Mason\",\"Ethan\",\"Alexander\",\"Henry\",\"Jacob\",\"Michael\",\"Daniel\",\"Logan\",\"Jackson\",\"Sebastian\",\"Jack\",\"Aiden\"];\nconst sirNames = [\"Jones\",\"Taylor\",\"Williams\",\"Brown\",\"White\",\"Harris\",\"Martin\",\"Davies\",\"Wilson\",\"Cooper\",\"Evans\",\"King\",\"Thomas\",\"Baker\",\"Green\",\"Wright\",\"Johnson\",\"Edwards\",\"Clark\",\"Roberts\",\"Robinson\",\"Hall\",\"Lewis\",\"Young\",\"Davis\",\"Turner\",\"Hill\",\"Phillips\",\"Collins\",\"Allen\",\"Moore\",\"Thompson\",\"Carter\",\"James\",\"Knight\",\"Walker\",\"Wood\",\"Hughes\",\"Parker\",\"Ward\",\"Bennett\",\"Cook\",\"Webb\",\"Bailey\",\"Scott\",\"Jackson\",\"Lee\",\"Cox\"];\n \n \n\nvar database = [];\n\nfor(let i = 1; i <= 30; i++) {\n var item = {\"id\":i, \"timestamp\":Date.now()};\n item.foreName = randomFirstname();\n item.sirName = randomSirname();\n item.userID = \"U\" + randomInt(1000, 2999);\n item.age = randomInt(20, 65);\n database.push(item);\n}\n\n\nfunction randomFirstname() {\n return firstNames[randomInt(0, firstNames.length-1)];\n}\n\nfunction randomSirname() {\n return sirNames[randomInt(0, sirNames.length-1)];\n}\n\nfunction randomInt(min, max) { \n return Math.floor(Math.random() * (max - min + 1) + min);\n}\n\nflow.set(\"database\", database);\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":436,"y":640,"wires":[[]]},{"id":"3b023f12.50205","type":"inject","z":"38c5e956.a8e786","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":159,"y":640,"wires":[["a2dfdc4a.dc161"]],"l":false},{"id":"de0d507c.2e3c6","type":"function","z":"38c5e956.a8e786","name":"update-query","func":"\nvar database = flow.get(\"database\") || [];\n\nvar item = database.find(e => e.id == msg.payload.id );\n\nif(msg.payload.foreName == \"\" || msg.payload.sirName == \"\" || msg.payload.userID == \"\") {\n msg.statusCode = 400;\n msg.payload = null;\n return msg;\n}\n\nif(item) {\n item.foreName = msg.payload.foreName;\n item.sirName = msg.payload.sirName;\n item.age = msg.payload.age;\n item.userID = msg.payload.userID;\n flow.set(\"database\", database);\n} else {\n msg.statusCode = 404;//not found\n msg.payload = null;\n return msg;\n}\nmsg.payload = item;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":390,"y":784,"wires":[["80b7d10e.159df"]]},{"id":"9ca4b651.6f2aa8","type":"function","z":"38c5e956.a8e786","name":"insert-query","func":"var database = flow.get(\"database\") || [];\nvar nextID = Math.max.apply(Math, database.map(function(o) { return o.id; })) + 1;\nvar item = {\n id: nextID,\n timestamp: Date.now(),\n foreName : msg.payload.foreName,\n sirName : msg.payload.sirName,\n age : msg.payload.age,\n userID : msg.payload.userID,\n}\n\nif(item.foreName == \"\" || item.sirName == \"\" || item.userID == \"\") {\n msg.statusCode = 400;\n msg.payload = null;\n return msg;\n}\n\ndatabase.push(item);\nflow.set(\"database\", database);\n\nmsg.payload = item;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":390,"y":848,"wires":[["66d97ed7.39f1c"]]},{"id":"fe020366.04d91","type":"function","z":"38c5e956.a8e786","name":"delete-query","func":"\nvar database = flow.get(\"database\") || [];\n\ndatabase = database.filter(function( obj ) {\n return obj.id != msg.payload.id;\n});\n\nflow.set(\"database\", database);\n\nmsg.payload = database;\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":390,"y":912,"wires":[["17b5b5c4.a8a7b2"]]},{"id":"5a339947.2e2cf8","type":"function","z":"38c5e956.a8e786","name":"get database","func":"\nvar database = flow.get(\"database\") || [];\nmsg.payload = database;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":390,"y":720,"wires":[["43fff851.e2c7f8"]]},{"id":"43fff851.e2c7f8","type":"json","z":"38c5e956.a8e786","name":"","x":546,"y":720,"wires":[["a8dde467.e5dcd8"]]}]