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 += 'lat, lon'+ marker.getLatLng().toString().replace('LatLng(','').replace(')','') + ''; words += ''; } - words = ""+data.name+"
" + words; //"
" + words; + words = ""+data.name+"
" + words.replace(/\${name}/g,data.name); //"
" + words; var wopt = {autoClose:false, closeButton:true, closeOnClick:false, minWidth:200}; if (words.indexOf('