Timelinepicker for Node-Red in Dashboard?

On Openhab you can create a timeline picker. You simply click into a timeline, hold the mousebutton and drag where/when you want a device to be enabled.

Also AVMs Fritzbox supports a similiar feature

I now there are a lot of very good timers available. However I look for an easily customizable timer for Dashboard.

1 Like

Hi,
that indeed would be a very useful UI node. But unfortunately I don't think something similar exists already...
Bart

Perhaps this would suffice?...

Should be easy enough to adapt those 1's and 0's into a command to dynamically program the cron-plus node (or any scheduler node that accepts programmable/dynamic input)

The flow...

[{"id":"ba591917.ba3828","type":"ui_template","z":"42ea7bd7.2e3c24","group":"dce9e7a2.d20c78","name":"TimeSheet js css","order":5,"width":0,"height":0,"format":"\n\n<link rel=\"stylesheet\" href=\"https://www.jqueryscript.net/demo/Table-Based-jQuery-Calendar-Schedule-Plugin-TimeSheet/css/TimeSheet.css\" type=\"text/css\" media=\"screen\">\n<script type=\"text/javascript\" src=\"https://www.jqueryscript.net/demo/Table-Based-jQuery-Calendar-Schedule-Plugin-TimeSheet/js/TimeSheet.js\"></script>\n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"global","x":450,"y":960,"wires":[[]]},{"id":"52c4340b.74d2cc","type":"ui_template","z":"42ea7bd7.2e3c24","group":"6d01ec93.b1d374","name":"","order":4,"width":"12","height":"14","format":"<div>\n    <div style=\"padding:15px 0 10px;\">\n        <button class=\"J_sheetControl\" id=\"J_timingDisable\">Disable</button>\n        <button class=\"J_sheetControl\" id=\"J_timingEnable\">Enable</button>\n        <button class=\"J_sheetControl\" id=\"J_timingClean\">Clean</button>\n        <button class=\"J_sheetControl\" id=\"J_timingSubmit\">Submit</button>\n        <button class=\"J_sheetControl\" id=\"J_timingIsFull\">IsFull</button>\n        <div style=\"padding:15px 0 5px;\">\n            <input type=\"text\" placeholder=\"0,0\" id=\"J_cellIndex\" value=\"0,0\"/>\n            <button class=\"J_sheetControl\" id=\"J_timingGetCell\">GetCell</button>\n        </div>\n        <div style=\"padding:10px 0;\">\n            <input type=\"number\" placeholder=\"0\" id=\"J_rowIndex\" value=\"0\"/>\n            <button class=\"J_sheetControl\" id=\"J_timingGetRow\">GetRow</button>\n        </div>\n    </div>\n    <div id=\"J_calenderWrapper\">\n        <table>\n            <thead></thead>\n            <tbody id=\"J_timedSheet\">\n    \n            </tbody>\n        </table>\n    </div>\n</div>\n\n<script type=\"text/javascript\">\n\n    var dimensions = [7,24];\n\n    var yAxis = [\n        {name:\"Mon\"},{name:\"Tue\"},{name:\"Wed\"},{name:\"Thu\"},{name:\"Fru\"},\n        {name:\"Sat\"},{name:\"Sun\"}\n    ];\n\n    var xAxis = [\n        {name:\"00\",title:\"00-01\"},{name:\"01\",title:\"01-02\"},{name:\"02\",title:\"02-03\"},{name:\"03\",title:\"03-04\"},\n        {name:\"04\",title:\"04-05\"},{name:\"05\",title:\"05-06\"},{name:\"06\",title:\"06-07\"},{name:\"07\",title:\"07-08\"},\n        {name:\"08\",title:\"08-09\"},{name:\"09\",title:\"09-10\"},{name:\"10\",title:\"10-11\"},{name:\"11\",title:\"11-12\"},\n        {name:\"12\",title:\"12-13\"},{name:\"13\",title:\"13-14\"},{name:\"14\",title:\"14-15\"},{name:\"15\",title:\"15-16\"},\n        {name:\"16\",title:\"16-17\"},{name:\"17\",title:\"17-18\"},{name:\"18\",title:\"18-19\"},{name:\"19\",title:\"19-20\"},\n        {name:\"20\",title:\"20-21\"},{name:\"21\",title:\"21-22\"},{name:\"22\",title:\"22-23\"},{name:\"23\",title:\"23-00\"}\n    ];\n\n    var sheetData = [\n        [1,1,1,0,1,0,1,1,0,1,1,0,1,1,1,0,0,1,0,0,0,0,0,0],\n        [0,1,0,0,1,0,1,0,1,0,1,0,1,0,0,0,0,1,0,0,0,0,0,0],\n        [0,1,0,0,1,0,1,0,1,0,1,0,1,0,0,0,0,1,0,0,0,0,0,0],\n        [0,1,0,0,1,0,1,0,1,0,1,0,1,1,1,0,0,1,0,0,0,0,0,0],\n        [0,1,0,0,1,0,1,0,1,0,1,0,1,0,0,0,0,1,0,0,0,0,0,0],\n        [0,1,0,0,1,0,1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0],\n        [0,1,0,0,1,0,1,0,1,0,1,0,1,1,1,0,0,1,0,0,0,0,0,0],        \n    ];\n\n    var updateRemark = function(sheet){\n\n        var sheetStates = sheet.getSheetStates();\n        var rowsCount = dimensions[0];\n        var colsCount = dimensions[1];\n        var rowRemark = [];\n        var rowRemarkLen = 0;\n        var remarkHTML = '';\n\n        for(var row= 0, rowStates=[]; row<rowsCount; ++row){\n            rowRemark = [];\n            rowStates = sheetStates[row];\n            for(var col=0; col<colsCount; ++col){\n                if(rowStates[col]===0 && rowStates[col-1]===1){\n                    rowRemark[rowRemarkLen-1] += (col<=10?'0':'')+col+':00';\n                }else if(rowStates[col]===1 && (rowStates[col-1]===0 || rowStates[col-1]===undefined)){\n                    rowRemarkLen = rowRemark.push((col<=10?'0':'')+col+':00-');\n                }\n                if(rowStates[col]===1 && col===colsCount-1){\n                    rowRemark[rowRemarkLen-1] += '00:00';\n                }\n            }\n            remarkHTML = rowRemark.join(\",\");\n            sheet.setRemark(row,remarkHTML==='' ? sheet.getDefaultRemark() : remarkHTML);\n        }\n    };\n\n    (function(scope) {\ndebugger\n        var sheet = $(\"#J_timedSheet\").TimeSheet({\n            data: {\n                dimensions : dimensions,\n                colHead : xAxis,\n                rowHead : yAxis,\n                sheetHead : {name:\"Date\\\\Time\"},\n                sheetData : sheetData\n            },\n            remarks : {\n                title : \"Description\",\n                default : \"N/A\"\n            },\n            end : function(ev,selectedArea){\n                updateRemark(sheet);\n            }\n        });\n\n        updateRemark(sheet);\n\n        $(\"#J_timingDisable\").click(function(ev){\n            sheet.disable();\n        });\n\n        $(\"#J_timingEnable\").click(function(ev){\n            sheet.enable();\n        });\n\n        $(\"#J_timingClean\").click(function(ev){\n            sheet.clean();\n        });\n\n        $(\"#J_timingSubmit\").click(function(ev){\n\n            var sheetStates = sheet.getSheetStates();\n            var rowsCount = dimensions[0];\n            var $submitDataDisplay = $(\"#J_dataDisplay\") ;\n            scope.send({ topic: \"sheet/all\", payload: sheetStates });\n            \n        });\n\n        $(\"#J_timingIsFull\").click(function(ev){\n            alert(sheet.isFull());\n        });\n\n        $(\"#J_timingGetCell\").click(function(ev){\n            var cellIndex = $(\"#J_cellIndex\").val().split(',');\n            var cellData = sheet.getCellState(cellIndex);\n            var $dataDisplay = $(\"#J_dataDisplay\") ;\n            scope.send({ topic: \"sheet/cell/\" + cellIndex[0] + \"/\" + cellIndex[1], payload: cellData });\n        });\n\n        $(\"#J_timingGetRow\").click(function(ev){\n            var rowIndex = $(\"#J_rowIndex\").val();\n            var rowData = sheet.getRowStates(rowIndex);\n            var $dataDisplay = $(\"#J_dataDisplay\") ;\n            scope.send({ topic: \"sheet/row/\" + rowIndex, payload: rowData });\n        });\n\n\n\n    })(scope);\n</script>\n\n\n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":420,"y":1000,"wires":[["3fb2a061.51fcd"]]},{"id":"3fb2a061.51fcd","type":"debug","z":"42ea7bd7.2e3c24","name":"","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":610,"y":1000,"wires":[]},{"id":"dce9e7a2.d20c78","type":"ui_group","name":"Object detection","tab":"5132060d.4cde48","order":1,"disp":true,"width":"7","collapse":false},{"id":"6d01ec93.b1d374","type":"ui_group","name":"UserEntry","tab":"5132060d.4cde48","order":2,"disp":true,"width":"12","collapse":false},{"id":"5132060d.4cde48","type":"ui_tab","name":"Home","icon":"dashboard","disabled":false,"hidden":false}]

