node-red-contrib-chronos
Version:
Time-based Node-RED scheduling, repeating, queueing, routing, filtering and manipulating nodes
789 lines (730 loc) • 79.1 kB
HTML
<!--
Copyright (c) 2020 - 2025 Jens-Uwe Rossbach
This code is licensed under the MIT License.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-->
<script type="text/html" data-template-name="chronos-switch">
<style>
a:focus:not(:focus-visible)
{
outline: none;
}
</style>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red-contrib-chronos/chronos-config:common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]node-red-contrib-chronos/chronos-config:common.label.name">
</div>
<div class="form-row">
<label for="node-input-config"><i class="fa fa-cog"></i> <span data-i18n="node-red-contrib-chronos/chronos-config:common.label.config"></span></label>
<input type="text" id="node-input-config" data-i18n="[placeholder]node-red-contrib-chronos/chronos-config:common.label.config">
</div>
<div class="form-row" style="padding-top: 4px;">
<label for="node-input-baseTime"><i class="fa fa-clock-o"></i> <span data-i18n="node-red-contrib-chronos/chronos-config:common.label.baseTime"></span></label>
<input type="text" id="node-input-baseTime" style="width: 70%;">
<input id="node-input-baseTimeType" type="hidden">
</div>
<div class="form-row node-input-conditionList-row" style="padding-top: 10px">
<label for="node-input-conditionList"><i class="fa fa-sliders"></i> <span data-i18n="node-red-contrib-chronos/chronos-config:common.label.conditions"></span></label>
<div class="form-row">
<ol id="node-input-conditionList"></ol>
</div>
</div>
<div class="form-row">
<input id="node-input-stopOnFirstMatch" type="checkbox" style="margin-top: 0px; margin-bottom: 1px; width: auto;">
<label for="node-input-stopOnFirstMatch" style="margin-bottom: 0px; width: auto;" data-i18n="switch.label.stopOnFirstMatch"></label>
</div>
</script>
<script type="text/javascript">
(function()
{
const PATTERN_DATETIME = /^(?:(?:(?:(?:[1-9]\d)?\d\d)-(?:[1-9]|0[1-9]|1[0-2])-(?:[1-9]|0[1-9]|[12]\d|3[01])|(?:(?:[1-9]|0[1-9]|[12]\d|3[01])\.(?:[1-9]|0[1-9]|1[0-2])\.(?:(?:[1-9]\d)?\d\d))|(?:(?:[1-9]|0[1-9]|1[0-2])\/(?:[1-9]|0[1-9]|[12]\d|3[01])\/(?:(?:[1-9]\d)?\d\d)))\s)?(?:\d|0\d|1\d|2[0-3]):(?:[0-5]\d)(?::(?:[0-5]\d))?(?:\s(?:a|am|A|AM|p|pm|P|PM))?$/;
const PATTERN_CUSTOM_TIME = /^[a-zA-Z][0-9a-zA-Z_]*$/;
const months =
[
"january",
"february",
"march",
"april",
"may",
"june",
"july",
"august",
"september",
"october",
"november",
"december"
];
const weekdays =
[
"sunday", // moment.js uses US order -> Sunday is first day of the week
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday"
];
RED.nodes.registerType("chronos-switch",
{
category: "chronos",
color: "#DEB887",
icon: "chronos_switch.svg",
inputs: 1,
outputs: 1,
paletteLabel: "time switch",
label: function()
{
return (this.name || this._("switch.label.node"));
},
labelStyle: function()
{
return (this.name ? "node_label_italic" : "");
},
inputLabels: function()
{
return this._("node-red-contrib-chronos/chronos-config:common.label.inputPort");
},
outputLabels: function(index)
{
return this.conditions[index].label;
},
defaults:
{
name:
{
value: ""
},
config:
{
value: "",
type: "chronos-config",
required: true
},
baseTime:
{
value: "",
validate: RED.validators.typedInput("baseTimeType")
},
baseTimeType:
{
value: "msgIngress"
},
conditions:
{
value: [{operator: "before", operands: {type: "time", value: "", offset: 0, random: false}, label: ""}],
validate: function(items, opt)
{
if (!items || (items.length == 0))
{
return this._("node-red-contrib-chronos/chronos-config:common.error.noConditions");
}
const errors = [];
for (const item of items)
{
if (/^equal|notEqual|before|until|since|after$/.test(item.operator))
{
if ((item.operands.type == "time") && !PATTERN_DATETIME.test(item.operands.value))
{
errors.push(this._("node-red-contrib-chronos/chronos-config:common.error.invalidTime"));
}
else if ((item.operands.type == "custom") && !PATTERN_CUSTOM_TIME.test(item.operands.value))
{
errors.push(this._("node-red-contrib-chronos/chronos-config:common.error.invalidName"));
}
else if (/^env|global|flow|msg$/.test(item.operands.type))
{
const res = RED.utils.validateTypedProperty(item.operands.value, item.operands.type, opt)
if (res !== true)
{
errors.push(res);
}
}
}
else if (/^between|outside$/.test(item.operator))
{
if ((item.operands[0].type == "time") && !PATTERN_DATETIME.test(item.operands[0].value))
{
errors.push(this._("node-red-contrib-chronos/chronos-config:common.error.invalidTime"));
}
else if ((item.operands[0].type == "custom") && !PATTERN_CUSTOM_TIME.test(item.operands[0].value))
{
errors.push(this._("node-red-contrib-chronos/chronos-config:common.error.invalidName"));
}
else if (/^env|global|flow|msg$/.test(item.operands[0].type))
{
const res = RED.utils.validateTypedProperty(item.operands[0].value, item.operands[0].type, opt)
if (res !== true)
{
errors.push(res);
}
}
if ((item.operands[1].type == "time") && !PATTERN_DATETIME.test(item.operands[1].value))
{
errors.push(this._("node-red-contrib-chronos/chronos-config:common.error.invalidTime"));
}
else if ((item.operands[1].type == "custom") && !PATTERN_CUSTOM_TIME.test(item.operands[1].value))
{
errors.push(this._("node-red-contrib-chronos/chronos-config:common.error.invalidName"));
}
else if (/^env|global|flow|msg$/.test(item.operands[1].type))
{
const res = RED.utils.validateTypedProperty(item.operands[1].value, item.operands[1].type, opt)
if (res !== true)
{
errors.push(res);
}
}
}
else if (item.operator == "expression")
{
const res = RED.utils.validateTypedProperty(item.expression, "jsonata", opt)
if (res !== true)
{
errors.push(res);
}
}
else if (item.operator == "context")
{
const res = RED.utils.validateTypedProperty(item.context.value, item.context.type, opt)
if (res !== true)
{
errors.push(res);
}
}
}
return (errors.length > 0) ? errors : true;
}
},
stopOnFirstMatch:
{
value: false
},
outputs:
{
value: 1
}
},
oneditprepare: function()
{
let node = this;
const msgIngressInput =
{
value: "msgIngress",
label: node._("node-red-contrib-chronos/chronos-config:common.label.msgIngress"),
hasValue: false
};
let baseTime = $("#node-input-baseTime")
.typedInput({types: [msgIngressInput, "global", "flow", "msg"], typeField: "#node-input-baseTimeType"});
let conditionList = $("#node-input-conditionList").css("min-width", "545px").css("min-height", "150px").editableList(
{
removable: true,
sortable: true,
addItem: function(item, index, data)
{
const sunTimes =
[
{
value: "nightEnd",
label: node._("node-red-contrib-chronos/chronos-config:common.list.sun.nightEnd")
},
{
value: "nauticalDawn",
label: node._("node-red-contrib-chronos/chronos-config:common.list.sun.nauticalDawn")
},
{
value: "dawn",
label: node._("node-red-contrib-chronos/chronos-config:common.list.sun.dawn")
},
{
value: "sunrise",
label: node._("node-red-contrib-chronos/chronos-config:common.list.sun.sunrise")
},
{
value: "sunriseEnd",
label: node._("node-red-contrib-chronos/chronos-config:common.list.sun.sunriseEnd")
},
{
value: "goldenHourEnd",
label: node._("node-red-contrib-chronos/chronos-config:common.list.sun.goldenHourEnd")
},
{
value: "solarNoon",
label: node._("node-red-contrib-chronos/chronos-config:common.list.sun.solarNoon")
},
{
value: "goldenHour",
label: node._("node-red-contrib-chronos/chronos-config:common.list.sun.goldenHour")
},
{
value: "sunsetStart",
label: node._("node-red-contrib-chronos/chronos-config:common.list.sun.sunsetStart")
},
{
value: "sunset",
label: node._("node-red-contrib-chronos/chronos-config:common.list.sun.sunset")
},
{
value: "dusk",
label: node._("node-red-contrib-chronos/chronos-config:common.list.sun.dusk")
},
{
value: "nauticalDusk",
label: node._("node-red-contrib-chronos/chronos-config:common.list.sun.nauticalDusk")
},
{
value: "night",
label: node._("node-red-contrib-chronos/chronos-config:common.list.sun.night")
},
{
value: "nadir",
label: node._("node-red-contrib-chronos/chronos-config:common.list.sun.nadir")
}
];
const moonTimes =
[
{
value: "rise",
label: node._("node-red-contrib-chronos/chronos-config:common.list.moon.rise")
},
{
value: "set",
label: node._("node-red-contrib-chronos/chronos-config:common.list.moon.set")
}
];
const timeInput =
{
value: "time",
label: node._("node-red-contrib-chronos/chronos-config:common.label.time"),
icon: "fa fa-clock-o",
hasValue: true,
validate: PATTERN_DATETIME
};
const sunTimeInput =
{
value: "sun",
label: node._("node-red-contrib-chronos/chronos-config:common.label.sun"),
icon: "fa fa-sun-o",
options: sunTimes
};
const moonTimeInput =
{
value: "moon",
label: node._("node-red-contrib-chronos/chronos-config:common.label.moon"),
icon: "fa fa-moon-o",
options: moonTimes
};
const customInput =
{
value: "custom",
label: node._("node-red-contrib-chronos/chronos-config:common.label.custom"),
icon: "fa fa-user-o",
hasValue: true,
validate: PATTERN_CUSTOM_TIME
};
const validateNumericInput = function(event, ui)
{
const value = parseInt($(this).spinner("value"), 10);
const min = $(this).spinner("option", "min");
const max = $(this).spinner("option", "max");
if (isNaN(value) ||
(value < min))
{
$(this).spinner("value", min);
}
else if (value > max)
{
$(this).spinner("value", max);
}
};
const offsetInput =
{
min: -300,
max: 300,
step: 1,
change: validateNumericInput
};
const randomInput =
{
min: 0,
max: 300,
step: 1,
change: validateNumericInput
};
const fixedDayInput =
{
min: 1,
max: 31,
step: 1,
change: validateNumericInput
};
const fragment = document.createDocumentFragment();
const operatorBox = $("<div/>", {style: "display: inline-block; vertical-align: middle; width: auto;"}).appendTo(fragment);
const timeBox = $("<div/>", {style: "display: inline-block; vertical-align: middle; width: auto; margin-left: 2px;"}).appendTo(fragment);
const daysBox = $("<div/>", {style: "display: inline-block; vertical-align: middle; width: auto; margin-left: 2px;"}).appendTo(fragment);
const weekdaysBox = $("<div/>", {style: "display: inline-block; vertical-align: middle; width: auto; margin-left: 2px;"}).appendTo(fragment);
const monthsBox = $("<div/>", {style: "display: inline-block; vertical-align: middle; width: auto; margin-left: 2px;"}).appendTo(fragment);
const expressionBox = $("<div/>", {style: "display: inline-block; vertical-align: middle; width: auto; margin-left: 6px;"}).appendTo(fragment);
const contextBox = $("<div/>", {style: "display: inline-block; vertical-align: middle; width: auto; margin-left: 6px;"}).appendTo(fragment);
$("<div/>", {class: "node-input-index", style: "position: absolute; width: auto; top: 50%; right: 26px; margin-top: -9px;"})
.html("→ " + (index + 1))
.appendTo(fragment);
const operator = $("<select/>", {class: "node-input-operator", style: "width: auto;"})
.appendTo(operatorBox);
const operGrp = $("<optgroup></optgroup>").attr("label", node._("node-red-contrib-chronos/chronos-config:common.list.condition.operators"))
.append($("<option></option>").val("equal").text(node._("node-red-contrib-chronos/chronos-config:common.list.condition.operator.equal")))
.append($("<option></option>").val("notEqual").text(node._("node-red-contrib-chronos/chronos-config:common.list.condition.operator.notEqual")))
.append($("<option></option>").val("before").text(node._("node-red-contrib-chronos/chronos-config:common.list.condition.operator.before")))
.append($("<option></option>").val("until").text(node._("node-red-contrib-chronos/chronos-config:common.list.condition.operator.until")))
.append($("<option></option>").val("since").text(node._("node-red-contrib-chronos/chronos-config:common.list.condition.operator.since")))
.append($("<option></option>").val("after").text(node._("node-red-contrib-chronos/chronos-config:common.list.condition.operator.after")))
.append($("<option></option>").val("between").text(node._("node-red-contrib-chronos/chronos-config:common.list.condition.operator.between")))
.append($("<option></option>").val("outside").text(node._("node-red-contrib-chronos/chronos-config:common.list.condition.operator.outside")))
.append($("<option></option>").val("days").text(node._("node-red-contrib-chronos/chronos-config:common.list.condition.operator.days")))
.append($("<option></option>").val("weekdays").text(node._("node-red-contrib-chronos/chronos-config:common.list.condition.operator.weekdays")))
.append($("<option></option>").val("months").text(node._("node-red-contrib-chronos/chronos-config:common.list.condition.operator.months")))
.append($("<option></option>").val("otherwise").text(node._("node-red-contrib-chronos/chronos-config:common.list.condition.operator.otherwise")))
.appendTo(operator);
const fromGrp = $("<optgroup></optgroup>").attr("label", node._("node-red-contrib-chronos/chronos-config:common.list.condition.sources"))
.append($("<option></option>").val("expression").text(node._("node-red-contrib-chronos/chronos-config:common.list.condition.source.expression")))
.append($("<option></option>").val("context").text(node._("node-red-contrib-chronos/chronos-config:common.list.condition.source.context")))
.appendTo(operator);
const time1Box = $("<div/>", {style: "margin-left: 4px;"}).appendTo(timeBox);
const time2Box = $("<div/>", {style: "margin-left: 4px; margin-top: 5px;"}).appendTo(timeBox);
const time1Row1 = $("<div/>").appendTo(time1Box);
const time1Row2 = $("<div/>", {style: "margin-top: 5px;"}).appendTo(time1Box);
const time2Row1 = $("<div/>").appendTo(time2Box);
const time2Row2 = $("<div/>", {style: "margin-top: 5px;"}).appendTo(time2Box);
const monthsRow1 = $("<div/>").appendTo(monthsBox);
const monthsRow2 = $("<div/>", {style: "margin-top: 5px;"}).appendTo(monthsBox);
const monthsRow3 = $("<div/>", {style: "margin-top: 5px;"}).appendTo(monthsBox);
const time1 = $("<input/>", {type: "text", class: "node-input-time1"})
.appendTo(time1Row1)
.typedInput({types: [timeInput, sunTimeInput, moonTimeInput, customInput, "env", "global", "flow", "msg"]});
time1.typedInput("width", "315px");
time1._prevType = "time";
const extend1 = $("<a/>", {href: "#", title: node._("node-red-contrib-chronos/chronos-config:common.tooltip.expand"), style: "margin-left: 6px;"})
.html("<i class='fa fa-angle-right' aria-hidden='true'></i>")
.appendTo(time1Row1);
const time2 = $("<input/>", {type: "text", class: "node-input-time2"})
.appendTo(time2Row1)
.typedInput({types: [timeInput, sunTimeInput, moonTimeInput, customInput, "env", "global", "flow", "msg"]});
time2.typedInput("width", "315px");
time2._prevType = "time";
const extend2 = $("<a/>", {href: "#", title: node._("node-red-contrib-chronos/chronos-config:common.tooltip.expand"), style: "margin-left: 6px;"})
.html("<i class='fa fa-angle-right' aria-hidden='true'></i>")
.appendTo(time2Row1);
const offset1Label = $("<label/>", {title: node._("node-red-contrib-chronos/chronos-config:common.tooltip.offset"), style: "display: inline-block; width: auto; margin-bottom: 0px;"})
.html("<span style='margin-right: 6px;'>⇔</span>")
.appendTo(time1Row2);
const offset1 = $("<input/>", {class: "node-input-offset1", style: "width: 35px !important;"})
.appendTo(offset1Label)
.spinner(offsetInput);
const random1Label = $("<label/>", {title: node._("node-red-contrib-chronos/chronos-config:common.tooltip.random"), style: "width: auto; margin-left: 6px; margin-bottom: 0px;"})
.html("<i class='fa fa-random' aria-hidden='true'></i>")
.appendTo(time1Row2);
const random1Box = $("<div/>", {style: "display: inline-block; width: auto; margin-left: 6px;"})
.appendTo(random1Label);
const random1 = $("<input/>", {class: "node-input-random1", style: "width: 35px !important;"})
.appendTo(random1Box)
.spinner(randomInput);
$("<label/>", {title: node._("node-red-contrib-chronos/chronos-config:common.tooltip.precision"), style: "width: auto; margin-left: 6px; margin-bottom: 0px;"})
.html("<i class='fa fa-bullseye' aria-hidden='true'></i>")
.appendTo(time1Row2);
const precision1 = $("<select/>", {class: "node-input-precision1", title: node._("node-red-contrib-chronos/chronos-config:common.tooltip.precision"), style: "width: 115px; margin-left: 4px;"})
.append($("<option></option>").val("millisecond").text(node._("node-red-contrib-chronos/chronos-config:common.list.unit.millisecond")))
.append($("<option></option>").val("second").text(node._("node-red-contrib-chronos/chronos-config:common.list.unit.second")))
.append($("<option></option>").val("minute").text(node._("node-red-contrib-chronos/chronos-config:common.list.unit.minute")))
.append($("<option></option>").val("hour").text(node._("node-red-contrib-chronos/chronos-config:common.list.unit.hour")))
.append($("<option></option>").val("day").text(node._("node-red-contrib-chronos/chronos-config:common.list.unit.day")))
.append($("<option></option>").val("month").text(node._("node-red-contrib-chronos/chronos-config:common.list.unit.month")))
.append($("<option></option>").val("year").text(node._("node-red-contrib-chronos/chronos-config:common.list.unit.year")))
.appendTo(time1Row2);
const offset2Label = $("<label/>", {title: node._("node-red-contrib-chronos/chronos-config:common.tooltip.offset"), style: "display: inline-block; width: auto; margin-bottom: 0px;"})
.html("<span style='margin-right: 6px;'>⇔</span>")
.appendTo(time2Row2);
const offset2 = $("<input/>", {class: "node-input-offset2", style: "width: 35px !important;"})
.appendTo(offset2Label)
.spinner(offsetInput);
const random2Label = $("<label/>", {title: node._("node-red-contrib-chronos/chronos-config:common.tooltip.random"), style: "width: auto; margin-left: 6px; margin-bottom: 0px;"})
.html("<i class='fa fa-random' aria-hidden='true'></i>")
.appendTo(time2Row2);
const random2Box = $("<div/>", {style: "display: inline-block; width: auto; margin-left: 6px;"})
.appendTo(random2Label);
const random2 = $("<input/>", {class: "node-input-random2", style: "width: 35px !important;"})
.appendTo(random2Box)
.spinner(randomInput);
$("<label/>", {title: node._("node-red-contrib-chronos/chronos-config:common.tooltip.precision"), style: "width: auto; margin-left: 6px; margin-bottom: 0px;"})
.html("<i class='fa fa-bullseye' aria-hidden='true'></i>")
.appendTo(time2Row2);
const precision2 = $("<select/>", {class: "node-input-precision2", title: node._("node-red-contrib-chronos/chronos-config:common.tooltip.precision"), style: "width: 115px; margin-left: 4px;"})
.append($("<option></option>").val("millisecond").text(node._("node-red-contrib-chronos/chronos-config:common.list.unit.millisecond")))
.append($("<option></option>").val("second").text(node._("node-red-contrib-chronos/chronos-config:common.list.unit.second")))
.append($("<option></option>").val("minute").text(node._("node-red-contrib-chronos/chronos-config:common.list.unit.minute")))
.append($("<option></option>").val("hour").text(node._("node-red-contrib-chronos/chronos-config:common.list.unit.hour")))
.append($("<option></option>").val("day").text(node._("node-red-contrib-chronos/chronos-config:common.list.unit.day")))
.append($("<option></option>").val("month").text(node._("node-red-contrib-chronos/chronos-config:common.list.unit.month")))
.append($("<option></option>").val("year").text(node._("node-red-contrib-chronos/chronos-config:common.list.unit.year")))
.appendTo(time2Row2);
const dayType = $("<select/>", {class: "node-input-dayType", style: "width: auto; margin-left: 4px;"})
.append($("<option></option>").val("first").text(node._("node-red-contrib-chronos/chronos-config:common.list.dayType.first")))
.append($("<option></option>").val("second").text(node._("node-red-contrib-chronos/chronos-config:common.list.dayType.second")))
.append($("<option></option>").val("third").text(node._("node-red-contrib-chronos/chronos-config:common.list.dayType.third")))
.append($("<option></option>").val("fourth").text(node._("node-red-contrib-chronos/chronos-config:common.list.dayType.fourth")))
.append($("<option></option>").val("fifth").text(node._("node-red-contrib-chronos/chronos-config:common.list.dayType.fifth")))
.append($("<option></option>").val("last").text(node._("node-red-contrib-chronos/chronos-config:common.list.dayType.last")))
.append($("<option></option>").val("even").text(node._("node-red-contrib-chronos/chronos-config:common.list.dayType.even")))
.append($("<option></option>").val("specific").text(node._("node-red-contrib-chronos/chronos-config:common.list.dayType.specific")))
.appendTo(daysBox);
const nthDaysBox = $("<div/>", {style: "display: inline-block; vertical-align: middle; width: auto; margin-left: 6px;"}).appendTo(daysBox);
const specificDaysBox = $("<div/>", {style: "display: inline-block; vertical-align: middle; width: auto; margin-left: 6px;"}).appendTo(daysBox);
const day = $("<select/>", {class: "node-input-day", style: "width: auto;"})
.append($("<option></option>").val("monday").text(node._("node-red-contrib-chronos/chronos-config:common.list.weekdayLong.monday")))
.append($("<option></option>").val("tuesday").text(node._("node-red-contrib-chronos/chronos-config:common.list.weekdayLong.tuesday")))
.append($("<option></option>").val("wednesday").text(node._("node-red-contrib-chronos/chronos-config:common.list.weekdayLong.wednesday")))
.append($("<option></option>").val("thursday").text(node._("node-red-contrib-chronos/chronos-config:common.list.weekdayLong.thursday")))
.append($("<option></option>").val("friday").text(node._("node-red-contrib-chronos/chronos-config:common.list.weekdayLong.friday")))
.append($("<option></option>").val("saturday").text(node._("node-red-contrib-chronos/chronos-config:common.list.weekdayLong.saturday")))
.append($("<option></option>").val("sunday").text(node._("node-red-contrib-chronos/chronos-config:common.list.weekdayLong.sunday")))
.append($("<option></option>").val("day").text(node._("node-red-contrib-chronos/chronos-config:common.list.day.day")))
.append($("<option></option>").val("workday").text(node._("node-red-contrib-chronos/chronos-config:common.list.day.workday")))
.append($("<option></option>").val("weekend").text(node._("node-red-contrib-chronos/chronos-config:common.list.day.weekend")))
.appendTo(nthDaysBox);
const fixedDay = $("<input/>", {class: "node-input-fixedDay", style: "width: 30px !important;"})
.appendTo(specificDaysBox)
.spinner(fixedDayInput);
const fixedMonth = $("<select/>", {class: "node-input-fixedMonth", style: "width: auto; margin-left: 6px;"})
.append($("<option></option>").val("january").text(node._("node-red-contrib-chronos/chronos-config:common.list.month.january")))
.append($("<option></option>").val("february").text(node._("node-red-contrib-chronos/chronos-config:common.list.month.february")))
.append($("<option></option>").val("march").text(node._("node-red-contrib-chronos/chronos-config:common.list.month.march")))
.append($("<option></option>").val("april").text(node._("node-red-contrib-chronos/chronos-config:common.list.month.april")))
.append($("<option></option>").val("may").text(node._("node-red-contrib-chronos/chronos-config:common.list.month.may")))
.append($("<option></option>").val("june").text(node._("node-red-contrib-chronos/chronos-config:common.list.month.june")))
.append($("<option></option>").val("july").text(node._("node-red-contrib-chronos/chronos-config:common.list.month.july")))
.append($("<option></option>").val("august").text(node._("node-red-contrib-chronos/chronos-config:common.list.month.august")))
.append($("<option></option>").val("september").text(node._("node-red-contrib-chronos/chronos-config:common.list.month.september")))
.append($("<option></option>").val("october").text(node._("node-red-contrib-chronos/chronos-config:common.list.month.october")))
.append($("<option></option>").val("november").text(node._("node-red-contrib-chronos/chronos-config:common.list.month.november")))
.append($("<option></option>").val("december").text(node._("node-red-contrib-chronos/chronos-config:common.list.month.december")))
.append($("<option></option>").val("any").text(node._("node-red-contrib-chronos/chronos-config:common.list.month.any")))
.appendTo(specificDaysBox);
const excludeLabel = $("<label/>", {title: node._("node-red-contrib-chronos/chronos-config:common.label.exclude"), style: "width: auto; margin-left: 10px; margin-bottom: 0px;"})
.html("<i class='fa fa-ban' aria-hidden='true'></i>")
.appendTo(daysBox);
const exclude = $("<input/>", {type: "checkbox", class: "node-input-exclude", style: "width: auto; margin-left: 4px; margin-top: 0px; margin-bottom: 3px;"})
.appendTo(excludeLabel);
const weekdayInputs = new Array(6);
const monthInputs = new Array(12);
for (let i=0; i<7; ++i)
{
weekdayInputs[(i+1)%7] = $("<input/>", {type: "checkbox", class: "node-input-" + weekdays[(i+1)%7], style: "width: auto; margin-top: 0px; margin-bottom: 1px; margin-left: 6px;"})
.appendTo(weekdaysBox);
$("<label/>", {title: node._("node-red-contrib-chronos/chronos-config:common.list.weekdayLong." + weekdays[(i+1)%7]), style: "margin-left: 4px; margin-bottom: 0px; width: auto;"})
.text(node._("node-red-contrib-chronos/chronos-config:common.list.weekday." + weekdays[(i+1)%7])).appendTo(weekdaysBox);
}
for (let i=0; i<12; ++i)
{
const row = (i < 4) ? monthsRow1 : (i < 8) ? monthsRow2 : monthsRow3;
monthInputs[i] = $("<input/>", {type: "checkbox", class: "node-input-" + months[i], style: "width: auto; margin-top: 0px; margin-bottom: 1px; margin-left: 6px;"})
.appendTo(row);
$("<label/>", {title: node._("node-red-contrib-chronos/chronos-config:common.list.monthLong." + months[i]), style: "margin-left: 4px; margin-bottom: 0px; width: 40px;"})
.text(node._("node-red-contrib-chronos/chronos-config:common.list.month." + months[i])).appendTo(row);
}
const expression = $("<input/>", {type: "text", class: "node-input-expression"})
.appendTo(expressionBox)
.typedInput({types: ["jsonata"]});
expression.typedInput("width", "315px");
const context = $("<input/>", {type: "text", class: "node-input-context"})
.appendTo(contextBox)
.typedInput({types: ["env", "global", "flow"]});
context.typedInput("width", "315px");
operator.change(function()
{
timeBox.hide();
time2Box.hide();
daysBox.hide();
weekdaysBox.hide();
monthsBox.hide();
expressionBox.hide();
contextBox.hide();
var value = $(this).val();
switch (value)
{
case "equal":
case "notEqual":
case "before":
case "until":
case "since":
case "after":
{
timeBox.show();
break;
}
case "between":
case "outside":
{
timeBox.show();
time2Box.show();
break;
}
case "days":
{
daysBox.show();
break;
}
case "weekdays":
{
weekdaysBox.show();
break;
}
case "months":
{
monthsBox.show();
break;
}
case "expression":
{
expressionBox.show();
break;
}
case "context":
{
contextBox.show();
break;
}
}
});
time1.on("change", function()
{
let type = time1.typedInput("type");
if (type != time1._prevType)
{
time1._prevType = type;
if ((type != "sun") && (type != "moon"))
{
time1.typedInput("value", "");
}
}
});
time2.on("change", function()
{
let type = time2.typedInput("type");
if (type != time2._prevType)
{
time2._prevType = type;
if ((type != "sun") && (type != "moon"))
{
time2.typedInput("value", "");
}
}
});
function showExtensionRow1()
{
time1Row2.show();
extend1.html("<i class='fa fa-angle-down' aria-hidden='true'></i>");
}
function hideExtensionRow1()
{
time1Row2.hide();
extend1.html("<i class='fa fa-angle-right' aria-hidden='true'></i>");
offset1.spinner("value", 0);
random1.spinner("value", 0);
}
function showExtensionRow2()
{
time2Row2.show();
extend2.html("<i class='fa fa-angle-down' aria-hidden='true'></i>");
}
function hideExtensionRow2()
{
time2Row2.hide();
extend2.html("<i class='fa fa-angle-right' aria-hidden='true'></i>");
offset2.spinner("value", 0);
random2.spinner("value", 0);
}
extend1.click(function()
{
if (time1Row2.is(":hidden"))
{
showExtensionRow1();
}
else
{
hideExtensionRow1();
}
});
extend2.click(function()
{
if (time2Row2.is(":hidden"))
{
showExtensionRow2();
}
else
{
hideExtensionRow2();
}
});
hideExtensionRow1();
hideExtensionRow2();
dayType.change(function()
{
nthDaysBox.hide();
specificDaysBox.hide();
let value = $(this).val();
if (value == "specific")
{
specificDaysBox.show();
}
else if (value != "even")
{
let val = day.val();
day.children("option[value='day']").remove();
day.children("option[value='workday']").remove();
day.children("option[value='weekend']").remove();
if ((value == "first") || (value == "last"))
{
day.append($("<option></option>").val("day").text(node._("node-red-contrib-chronos/chronos-config:common.list.day.day")));
day.append($("<option></option>").val("workday").text(node._("node-red-contrib-chronos/chronos-config:common.list.day.workday")));
day.append($("<option></option>").val("weekend").text(node._("node-red-contrib-chronos/chronos-config:common.list.day.weekend")));
day.val(val);
}
nthDaysBox.show();
}
});
fixedMonth.change(function()
{
let value = $(this).val();
switch (value)
{
case "january":
case "march":
case "may":
case "july":
case "august":
case "october":
case "december":
case "any":
{
fixedDay.spinner("option", "max", 31);
break;
}
case "february":
{
fixedDay.spinner("option", "max", 29);
break;
}
case "april":
case "june":