node-red-contrib-sun-position
Version:
NodeRED nodes to get sun and moon position
1,020 lines (985 loc) • 145 kB
HTML
<!DOCTYPE HTML>
<!--
This code is licensed under the Apache License Version 2.0.
Copyright (c) 2022 Robert Gester
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
-->
<!--
This code is licensed under the Apache License Version 2.0.
Copyright (c) 2022 Robert Gester
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
-->
<script type="text/javascript">
(function() {
/**
* Retrieve editableList items (refactored for re-use in the form inject button)
*/
function getProps(el, node) {
const result = {
props: [],
payloadLbl : '',
payloadType : '',
topicLbl : ''
};
const getPropTLbl = prop => {
if (prop) {
if (prop.vt === 'dateSpecific') {
if (prop.fS === 99) {
return RED.nodes.getType('position-config').clipValueLength(prop.fI, 20);
}
return RED.nodes.getType('position-config').clipValueLength($.trim(prop.fT.split(',')[0]), 20);
}
return RED.nodes.getType('position-config').getRDGNodeValLbl(node, prop.vt, prop.v, prop.oT, prop.o, prop.oM);
}
return '{}';
};
el.each(function(_i) {
const pElem = $(this);
const p = {
p : pElem.find('.node-input-prop-property-name').typedInput('value'),
pt : pElem.find('.node-input-prop-property-name').typedInput('type'),
v : pElem.find('.node-input-prop-property-value').typedInput('value'),
vt : pElem.find('.node-input-prop-property-value').typedInput('type'),
o : pElem.find('.node-input-prop-property-offset').typedInput('value'),
oT : pElem.find('.node-input-prop-property-offset').typedInput('type'),
oM : pElem.find('.node-input-prop-property-multiplier').val(),
f : '',
fS : Number(pElem.find('.node-input-prop-property-FormatSel').val()),
fT : pElem.find('.node-input-prop-property-FormatSel option:selected').text(),
fI : pElem.find('.node-input-prop-property-FormatIp').val(),
next : pElem.find('.node-input-prop-property-Next').is(':checked'),
days : getCheckboxesStr(pElem.find('.node-input-prop-property-days input[type=checkbox]:checked'), 7),
months : getCheckboxesStr(pElem.find('.node-input-prop-property-months input[type=checkbox]:checked'), 12),
onlyOddDays : pElem.find('.node-input-prop-property-onlyOddDays').is(':checked'),
onlyEvenDays : pElem.find('.node-input-prop-property-onlyEvenDays').is(':checked'),
onlyOddWeeks : pElem.find('.node-input-prop-property-onlyOddWeeks').is(':checked'),
onlyEvenWeeks : pElem.find('.node-input-prop-property-onlyEvenWeeks').is(':checked')
};
p.f = (p.fS !== 99 ? p.fS : p.fI);
if (p.p === 'payload' && (!p.pt || p.pt === 'msg')) {
p.pt = 'msgPayload';
} else if (p.p === 'topic' && (!p.pt || p.pt === 'msg')) {
p.pt = 'msgTopic';
} else if (p.p === 'ts' && (!p.pt || p.pt === 'msg')) {
p.pt = 'msgTs';
} else if (p.p === 'lc' && (!p.pt || p.pt === 'msg')) {
p.pt = 'msgLC';
} else if (p.p === 'value' && (!p.pt || p.pt === 'msg')) {
p.pt = 'msgValue';
}
if (p.pt === 'msgPayload') {
result.payloadLbl = getPropTLbl(p);
result.payloadType = p.vt;
}
if (p.pt === 'msgTopic') {
result.topicLbl = getPropTLbl(p).replace(/(^")|("↷?↶?$)|({}↷?↶?)/g, '').trim();
}
result.props.push(p);
});
let pos = 0;
while (result.payloadLbl === '' && el.length > pos) {
const p = el[pos];
if (p.pt !== 'msgTopic') {
result.payloadLbl = RED.nodes.getType('position-config').getRDGNodeValLbl(this, p.pt, p.p) + '=' + getPropTLbl(p);
}
pos++;
}
if (result.payloadLbl === '') {
result.payloadLbl = '$!{lblInject}!';
}
return result;
}
/**
* Perform inject, optionally sending a custom msg (refactored for re-use in the form inject button)
*/
function doInject(node, customMsg) {
console.log('doInject', customMsg); // eslint-disable-line no-console
let payload = node.payload;
if ((node.payloadType === 'flow') ||
(node.payloadType === 'global')) {
const key = RED.utils.parseContextKey(payload);
payload = node.payloadType + '.' + key.key;
}
if (node.payloadType === 'str' || node.payloadType === 'string') { payload = '"' + node.payload + '"'; }
let label = node._def.label.call(node, customMsg?customMsg.__user_inject_props__:undefined);
if (label.length > 30) {
label = label.substring(0, 50) + '...';
}
label = label.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
$.ajax({
url: 'time-inject/' + node.id,
type: 'POST',
data: JSON.stringify(customMsg||{}),
contentType: 'application/json; charset=utf-8',
success(_resp) {
RED.notify(node._('node-red:inject.success',{ label }), { type:'success', id:'inject', timeout: 2000 });
},
error(jqXHR, textStatus, _errorThrown) {
if (jqXHR.status === 404) {
RED.notify(node._('node-red:common.notification.error',{message:node._('node-red:common.notification.errors.not-deployed')}),'error');
} else if (jqXHR.status === 500) {
RED.notify(node._('node-red:common.notification.error',{message:node._('node-red:inject.errors.failed')}),'error');
} else if (jqXHR.status === 0) {
RED.notify(node._('node-red:common.notification.error',{message:node._('node-red:common.notification.errors.no-response')}),'error');
} else {
RED.notify(node._('node-red:common.notification.error',{message:node._('node-red:common.notification.errors.unexpected',{status:jqXHR.status,message:textStatus})}),'error');
}
}
});
}
// category: 'time and astro',
RED.nodes.registerType('time-inject', {
category: 'common',
color: '#a6bbcf',
icon: 'injectSun-white.svg',
inputs: 0,
outputs: 1,
paletteLabel: 'inject enhanced',
align: 'left',
defaults: {
name: { value: '' },
nameInt: { value: '' },
positionConfig: {
value: '',
type: 'position-config',
required: true
},
props: {
value: [
{
p : '',
pt : 'msgPayload',
v : '',
vt : 'date',
o : '',
oT : 'none',
oM : 60000,
f : 0, // = (p.fS !== 99 ? p.fS : p.fI)
fS : 0,
fI : '', // input format
next : true,
days : '*',
months: '*'
},{
p : '',
pt : 'msgTopic',
v : '',
vt : 'str',
o : '',
oT : 'none',
oM : 60000,
f : 0, // = (p.fS !== 99 ? p.fS : p.fI)
fS : 0,
fI : '', // input format
next : false,
days : '*',
months: '*'
}
]
},
injectTypeSelect: {
value: 'none'
},
intervalCount: {
value: 1,
validate(v) {
return (
(typeof this.injectTypeSelect === 'undefined') ||
(this.injectTypeSelect === 'none') ||
(this.injectTypeSelect === 'time') ||
(this.injectTypeSelect === 'cron') ||
RED.validators.typedInput('intervalCountType')(v) ||
(v > 0)
);
}
},
intervalCountType: {
value: 'num'
},
intervalCountMultiplier: {
value: 60000,
required: true,
validate(v) {
return (
(typeof this.injectTypeSelect === 'undefined') ||
(this.injectTypeSelect === 'none') ||
(this.injectTypeSelect === 'time') ||
(this.injectTypeSelect === 'interval-amount') ||
(this.injectTypeSelect === 'cron') ||
RED.validators.number()(v)
);
}
},
intervalStart: {
value: '',
required: false,
validate(v) {
return (
(v === '') ||
(typeof v === 'undefined') ||
(this.injectTypeSelect !== 'interval') ||
RED.validators.regex(/^\d{4}-\d{1,2}-\d{1,2}T\d{1,2}:\d{1,2}(:\d{1,2})?(.\d{1,3})?Z?$|^\d{2}-\d{1,2}-\d{1,2}T\d{1,2}:\d{1,2}(:\d{1,2})?(.\d{1,3})?Z?$/)(v)
);
}
},
cron: {
value: '',
required: false,
validate(v) {
return (this.injectTypeSelect !== 'cron') ||
RED.validators.typedInput('cronType')(v);
}
},
cronType: {
value: 'cronexpr'
},
time: {
value: '',
required: false,
validate(v) {
return (
((this.injectTypeSelect !== 'interval-time') &&
(this.injectTypeSelect !== 'interval-amount') &&
(this.injectTypeSelect !== 'time')) ||
RED.validators.typedInput('timeType')(v)
);
}
},
timeType: {
value: 'entered'
},
offset: {
value: 0,
validate(v) {
return (
(typeof v === 'undefined') ||
(typeof this.offsetType === 'undefined') ||
(this.injectTypeSelect === 'none') ||
(this.injectTypeSelect === 'interval') ||
(this.injectTypeSelect === 'cron') ||
RED.validators.typedInput('offsetType')(v)
);
}
},
offsetType: {
value: 'none'
},
offsetMultiplier: {
value: 60000,
required: true,
validate(v) {
return (
typeof v === 'undefined' ||
RED.validators.number()(v) ||
(this.injectTypeSelect === 'none') ||
(this.injectTypeSelect === 'interval') ||
(this.injectTypeSelect === 'cron') ||
$('#node-input-offsetType').typedInput('type') === 'none' ||
this.offsetType === 'none'
);
}
},
timeEnd: {
value: '',
required: false,
validate(v) {
return (
((this.injectTypeSelect !== 'interval-time') &&
(this.injectTypeSelect !== 'interval-amount')) ||
RED.validators.typedInput('timeEndType')(v)
);
}
},
timeEndType: {
value: 'entered'
},
timeEndOffset: {
value: 0,
validate(v) {
return (
(typeof v === 'undefined') ||
(typeof this.timeEndOffsetType === 'undefined') ||
((this.injectTypeSelect !== 'interval-time') &&
(this.injectTypeSelect !== 'interval-amount')) ||
RED.validators.typedInput('timeEndOffsetType')(v)
);
}
},
timeEndOffsetType: {
value: 'none'
},
timeEndOffsetMultiplier: {
value: 60000,
required: true,
validate(v) {
return (
typeof v === 'undefined' ||
RED.validators.number()(v) ||
((this.injectTypeSelect !== 'interval-time') &&
(this.injectTypeSelect !== 'interval-amount')) ||
$('#node-input-timeEndOffsetType').typedInput('type') === 'none' ||
this.timeEndOffsetType === 'none'
);
}
},
timeDays: {
value: '*',
required: true
},
timeOnlyOddDays: {
value: false,
required: true
},
timeOnlyEvenDays: {
value: false,
required: true
},
timeOnlyOddWeeks: {
value: false,
required: true
},
timeOnlyEvenWeeks: {
value: false,
required: true
},
timeMonths: {
value: '*',
required: true
},
timedatestart: {
value: '',
required: false,
validate(v) {
return (
typeof v === 'undefined' ||
(this.injectTypeSelect === 'none') ||
(this.injectTypeSelect === 'interval') ||
(this.injectTypeSelect === 'cron') ||
RED.validators.regex(/^$|^\d{4}-\d{1,2}-\d{1,2}$|^\d{2}-\d{1,2}-\d{1,2}$/)(v)
);
}
},
timedateend: {
value: '',
required: false,
validate(v) {
return (
typeof v === 'undefined' ||
(this.injectTypeSelect === 'none') ||
(this.injectTypeSelect === 'interval') ||
(this.injectTypeSelect === 'cron') ||
RED.validators.regex(/^$|^\d{4}-\d{1,2}-\d{1,2}$|^\d{2}-\d{1,2}-\d{1,2}$/)(v)
);
}
},
property: {
value: '',
required: false,
validate(v){
return (
typeof v === 'undefined' ||
typeof this.propertyType === 'undefined' ||
(this.injectTypeSelect !== 'time') ||
RED.validators.typedInput('propertyType')(v)
);
}
},
propertyType: {
value: 'none'
},
propertyCompare: {
value: 'true'
},
propertyThreshold: {
value: '',
validate(v){
return (
typeof v === 'undefined' ||
RED.validators.typedInput('propertyThresholdType')(v) ||
(this.injectTypeSelect !== 'time') ||
typeof this.propertyType === 'undefined' ||
($('#node-input-propertyType').length ?
$('#node-input-property').typedInput('type') === 'none' :
this.propertyType === 'none')
);
}
},
propertyThresholdType: {
value: 'num'
},
timeAlt: {
value: '',
required: false,
validate(v){
return (
RED.validators.typedInput('timeAltType')(v) ||
(this.injectTypeSelect !== 'time') ||
typeof this.propertyType === 'undefined' ||
($('#node-input-propertyType').length ?
$('#node-input-property').typedInput('type') === 'none' :
this.propertyType === 'none')
);
}
},
timeAltType: {
value: 'entered'
},
timeAltDays: {
value: '*',
required: true
},
timeAltOnlyOddDays: {
value: false,
required: true
},
timeAltOnlyEvenDays: {
value: false,
required: true
},
timeAltOnlyOddWeeks: {
value: false,
required: true
},
timeAltOnlyEvenWeeks: {
value: false,
required: true
},
timeAltMonths: {
value: '*',
required: true
},
timeAltOffset: {
value: 0,
validate(v){
return (
typeof v === 'undefined' ||
(typeof this.timeAltOffsetType === 'undefined') ||
(this.injectTypeSelect !== 'time') ||
RED.validators.typedInput('timeAltOffsetType')(v) ||
typeof this.propertyType === 'undefined' ||
($('#node-input-propertyType').length ?
$('#node-input-property').typedInput('type') === 'none' :
this.propertyType === 'none')
);
}
},
timeAltOffsetType: {
value: 'none'
},
timeAltOffsetMultiplier: {
value: 60000,
required: true,
validate(v){
return (
typeof v === 'undefined' ||
RED.validators.number()(v) ||
$('#node-input-timeAltOffsetType').typedInput('type') === 'none' ||
this.timeAltOffsetType === 'none' ||
(this.injectTypeSelect !== 'time') ||
typeof this.propertyType === 'undefined' ||
($('#node-input-propertyType').length ?
$('#node-input-property').typedInput('type') === 'none' :
this.propertyType === 'none')
);
}
},
once: {
value: false
},
onceDelay: {
value: 0.1,
validate(v){
return (
RED.validators.number()(v) ||
v === '' ||
v === null ||
typeof v === 'undefined'
);
}
},
recalcTime: {
value: 2,
validate(v){
return (
RED.validators.number()(v) ||
v === '' ||
v === null ||
typeof v === 'undefined'
);
}
},
payload: { value: '' }, // p
payloadType: { value: 'date' }, // vt
payloadTimeFormat: { value: 0 }, // f
payloadOffset: { value: 0 }, // o
payloadOffsetType: { value: 'none' }, // oT
payloadOffsetMultiplier: { value: 60000 }, // oM
topic: { value: '' }, // p
addPayload1: { value: '' }, // p
addPayload2: { value: '' }, // p
addPayload3: { value: '' }, // p
addPayload1Type: { value: 'none' },
addPayload2Type: { value: 'none' },
addPayload3Type: { value: 'none' },
addPayload1Value: { value: '' }, // v
addPayload2Value: { value: '' }, // v
addPayload3Value: { value: '' }, // v
addPayload1ValueType: { value: '' },
addPayload2ValueType: { value: '' },
addPayload3ValueType: { value: '' },
addPayload1Format: { value: 0 }, // f
addPayload2Format: { value: 0 }, // f
addPayload3Format: { value: 0 }, // f
addPayload1Offset: { value: 0 }, // o
addPayload2Offset: { value: 0 }, // o
addPayload3Offset: { value: 0 }, // o
addPayload1OffsetType: { value: 'none' },
addPayload2OffsetType: { value: 'none' },
addPayload3OffsetType: { value: 'none' },
addPayload1OffsetMultiplier: { value: 60000 },
addPayload2OffsetMultiplier: { value: 60000 },
addPayload3OffsetMultiplier: { value: 60000 },
addPayload1Next: { value: true },
addPayload2Next: { value: true },
addPayload3Next: { value: true },
addPayload1Days: { value: '*' },
addPayload2Days: { value: '*' },
addPayload3Days: { value: '*' }
},
outputLabels(_index) {
const types = {
msgPayload : 'msg.payload',
msgTopic : 'msg.topic',
msgTs : 'msg.ts',
msgLC : 'msg.lc',
msgValue : 'msg.value'
};
const labels = {
str: 'string',
num: 'number',
bool: 'boolean',
flow: 'flow context',
global: 'global context',
bin: 'binary data',
date: 'timestamp',
dateSpecific: 'special timestamp',
entered: 'timestamp',
dateEntered: 'timestamp',
env: 'Environment variable',
jsonata: 'result of jsonata expression',
dayOfMonth: 'timestamp',
pdsTime: 'sun time',
pdsTimeNow: 'sun time object',
pdsTimeCustom: 'sun time (custom)',
pdmTime: 'moon time',
pdsCalcPercent: 'percent of sun in the sky',
pdsCalcAzimuth: 'Azimuth of sun',
pdsCalcElevation: 'Elevation of sun',
pdsCalcData: 'object of sun position',
pdmCalcData: 'object of moon position',
pdmPhase: 'object of moon phase',
pdmPhaseCheck: 'boolean indicates if moon in specific phase',
pdbIsDST: 'is DST',
pdnWeekOfYear: 'week number',
pdbWeekOfYearEven: 'is week even',
pdnDayOfYear: 'day number',
pdbDayOfYearEven: 'is day even'
};
// if only payload and topic - display payload type
// if only one property - show it's type
// if more than one property (other than payload and topic) - show "x properties" where x is the number of properties.
// this.props will not be an array for legacy inject nodes until they are re-deployed
//
let props = this.props;
if (!Array.isArray(props)) {
props = [{
p : '',
pt : types.MsgPayload.value,
v : this.payload ? this.payload : '',
vt : this.payloadType ? ((this.payloadType === 'string') ? 'str' : this.payloadType) : (this.payload ? 'str' : 'date'),
o : this.payloadOffset ? this.payloadOffset : 1,
oT : (this.payloadOffset === 0 || this.payloadOffset === '') ? types.Undefined.value : (this.payloadOffsetType ? this.payloadOffsetType : 'num'),
oM : this.payloadOffsetMultiplier ? this.payloadOffsetMultiplier : 60000,
f : this.payloadTimeFormat ? this.payloadTimeFormat : 0,
next : true,
days : '*',
months: '*'
},{
p : '',
pt : types.MsgTopic.value,
v : this.topic ? this.topic : '',
vt : 'str',
o : 1,
oT : types.Undefined.value,
oM : 60000,
f : 0,
next : false,
days : '*',
months: '*'
}];
}
let lab = '';
if (props) {
let cnt = -1;
for (let i=0, l=props.length; i<l; i++) {
if (!props[i].pt.includes('msg')) { continue; }
cnt++;
if (cnt > 0) lab += '\n';
if (cnt === 5) {
lab += ' + ' + (props.length - 4);
break;
}
lab += (types[props[i].pt] ? types[props[i].pt] : (props[i].pt + '.' + props[i].p)) + ': ';
let propType = labels[props[i].vt] ? labels[props[i].vt] : props[i].vt;
if (propType === 'json' || propType === 'object') {
try {
const parsedProp = JSON.parse(props[i].v);
propType = typeof parsedProp;
if (propType === 'object' && Array.isArray(parsedProp)) {
propType = 'Array';
}
} catch(e) {
propType = 'invalid';
}
}
lab += propType;
// lab += this._('inject.label.'+propType);
}
}
return lab || '?';
},
label(customProps) {
const injTxt = this._('time-inject.label.time-inject');
let result = '';
if (!this.positionConfig || !this.nameInt || !this.injectTypeSelect) {
result += '⚠ ';
}
if (customProps && customProps.payloadLbl) {
if (customProps.topicLbl && customProps.topicLbl.length + customProps.payloadLbl.length <= 45) {
return result + customProps.topicLbl + ':' + customProps.payloadLbl.replace('$!{lblInject}!', injTxt);
}
return result + customProps.payloadLbl.replace('$!{lblInject}!', injTxt);
}
if (this.name) {
return result + this.name;
}
if (this.nameInt) {
if (this.topicLbl && this.topicLbl.length + this.nameInt.length <= 45) {
return result + this.topicLbl + ':' + this.nameInt.replace('$!{lblInject}!', injTxt);
}
return result + this.nameInt.replace('$!{lblInject}!', injTxt);
}
if (this.topicLbl) {
return result + this.topicLbl + ':' + injTxt;
}
return result + injTxt;
},
labelStyle() {
return this.name ? 'node_label_italic' : '';
},
oneditprepare() {
setTimeout(() => {
$('.is-to-show-initial').show();
$('.is-to-hide-initial').hide();
}, 300);
const node = this;
const $nodeConfig = $('#node-input-positionConfig');
const setup = function(node) {
setTimeout(() => {
$('.is-to-show-normally').show();
}, 300);
/* global initializeValue getTypes getSelectFields appendOptions setupTInput autocomplete addLabel initCombobox initCheckboxesBlock getBackendData bdDateToTime getCheckboxesStr setTInputValue */
const types = getTypes(node, () => $nodeConfig.val());
const selFields = getSelectFields();
// #region initialize
/**
* update multiplier settings from a previous version
* @param {number} mp - the multiplier value
* @param {string} name - the name of the element
* @param {function} onchange - the function to be called on field change
* @returns {number} the updated multiplier value
*/
function multiplierUpdate(mp, name, onchange) {
const $field = $('#node-input-' + name);
appendOptions(node, $field, 'multiplier', data => (data.id > 0));
if (mp === null || typeof mp === 'undefined' || isNaN(mp) || mp === '' || mp === 0) {
mp = 60000;
} else {
mp = parseFloat(mp);
if (mp === 1) {
mp = 1000;
} else if (mp === 60) {
mp = 60000;
} else if (mp === 3600) {
mp = 3600000;
}
}
$field.val(mp);
$field.on('change', onchange);
return mp;
}
/**
* Parse an ISO date string (i.e. "2019-01-18T00:00:00.000Z",
* "2019-01-17T17:00:00.000-07:00", or "2019-01-18T07:00:00.000+07:00",
* which are the same time) and return a JavaScript Date object with the
* value represented by the string.
* @param {string} dateString - ISO Date String
* @returns {Date} Date object
*/
function parseDate(dateString) {
const b = dateString.split(/\D+/);
const offsetMult = dateString.indexOf('+') !== -1 ? -1 : 1;
const hrOffset = offsetMult * (+b[7] || 0);
const minOffset = offsetMult * (+b[8] || 0);
return new Date(Date.UTC(+b[0], +b[1] - 1, +b[2], +b[3] + hrOffset, +b[4] + minOffset, +b[5], +b[6] || 0));
}
initializeValue(node, 'timeDays', '*');
initializeValue(node, 'timeAltDays', '*');
initializeValue(node, 'timeMonths', '*');
initializeValue(node, 'timeAltMonths', '*');
// #endregion initialize
// #region property Container
/**
* resizes a rule container
*/
function resizeContainer() {
const editorRow = $('.node-input-property-container-row ol');
const height = editorRow.outerHeight(true);
const rowCount = $('#node-input-property-container').editableList('length');
if (rowCount > 0) {
$('#node-input-property-container').editableList('height', height + 40);
} else {
$('#node-input-property-container').editableList('height', 20);
}
}
const $propList = $('#node-input-property-container').css('min-height', '40px').css('min-width', '420px');
$propList.editableList({
buttons: [
{
id: 'node-time-inject-test-inject-button',
label: node._('node-red:inject.injectNow'),
click(_e) {
const props = getProps($propList.editableList('items'), node);
const m = {__user_inject_props__: props};
doInject(node, m);
}
}
],
addItem($containerRow, containerIndex, data) {
$containerRow.css({overflow: 'hidden', whiteSpace: 'nowrap'});
const $row1 = $('<div/>').appendTo($containerRow);
const $row2 = $('<div/>', {style: 'padding-top: 5px;'}).appendTo($containerRow);
const $row3 = $('<div/>', {style: 'padding-top: 5px;'}).appendTo($containerRow);
const $row4 = $('<div/>', {style: 'padding-top: 5px;'}).appendTo($containerRow);
// row1 - Property / Type Selection
let prop = data;
if (!Object.prototype.hasOwnProperty.call(prop, 'p')) {
prop = {
p : '',
pt : types.MsgPayload.value,
v : '',
vt : 'str',
o : '',
oT : types.Undefined.value,
oM : 60000,
f : 0,
next : true,
days : '*',
months: '*'
};
}
if (prop.p === 'payload' && (!prop.pt || prop.pt === 'msg')) {
prop.pt = types.MsgPayload.value;
} else if (prop.p === 'topic' && (!prop.pt || prop.pt === 'msg')) {
prop.pt = types.MsgTopic.value;
} else if (prop.p === 'ts' && (!prop.pt || prop.pt === 'msg')) {
prop.pt = types.MsgTs.value;
} else if (prop.p === 'lc' && (!prop.pt || prop.pt === 'msg')) {
prop.pt = types.MsgLc.value;
} else if (prop.p === 'value' && (!prop.pt || prop.pt === 'msg')) {
prop.pt = types.MsgValue.value;
}
const $propertyName = $('<input/>', {
class: 'node-input-prop-property-name',
type: 'text'
})
.css('width', '40%')
.appendTo($row1)
.typedInput({
default: types.MsgPayload.value,
types: [
types.MsgPayload,
types.MsgTopic,
types.MsgTs,
types.MsgLc,
types.MsgValue,
'msg',
'flow',
'global'
]
});
setTInputValue($propertyName, prop.p, prop.pt);
$('<div/>', { style: 'display:inline-block; padding:0px 6px;' })
.text('=')
.appendTo($row1);
const $propertyValue = $('<input/>', {
class:'node-input-prop-property-value',
type: 'text'
})
.css('width', 'calc(60% - 30px)')
.appendTo($row1)
.typedInput({
default: 'str',
types: [
'str',
'num',
'bool',
'date',
types.DateSpecific,
types.TimeEntered,
types.DateEntered,
types.TimeSun,
types.TimeSunCustom,
types.TimeSunNow,
types.TimeMoon,
types.DayOfMonth,
types.strPlaceholder,
'json',
'bin',
'env',
'flow',
'global',
'jsonata',
types.SunCalc,
types.SunInSky,
types.MoonCalc,
types.MoonPhase,
types.SunAzimuth,
types.SunElevation,
types.SunTimeByAzimuth,
types.SunTimeByElevationObj,
types.SunTimeByElevationNext,
types.SunTimeByElevationRise,
types.SunTimeByElevationSet,
types.isDST,
types.WeekOfYear,
types.WeekOfYearEven,
types.DayOfYear,
types.DayOfYearEven,
types.numPercent,
types.randomNumber,
types.randmNumCachedDay,
types.randmNumCachedWeek,
types.nodeId,
types.nodeName,
types.nodePath
]
});
setTInputValue($propertyValue, prop.v, prop.vt);
// row2 - Format Selection
const operandSelFieldName = 'node-input-prop-property-FormatSel';
addLabel($row2, operandSelFieldName, 'fa fa-file-code-o');
const $formatSel = $('<select/>', {
class: operandSelFieldName
}).appendTo($row2);
const $formatIp = $('<input/>', {
class: 'node-input-prop-property-FormatIp',
type: 'text'
})
.css('width', 'calc(100% - 30px)')
.appendTo($row2);
initCombobox(node, $formatSel, $formatIp, 'dateOutFormat', 'outputFormats', prop.f ? prop.f : 0, -70);
// row3 - Offset and Multiplier
addLabel($row3, 'node-input-prop-property-offset', 'fa fa-plus');
const $offset = $('<input/>', {
class: 'node-input-prop-property-offset',
type: 'text',
value: (prop.o ? prop.o : '')
})
.css('width', '70%')
.appendTo($row3)
.typedInput({
default: (prop.oT ? prop.oT : types.Undefined.value),
types: [types.Undefined, 'num', 'flow', 'global', 'env', types.randomNumber, types.randmNumCachedDay, types.randmNumCachedWeek]
});
setTInputValue($offset, prop.o, prop.oT);
const $multiplier = $('<select/>', {
class: 'node-input-prop-property-multiplier'
})
.css('width', 'calc(30% - 30px)')
.appendTo($row3);
appendOptions(node, $multiplier, 'multiplier');
$multiplier.val(prop.oM ? prop.oM: 60000);
// row4 - Next, Day, Month selection
// row4 - 1 - Next selection
const $divNext = $('<div/>').appendTo($row4);
addLabel($divNext, 'node-input-prop-property-Next', 'fa fa-clock-o', node._('node-red-contrib-sun-position/position-config:common.label.nextOccurrence'));
const $cbNext = $('<input/>', {
class: 'node-input-prop-property-Next',
type: 'checkbox',
style: 'display:inline-block; width:15px; vertical-align:baseline;'
})
.css('width', 'calc(30% - 30px)')
.appendTo($divNext);
if (typeof prop.next === 'undefined' || prop.next === null || prop.next == true) { // eslint-disable-line eqeqeq
$cbNext.prop('checked', true);
} else {
$cbNext.prop('checked', false);
}
// row4 - 2 - Day selection
const $divMnDays = $('<div/>', {
style: 'margin-top:5px;border-top: 1px solid #000;margin-bottom: 0;'
}
).appendTo($row4);
addLabel($divMnDays, 'node-input-prop-property-days', 'fa fa-clock-o', node._('node-red-contrib-sun-position/position-config:common.label.validForDays'));
const $divDays = $('<div/>', {
class: 'node-input-prop-property-days',
style: 'style="display:inline-block;'
}).appendTo($divMnDays);
const $divDays2 = $('<div/>').appendTo($divDays);
for (let d = 0; d < 7; d++) {
const $lbl = $('<label/>', {
title: node._('node-red-contrib-sun-position/position-config:common.days.' + d)
})
.css('width', 'calc(14% - 7px)') // (100% / 7 = 14%) - (30 px / 7 = 7px)
.appendTo($divDays2);
const $cb = $('<input/>', {
type: 'checkbox',
class: 'node-input-prop-property-day-' + d,
value: d,
style: 'width: auto;'
}).appendTo($lbl);
$('<span/>').text(node._('node-red-contrib-sun-position/position-config:common.days.' + (d + 7))).appendTo($lbl);
if (typeof prop.days === 'undefined' || prop.days === null || prop.days === '*') {
$cb.prop('checked', true);
} else if (prop.days !== '' && prop.days !== 'none') {
$cb.prop('checked', false);
prop.days.split(',').forEach(v => {
if (v == d) { // eslint-disable-line eqeqeq
$cb.prop('checked', true);
}
});
}
}
// row4 - 3 - Special Limit selection
const $divSpecLimit = $('<div/>',