NOTES...

  • demo adapted from this demo
  • git project here
  • the CDN used in the head template is not an official CDN so might disappear. Recommend downloading src from git and adding the JS and CSS to the head template directly (or make it avaiable on your node-red server)
3 Likes

and here it is hooked up to CRON-PLUS (to generate dynamic schedules)...

[{"id":"ba591917.ba3828","type":"ui_template","z":"42ea7bd7.2e3c24","group":"dce9e7a2.d20c78","name":"TimeSheet js css","order":5,"width":0,"height":0,"format":"\n\n<link rel=\"stylesheet\" href=\"https://www.jqueryscript.net/demo/Table-Based-jQuery-Calendar-Schedule-Plugin-TimeSheet/css/TimeSheet.css\" type=\"text/css\" media=\"screen\">\n<script type=\"text/javascript\" src=\"https://www.jqueryscript.net/demo/Table-Based-jQuery-Calendar-Schedule-Plugin-TimeSheet/js/TimeSheet.js\"></script>\n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"global","x":450,"y":960,"wires":[[]]},{"id":"52c4340b.74d2cc","type":"ui_template","z":"42ea7bd7.2e3c24","group":"6d01ec93.b1d374","name":"","order":4,"width":"12","height":"14","format":"<div>\n    <div style=\"padding:15px 0 10px;\">\n        <button class=\"J_sheetControl\" id=\"J_timingDisable\">Disable</button>\n        <button class=\"J_sheetControl\" id=\"J_timingEnable\">Enable</button>\n        <button class=\"J_sheetControl\" id=\"J_timingClean\">Clean</button>\n        <button class=\"J_sheetControl\" id=\"J_timingSubmit\">Submit</button>\n        <button class=\"J_sheetControl\" id=\"J_timingIsFull\">IsFull</button>\n        <div style=\"padding:15px 0 5px;\">\n            <input type=\"text\" placeholder=\"0,0\" id=\"J_cellIndex\" value=\"0,0\"/>\n            <button class=\"J_sheetControl\" id=\"J_timingGetCell\">GetCell</button>\n        </div>\n        <div style=\"padding:10px 0;\">\n            <input type=\"number\" placeholder=\"0\" id=\"J_rowIndex\" value=\"0\"/>\n            <button class=\"J_sheetControl\" id=\"J_timingGetRow\">GetRow</button>\n        </div>\n    </div>\n    <div id=\"J_calenderWrapper\">\n        <table>\n            <thead></thead>\n            <tbody id=\"J_timedSheet\">\n    \n            </tbody>\n        </table>\n    </div>\n</div>\n\n<script type=\"text/javascript\">\n\n    var dimensions = [7,24];\n\n    var yAxis = [\n        {name:\"Mon\"},{name:\"Tue\"},{name:\"Wed\"},{name:\"Thu\"},{name:\"Fru\"},\n        {name:\"Sat\"},{name:\"Sun\"}\n    ];\n\n    var xAxis = [\n        {name:\"00\",title:\"00-01\"},{name:\"01\",title:\"01-02\"},{name:\"02\",title:\"02-03\"},{name:\"03\",title:\"03-04\"},\n        {name:\"04\",title:\"04-05\"},{name:\"05\",title:\"05-06\"},{name:\"06\",title:\"06-07\"},{name:\"07\",title:\"07-08\"},\n        {name:\"08\",title:\"08-09\"},{name:\"09\",title:\"09-10\"},{name:\"10\",title:\"10-11\"},{name:\"11\",title:\"11-12\"},\n        {name:\"12\",title:\"12-13\"},{name:\"13\",title:\"13-14\"},{name:\"14\",title:\"14-15\"},{name:\"15\",title:\"15-16\"},\n        {name:\"16\",title:\"16-17\"},{name:\"17\",title:\"17-18\"},{name:\"18\",title:\"18-19\"},{name:\"19\",title:\"19-20\"},\n        {name:\"20\",title:\"20-21\"},{name:\"21\",title:\"21-22\"},{name:\"22\",title:\"22-23\"},{name:\"23\",title:\"23-00\"}\n    ];\n\n    var sheetData = [\n        [1,1,1,0,1,0,1,1,0,1,1,0,1,1,1,0,0,1,0,0,0,0,0,0],\n        [0,1,0,0,1,0,1,0,1,0,1,0,1,0,0,0,0,1,0,0,0,0,0,0],\n        [0,1,0,0,1,0,1,0,1,0,1,0,1,0,0,0,0,1,0,0,0,0,0,0],\n        [0,1,0,0,1,0,1,0,1,0,1,0,1,1,1,0,0,1,0,0,0,0,0,0],\n        [0,1,0,0,1,0,1,0,1,0,1,0,1,0,0,0,0,1,0,0,0,0,0,0],\n        [0,1,0,0,1,0,1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0],\n        [0,1,0,0,1,0,1,0,1,0,1,0,1,1,1,0,0,1,0,0,0,0,0,0],        \n    ];\n\n    var updateRemark = function(sheet){\n\n        var sheetStates = sheet.getSheetStates();\n        var rowsCount = dimensions[0];\n        var colsCount = dimensions[1];\n        var rowRemark = [];\n        var rowRemarkLen = 0;\n        var remarkHTML = '';\n\n        for(var row= 0, rowStates=[]; row<rowsCount; ++row){\n            rowRemark = [];\n            rowStates = sheetStates[row];\n            for(var col=0; col<colsCount; ++col){\n                if(rowStates[col]===0 && rowStates[col-1]===1){\n                    rowRemark[rowRemarkLen-1] += (col<=10?'0':'')+col+':00';\n                }else if(rowStates[col]===1 && (rowStates[col-1]===0 || rowStates[col-1]===undefined)){\n                    rowRemarkLen = rowRemark.push((col<=10?'0':'')+col+':00-');\n                }\n                if(rowStates[col]===1 && col===colsCount-1){\n                    rowRemark[rowRemarkLen-1] += '00:00';\n                }\n            }\n            remarkHTML = rowRemark.join(\",\");\n            sheet.setRemark(row,remarkHTML==='' ? sheet.getDefaultRemark() : remarkHTML);\n        }\n    };\n\n    (function(scope) {\ndebugger\n        var sheet = $(\"#J_timedSheet\").TimeSheet({\n            data: {\n                dimensions : dimensions,\n                colHead : xAxis,\n                rowHead : yAxis,\n                sheetHead : {name:\"Date\\\\Time\"},\n                sheetData : sheetData\n            },\n            remarks : {\n                title : \"Description\",\n                default : \"N/A\"\n            },\n            end : function(ev,selectedArea){\n                updateRemark(sheet);\n            }\n        });\n\n        updateRemark(sheet);\n\n        $(\"#J_timingDisable\").click(function(ev){\n            sheet.disable();\n        });\n\n        $(\"#J_timingEnable\").click(function(ev){\n            sheet.enable();\n        });\n\n        $(\"#J_timingClean\").click(function(ev){\n            sheet.clean();\n        });\n\n        $(\"#J_timingSubmit\").click(function(ev){\n\n            var sheetStates = sheet.getSheetStates();\n            var rowsCount = dimensions[0];\n            var $submitDataDisplay = $(\"#J_dataDisplay\") ;\n            scope.send({ topic: \"sheet/all\", payload: sheetStates });\n            \n        });\n\n        $(\"#J_timingIsFull\").click(function(ev){\n            alert(sheet.isFull());\n        });\n\n        $(\"#J_timingGetCell\").click(function(ev){\n            var cellIndex = $(\"#J_cellIndex\").val().split(',');\n            var cellData = sheet.getCellState(cellIndex);\n            var $dataDisplay = $(\"#J_dataDisplay\") ;\n            scope.send({ topic: \"sheet/cell/\" + cellIndex[0] + \"/\" + cellIndex[1], payload: cellData });\n        });\n\n        $(\"#J_timingGetRow\").click(function(ev){\n            var rowIndex = $(\"#J_rowIndex\").val();\n            var rowData = sheet.getRowStates(rowIndex);\n            var $dataDisplay = $(\"#J_dataDisplay\") ;\n            scope.send({ topic: \"sheet/row/\" + rowIndex, payload: rowData });\n        });\n\n\n\n    })(scope);\n</script>\n\n\n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":420,"y":1000,"wires":[["3fb2a061.51fcd","e76906a0.c7c718"]]},{"id":"3fb2a061.51fcd","type":"debug","z":"42ea7bd7.2e3c24","name":"","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":610,"y":1000,"wires":[]},{"id":"dc325ddd.d276c","type":"cronplus","z":"42ea7bd7.2e3c24","name":"","outputField":"payload","timeZone":"","persistDynamic":false,"commandResponseMsgOutput":"output1","outputs":1,"options":[],"x":840,"y":1120,"wires":[[]]},{"id":"e76906a0.c7c718","type":"switch","z":"42ea7bd7.2e3c24","name":"","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"sheet/all","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":590,"y":1060,"wires":[["931be6f6.1fc0e8"]]},{"id":"931be6f6.1fc0e8","type":"function","z":"42ea7bd7.2e3c24","name":"generate cron schedules","func":"var days = [\"MON\", \"TUE\", \"WED\", \"THU\", \"FRI\", \"SAT\", \"SUN\"]\nvar dynamicCronCommands = [{ topic: \"remove-all-dynamic\", payload: { \"command\":\"remove-all-dynamic\"} }];\n\nfor (let index = 0; index < msg.payload.length; index++) {\n    const sched = msg.payload[index];\n    var ons = generateDynamicCronCmd(sched,index, \"on\");//on schedule\n    var offs = generateDynamicCronCmd(sched, index, \"off\");//off schedule\n    dynamicCronCommands.push(ons);\n    dynamicCronCommands.push(offs);\n}\n\n\n\nfunction generateDynamicCronCmd(data, dayIndex, on_or_off) {\n    var dayName = days[dayIndex];\n    var on_or_off_int = (on_or_off == \"on\" || on_or_off == \"ON\" || on_or_off == 1) ? 1 : 0;\n    var scheduleName = dayName + \"_\" + on_or_off;\n    var cronExpression = `0 0 ${arrayToCronHourString(data, on_or_off_int)} * * ${dayName} *`;\n    var cmd = {\n        \"command\" : \"add\",\n        \"name\": scheduleName,\n        \"topic\": scheduleName,\n        \"expression\": cronExpression,\n        \"expressionType\": \"cron\",\n        \"payloadType\": \"str\",\n        \"payload\": on_or_off_int,\n    }\n    return cmd;\n}\n\n\nfunction arrayToCronHourString(arr, ifValue) {\n    var result = [];\n    for (let index = 0; index < arr.length; index++) {\n        const element = arr[index];\n        if(element == ifValue) {\n            result.push(index)\n        }\n    }\n    return result.join(\",\")\n}\nmsg.payload = dynamicCronCommands;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":800,"y":1060,"wires":[["8e712d91.f9ebc","dc325ddd.d276c"]]},{"id":"8e712d91.f9ebc","type":"debug","z":"42ea7bd7.2e3c24","name":"","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":1010,"y":1060,"wires":[]},{"id":"14b42658.01e6ea","type":"inject","z":"42ea7bd7.2e3c24","name":"remove-all-dynamic","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"remove-all-dynamic","payload":"","payloadType":"date","x":470,"y":1120,"wires":[["dc325ddd.d276c"]]},{"id":"dce9e7a2.d20c78","type":"ui_group","name":"Object detection","tab":"5132060d.4cde48","order":1,"disp":true,"width":"7","collapse":false},{"id":"6d01ec93.b1d374","type":"ui_group","name":"UserEntry","tab":"5132060d.4cde48","order":2,"disp":true,"width":"12","collapse":false},{"id":"5132060d.4cde48","type":"ui_tab","name":"Home","icon":"dashboard","disabled":false,"hidden":false}]
1 Like

Very nice Steve!!
But I see it is a JQuery widget, but he needs a planner/scheduler for his dashboard. So I don't think he can use this...

Dashboard has JQuery exposed.

Thats how our nodes access things like $(".myclass")

1 Like

Ah ok, I have been wondering about that in the past...
Good to know. Ah now I see your first demo is using the template node... Just finished my working day. Brains are still melting...

1 Like

Sorry for the late reply. Thanks you very much. That looks really nice. I'll test it in the next days and report back.