diff --git a/CHANGELOG.md b/CHANGELOG.md
index 428dd2b..9dec334 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
### Change Log for Node-RED Worldmap
+ - v4.0.0 - Breaking - Better context menu variable substitution and retention
+ Now uses ${name} syntax rather than $name so we can handle user defined variables in context menus.
+
- v3.2.0 - Sync up drawing sessions across browsers to same map
- v3.1.0 - Add esri overlay layers, and let geojson overlay rendering be customised
- v3.0.0 - Bump to Leaflet 1.9.4
diff --git a/README.md b/README.md
index 1f17074..81b1a70 100644
--- a/README.md
+++ b/README.md
@@ -13,10 +13,12 @@ Feel free to [![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%
### Updates
+- v4.0.0 - Breaking - Better context menu variable substitution and retention
+ Now uses ${name} syntax rather than $name so we can handle user defined variables in context menus.
- v3.2.0 - Sync up drawing sessions across browsers to same map
- v3.1.0 - Add esri overlay layers, and let geojson overlay rendering be customised
- v3.0.0 - Bump to Leaflet 1.9.4
- Move to geoman for drawing shapes.
+ Breaking - Move to geoman for drawing shapes.
Allow command.rotation to set rotation of map.
Allow editing of multipoint geojson tracks.
- v2.43.1 - Tweak drawing layer double click
@@ -73,12 +75,11 @@ Optional properties for **msg.payload** include
- **popup** : html to fill the popup if you don't want the automatic default of the properties list. Using this overrides photourl, videourl and weblink options.
- **label** : displays the contents as a permanent label next to the marker, or
- **tooltip** : displays the contents when you hover over the marker. (Mutually exclusive with label. Label has priority)
- - **contextmenu** : an html fragment to display on right click of marker - defaults to delete marker. You can specify `$name` to pass in the name of the marker. Set to `""` to disable just this instance.
+ - **contextmenu** : an html fragment to display on right click of marker - defaults to delete marker. You can specify `${name}` to substitute in the name of the marker. Set to `""` to disable just this instance.
Any other `msg.payload` properties will be added to the icon popup text box. This can be
overridden by using the **popup** property to supply your own html content. If you use the
-popup property it will completely replace the contents so photourl, videourl and weblink are
-meaningless in this mode.
+popup property it will completely replace the contents so photourl, videourl and weblink are meaningless in this mode.
### Icons
@@ -273,6 +274,7 @@ Other properties can be found in the leaflet documentation.
Shapes can also have a **popup** property containing html, but you MUST also set a property `clickable:true` in order to allow it to be seen.
+
### Drawing
A single *right click* will allow you to add a point to the map - you must specify the `name` and optionally the `icon` and `layer`.
@@ -280,7 +282,9 @@ A single *right click* will allow you to add a point to the map - you must speci
Right-clicking on an icon will allow you to delete it.
If you select the **drawing** layer you can also add and edit polylines, polygons, rectangles and circles.
-Once an item is drawn you can right click to edit or delete it. Double click the object to exit edit mode.
+Once an item is drawn you can right click to edit or delete it.
+
+Double click the object to exit edit mode.
### Buildings
@@ -389,20 +393,22 @@ The "connected" action additionally includes a:
There are some internal functions available to make interacting with Node-RED easier (e.g. from inside a user defined popup., these include:
- - **feedback()** : it takes 2, 3, or 4 parameters, name, value, and optionally an action name (defaults to "feedback"), and optional boolean to close the popup on calling this function, and can be used inside something like an input tag - `onchange='feedback(this.name,this.value,null,true)'`. Value can be a more complex object if required as long as it is serialisable. If used with a marker the name should be that of the marker - you can use `$name` to let it be substituted automatically.
+ - **feedback()** : it takes 2, 3, or 4 parameters, name, value, and optionally an action name (defaults to "feedback"), and optional boolean to close the popup on calling this function, and can be used inside something like an input tag - `onchange='feedback(this.name,this.value,null,true)'`. Value can be a more complex object if required as long as it is serialisable. If used with a marker the name should be that of the marker - you can use `${name}` to let it be substituted automatically.
+
+ - **addToForm()** : takes a property name value pair to add to a variable called `form`. When used with contextmenu feedback (above) you can set the feedback value to `"_form"` to substitute this accumulated value. This allows you to do things like `onBlur='addToForm(this.name,this.value)'` over several different fields in the menu and then use `feedback(this.name,"_form")` to submit them all at once. For example a simple multiple line form could be as per the example below:
- - **addToForm()** : takes a property name value pair to add to a variable called form. When used with contextmenu feedback (above) you can set the feedback value to `"$form"` to substitute this accumulated value. This allows you to do things like `onChange='addToForm(this.name,this.value)'` over several different fields in the menu and then use `feedback(this.name,"$form")` to submit them all at once. For example a simple multiple line form could be:
+ Also if you wish to retain the values between separate openings of this form you can assign property names to the value field in the form `value="${foo}`, etc. These will then appear as part of an **value** property on the worldmap-in node message.
```
-var menu = 'Add some data
'
-menu += 'Add more data
'
-menu += ''
+var menu = 'Add some data
'
+menu += 'Add more data
'
+menu += ''
msg.payload = { command: { "contextmenu":menu } }
```
- - **delMarker()** : takes the name of the marker as a parameter. In a popup this can be specified as `$name` for dynamic substitution.
+ - **delMarker()** : takes the name of the marker as a parameter. In a popup this can be specified as `${name}` for dynamic substitution.
- - **editPoly()** : takes the name of the shape or line as a parameter. In a popup this can be specified as `$name` for dynamic substitution.
+ - **editPoly()** : takes the name of the shape or line as a parameter. In a popup this can be specified as `${name}` for dynamic substitution.
## Controlling the map
@@ -478,11 +484,11 @@ careful escaping quotes, and that they remain matched.
For example a marker popup with a slider (note the \ escaping the internal ' )
- popup: ''
+ popup: ''
Or a marker contextmenu with an input box
- contextmenu: ''
+ contextmenu: ''
Or you can add a contextmenu to the map. Simple contextmenu with a button
diff --git a/package.json b/package.json
index f0a52bb..2a1cdaa 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "node-red-contrib-web-worldmap",
- "version": "3.2.0",
+ "version": "4.0.0",
"description": "A Node-RED node to provide a web page of a world map for plotting things on.",
"dependencies": {
"@turf/bezier-spline": "~6.5.0",
diff --git a/worldmap/worldmap.js b/worldmap/worldmap.js
index e337cbc..6ccb8c4 100644
--- a/worldmap/worldmap.js
+++ b/worldmap/worldmap.js
@@ -28,11 +28,12 @@ var heat;
var minimap;
var sidebyside;
var layercontrol;
+var colorControl;
var drawCount = 0;
var drawingColour = "#910000";
var drawcontextmenu = "";
var sendDrawing;
-var colorControl;
+var rmenudata = {};
var sendRoute;
var oldBounds = {ne:{lat:0, lng:0}, sw:{lat:0, lng:0}};
var edgeLayer = new L.layerGroup();
@@ -55,30 +56,30 @@ var iconSz = {
"Command": 44
};
-var filesAdded = '';
-
-var loadStatic = function(fileName){
+var filesAdded = '';
+
+var loadStatic = function(fileName){
if(filesAdded.indexOf(fileName) !== -1)
return
var head = document.getElementsByTagName('head')[0]
- if(fileName.indexOf('js') !== -1) {
- var script = document.createElement('script')
+ if(fileName.indexOf('js') !== -1) {
+ var script = document.createElement('script')
script.src = fileName
script.type = 'text/javascript'
- console.log("Loading: ",fileName)
- head.append(script)
+ console.log("Loading: ",fileName)
+ head.append(script)
filesAdded += ' ' + fileName
} else if (fileName.indexOf('css') !== -1) {
- var style = document.createElement('link')
+ var style = document.createElement('link')
style.href = fileName
style.type = 'text/css'
style.rel = 'stylesheet'
- console.log("Loading: ",fileName)
+ console.log("Loading: ",fileName)
head.append(style);
filesAdded += ' ' + fileName
} else {
- console.log("Unsupported file type: ",fileName)
- }
+ console.log("Unsupported file type: ",fileName)
+ }
}
// L.PM.setOptIn(true);
@@ -221,10 +222,11 @@ if (inIframe === true) {
map = new L.map('map',{
zoomSnap: 0.1,
rotate: true,
- rotateControl: {
- closeOnZeroBearing: true,
- position: 'topleft'
- },
+ rotateControl: false,
+ // rotateControl: {
+ // closeOnZeroBearing: true,
+ // position: 'topleft'
+ // },
bearing: 0}).setView(startpos, startzoom);
map.whenReady(function() {
connect();
@@ -866,20 +868,22 @@ var addThing = function() {
var form = {};
var addToForm = function(n,v) { form[n] = v; }
var feedback = function(n,v,a,c) {
- if (v === "$form") { v = form; }
+ if (v === "_form") { v = form; }
if (markers[n]) {
- //var fp = markers[n]._latlng;
- // ws.send(JSON.stringify({action:a||"feedback", name:n, value:v, layer:markers[n].lay, lat:fp.lat, lon:fp.lng}));
- var fb = allData[n];
- fb.action = a || "feedback";
- if (v !== undefined) { fb.value = v; }
- ws.send(JSON.stringify(fb));
+ console.log("FB1",n,v,a,c)
+ allData[n].action = a || "feedback";
+ if (v !== undefined) { allData[n][a||"value"] = v; }
+ ws.send(JSON.stringify(allData[n]));
+ setMarker(allData[n]);
}
else if (polygons[n]) {
+ console.log("FB2",n,v,a)
sendDrawing(n,v,a)
}
else {
if (n === undefined) { n = "map"; }
+ console.log("FB3",n,v,a,c)
+ rmenudata = v;
ws.send(JSON.stringify({action:a||"feedback", name:n, value:v, lat:rclk.lat, lon:rclk.lng}));
}
if (c === true) { map.closePopup(); }
@@ -909,6 +913,12 @@ map.on('contextmenu', function(e) {
if ((hiderightclick !== true) && (addmenu.length > 0)) {
rclk = e.latlng;
form = {};
+ var ramen = ""+addmenu;
+ for (const item in rmenudata) {
+ ramen = ramen.replace(new RegExp("\\${"+item+"}","g"),rmenudata[item]);
+ }
+ ramen = ramen.replace(/\${.*?}/g,'')
+ rightmenuMap.setContent(ramen);
rightmenuMap.setLatLng(e.latlng);
map.openPopup(rightmenuMap);
setTimeout( function() {
@@ -1140,9 +1150,15 @@ var addOverlays = function(overlist) {
L.DomEvent.stopPropagation(e);
var name = e.target.name;
var rmen = L.popup({offset:[0,-12]}).setLatLng(e.latlng);
- var d = drawcontextmenu || "
";
- rmen.setContent(d.replace(/\$name/g,name));
- map.openPopup(rmen);
+ var d = drawcontextmenu || "
";
+ d = d.replace(/\${name}/g,name);
+ if (e.target.value) {
+ for (const item in e.target.value) {
+ d = d.replace(new RegExp("\\${"+item+"}","g"),e.target.value[item]);
+ }
+ }
+ rmen.setContent(d);
+ setImmediate(function() { map.openPopup(rmen) });
});
e.layer.bindPopup(name);
@@ -1167,9 +1183,9 @@ var addOverlays = function(overlist) {
polygons[name].name = name;
layers["_drawing"].addLayer(shape.layer);
- var rightmenuMarker = L.popup({offset:[0,-12]}).setContent(drawcontextmenu.replace(/\$name/g,name) || "
");
+ var rightmenuMarker = L.popup({offset:[0,-12]}).setContent(drawcontextmenu.replace(/\${name}/g,name).replace(/\${.*?}/g,'') || "
");
if (e.layer.options.fill === false && navigator.onLine) {
- rightmenuMarker = L.popup({offset:[0,-12]}).setContent(drawcontextmenu.replace(/\$name/g,name) || "
");
+ rightmenuMarker = L.popup({offset:[0,-12]}).setContent(drawcontextmenu.replace(/\${name}/g,name).replace(/\${.*?}/g,'') || "
");
}
rightmenuMarker.setLatLng(cent);
setTimeout(function() {map.openPopup(rightmenuMarker)},25);
@@ -1182,8 +1198,8 @@ var addOverlays = function(overlist) {
shape.layer.bindPopup(thing);
delMarker(n,true);
if (v) {
- shape.layer.form = v;
- shape.m.form = v;
+ shape.layer.value = v;
+ shape.m.value = v;
}
polygons[thing] = shape.layer;
polygons[thing].lay = "_drawing";
@@ -1457,7 +1473,7 @@ var editPoly = function(pname,fun) {
lo = e.target._latlng.lng;
}
var m = {action:"draw", name:pname, layer:polygons[pname].lay, options:e.target.options, radius:e.target._mRadius, lat:la, lon:lo};
- if (e.target.form) { m.form = e.target.form; }
+ if (e.target.value) { m.value = e.target.value; }
if (e.target.hasOwnProperty("_latlngs")) {
if (e.target.options.fill === false) { m.line = e.target._latlngs; }
else { m.area = e.target._latlngs[0]; }
@@ -1504,9 +1520,13 @@ function setMarker(data) {
rightcontext = "";
}
if ((data.contextmenu !== undefined) && (typeof data.contextmenu === "string")) {
- rightcontext = data.contextmenu.replace(/\$name/g,'"'+data.name+'"');
+ rightcontext = data.contextmenu.replace(/\${name}/g,data.name);
delete data.contextmenu;
}
+ for (const item in allData[data.name].value) {
+ rightcontext = rightcontext.replace(new RegExp("\\${"+item+"}","g"),allData[data.name].value[item]);
+ }
+ rightcontext = rightcontext.replace(/\${.*?}/g,'')
if (rightcontext.length > 0) {
var rightmenuMarker = L.popup({offset:[0,-12]}).setContent(""+data.name+"
"+rightcontext);
if (hiderightclick !== true) {
@@ -1731,7 +1751,7 @@ function setMarker(data) {
if (data.draggable === true) { drag = true; }
if (data.hasOwnProperty("icon")) {
- var dir = parseFloat(data.hdg ?? data.heading ?? data.bearing ?? "0");
+ var dir = parseFloat(data.track ?? data.hdg ?? data.heading ?? data.bearing ?? "0") + map.getBearing();
if (data.icon === "ship") {
marker = L.boatMarker(ll, {
title: data.name,
@@ -2200,7 +2220,7 @@ function setMarker(data) {
words += '