I am trying to switch between a leaflet map and a list. It is working fine untill the code is compiled by webpack. After clicking on an item the map shows up as it should. But when I close the map with the close button, I have map.remove() which removes the map but the class toggles do not toggle but instead they add the already existing class.
The function below runs to open the map and toggles correctly. And it runs when closing the map, but then the same toggles do not run correctly and adds the same class, as you can see in the photo below. The menu class is also duplicated which it should not.
Before I compile with webpack it does run correctly. Thanks!
const mapHandler = async id => {
let brewery;
if (id) {
brewery = await fetchBreweries.get('/' + id);
if (!brewery.latitude || !brewery.longitude) {
alert('Sorry, this entry has no coordinates.');
return;
};
};
if (!id) {
//no id means we are clicking the closehandler, so enable buttons in footer and remove the map
footerBtns.map(btn => btn.disabled = false);
//remove it before toggling classes or it will throw the can't find it error
if (map) map.remove();
};
mapEl.classList.toggle('not-visible');
list.classList.toggle('not-visible');
mapEl.classList.toggle('visible');
list.classList.toggle('visible');
if (!id) return;
//there is an id, so we are going to build the map to show it
//disable buttons in the footer so we cannot click on them when the map is shown
footerBtns.map(btn => btn.disabled = true);
if (aroundMyLocation) {
//if above flag is true than we want to build the map, show own location and then fly to brewery location
buildMap(myLocation);
setTimeout(() => {
map.flyTo([brewery.latitude, brewery.longitude], 12, {duration: 3})
}, 800);
setTimeout(() => {
L.marker([brewery.latitude, brewery.longitude])
.addTo(map)
.bindPopup(brewery.name)
.openPopup();
}, 1200);
} else {
//flag is false so we only want to show the brewery location
buildMap({lat: brewery.latitude, lng: brewery.longitude});
L.marker([brewery.latitude, brewery.longitude])
.addTo(map)
.bindPopup(brewery.name)
.openPopup();
};
//show brewery details in bottom right box
details.innerHTML = `
<h3>${brewery.name}</h3>
<p>${brewery.phone || 'number unknown'}</p>
<p>${brewery.street || 'street unknown'}</p>
<p>${brewery.city || 'city unknown'}</p>
`;
};
My script was being added twice with webpack. After adding inject: false to HtmlWebpackPlugin in my config file it was running as it should. I was trying to do a simple project so I could focus on learning webpack, serviceworkers and testing. A bit too much all at once probably...
Related
I have a report that contains a custom toggle visual. The toggle has two possible values, "OFF" (by default) or "ON"
What I would like to do is link that toggle value to a report theme change. So when the value of that toggle value changes, the report theme will update to either light or dark mode.
I'm at a stage where I can get the slicerState of the particular visual and determine it's value as being on or off. However, when trying to apply a report theme update, it appears to loop endlessly ( (see recording here))
My feeling is that it's related to the report.on('rendered'....... function but I'm looking to get a bit of help if possible. With it being a custom visual, I'm unable to listen to a buttonClick or dataSelection (as far as i'm aware), so i guess the only way I can detect that the toggle has been used is to track the slicerState function?
here's is the block of code relating to what i'm trying to do:
// Get a reference to the embedded dashboard HTML element
var $embedContainer = $("#embedContainer");
//define report embed
var report = powerbi.embed($embedContainer.get(0), config);
//when report renders
report.on('rendered', async () => {
//get all pages
const pages = await report.getPages()
//get active page
let activePage = pages.filter(function(page) {
return page.isActive
})[0];
//get visuals on active page
let visuals = await activePage.getVisuals();
//find target visual
const TargetVisual = visuals.filter(function(visual) {
return visual.name === "259fea6751434e7910b4"
})[0];
//get current state of visual
const state = await TargetVisual.getSlicerState();
//get current state value (on or off )
let filteredValue = state.filters[0].values
console.log(filteredValue)
//when the toggle is switched ON, apply the light theme.
if (filteredValue == "ON") {
/*
//when clickon on
report.applyTheme({
themeJson: themes.find(theme => theme.name === "dark")
}); } else {
//when the toggle is switched OFF, apply the dark theme.
//when clickon off
report.applyTheme({
themeJson: themes.find(theme => theme.name === "light")
});
*/
}
........
var themes = [
{
"name": "light",
"dataColors": ["#93A299", "#CF543F", "#B5AE53", "#848058", "#E8B54D", "#786C71", "#93A2A0", "#CF9A3F", "#8CB553", "#728458", "#D0E84D", "#786D6C"],
"background": "#FFFFFF",
"foreground": "#CF543F",
"tableAccent": "#93A299"
},
{
"name": "dark",
"dataColors": ["#31B6FD", "#4584D3", "#5BD078", "#A5D028", "#F5C040", "#05E0DB", "#3153FD", "#4C45D3", "#5BD0B0", "#54D028", "#D0F540", "#057BE0"],
"background": "#000000",
"foreground": "#4584D3",
"tableAccent": "#31B6FD"
}
]
It is not possible to track the slicer state which is applied at the runtime in Power BI embed. getSlicerState will give you an empty array of filters if you change the state of slicer at runtime. If you set the slicer state at compile time using setSlicerState then getslicerState will give you an array of filters and using that you can apply the theme.
if (state.filters.length > 0) {
report.applyTheme({
themeJson: themes.find(theme => theme.name === "dark")
});
Please find the references:
https://community.powerbi.com/t5/Developer/Power-BI-Embedded-Tracking-visual-filters-slicers/m-p/802196
https://learn.microsoft.com/javascript/api/overview/powerbi/control-report-slicers
On the project where I work (React, TS), we use the viewer and added the Box Selection extension for it.
The first time you activate it with a button in the toolbar, the extension works, the elements are highlighted. Then you can switch to another mode, for example, the orbit mode. And after that, when you click on the button that activates the "box Selection extension", the extension no longer works. The orbit mode remains working.
At the same time, the button is clicked (console.log() is fired) and the loadExtension('Autodesk.Box Selection') method works.
What could be the problem?
I will give some code snippets
This is the extension code:
export default function RectangleSelectionExtension(
this,
viewer,
options,
) {
window.Autodesk.Viewing.Extension.call(this, viewer, options);
}
RectangleSelectionExtension.prototype = (
Object.create(window.Autodesk.Viewing.Extension.prototype)
);
RectangleSelectionExtension.prototype.constructor = RectangleSelectionExtension;
RectangleSelectionExtension.prototype.load = () => true;
RectangleSelectionExtension.prototype.unload = () => true;
RectangleSelectionExtension.prototype.onToolbarCreated = function onToolbarCreated() {
this.group = this.viewer.toolbar.getControl('allExtensionsToolbar');
if (!this.group) {
this.group = new window.Autodesk.Viewing.UI.ControlGroup('allExtensionsToolbar');
this.viewer.toolbar.addControl(this.group);
}
// Add a new button to the toolbar group
this.button = new window.Autodesk.Viewing.UI.Button('RectangleSelectionExtension');
this.button.onClick = async () => {
const boxSelectionExtension = await this.viewer.loadExtension('Autodesk.BoxSelection');
this.viewer.toolController.activateTool(boxSelectionExtension.boxSelectionTool.getName());
boxSelectionExtension.addToolbarButton(this.viewer);
};
this.button.setToolTip('Select within a rectangle area');
this.button.addClass('RectangleSelectionExtension');
this.group.addControl(this.button);
};
window.Autodesk.Viewing.theExtensionManager.registerExtension('BoxSelection', RectangleSelectionExtension);
Next, in the Viewer component, we import and register the extension:
window.Autodesk.Viewing.theExtensionManager.registerExtension('RectangleSelectionExtension', RectangleSelectionExtension);
And this is how we initialize the viewer:
window.Autodesk.Viewing.Initializer(options, () => {
const container = document.getElementById('forgeViewer');
if (container) {
viewer = new window.Autodesk.Viewing.GuiViewer3D(
container,
{
token,
extensions: [
/* ...some extensions */
'RectangleSelectionExtension',
],
},
);
const startedCode = viewer.start();
if (startedCode > 0) {
return;
}
/* ...some eventListeners */
}
I'm not sure I understand the purpose of your RectangleSelectionExtension. From the code it looks like it just adds a button in the toolbar, and clicking that button repeatedly loads another extension (Autodesk.BoxSelection), repeatedly activates the box selection tool, and repeatedly adds the box selection button to the toolbar. That doesn't seem right.
If you're simply interested in the box selection, you can load it (and include it in the toolbar) like so:
// ...
viewer = new window.Autodesk.Viewing.GuiViewer3D(
container,
{
token,
extensions: [
/* ...some extensions */
'Autodesk.BoxSelection',
]
}
);
// and later ...
const boxSelectionExt = viewer.getExtension('Autodesk.BoxSelection');
boxSelectionExt.addToolbarButton(true); // Add the button to the toolbar
boxSelectionExt.addToolbarButton(false); // Remove the button from the toolbar
// ...
I have set up an app using Electron. For the most part, all of my code is in the index.html file. The app takes some data from the user, and uses it to plot areas on a Leaflet map. The map is plotted onto a <div>
The Leaflet map can be drawn with the taken geolocation, but, when the areas are plotted, the app reloads, causing the data to be lost.
I have rearranged most of the javascript, to find any issues there. I have tried global and local variables.
//Geoloaction for now using dev tools to spoof for test location.
const geo = navigator.geolocation;
let lat;
let lon;
let map;
document.getElementById("geo").addEventListener("click", () => {
geo.getCurrentPosition((position) => {
lat = position.coords.latitude;
lon = position.coords.longitude;
document.getElementById('location').value = (lat + ', ' + lon);
mapIt();
}, error);
});
document.getElementById('submit').addEventListener("click", () => {
let rad = document.getElementById('radius');
let squa = document.getElementById('square');
if (rad.checked) {
radius();
} else if (squa.checked) {
square();
}
});
//Mapping function using Leaflet
function mapIt() {
console.log("RUNNING");
map = L.map('maps').setView([lat, lon], 8);
const attribution = '© OpenStreetMap';
const tileURL = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
const tiles = L.tileLayer(tileURL, {
attribution
});
tiles.addTo(map);
}
function error(error) {
console.log(error.POSITION_UNAVAILABLE);
}
At the end of either of these two functions, the app is caused to reload:
function square() {
//Some Calculations...
let bounds = [
[lat,lon],[lat,lon]
];
L.rectangle(bounds, {
color: "#ff7800",
weight: 1
}).addTo(map);
map.fitBounds(bounds)
}
function radius() {
//Some Calculations...
L.circle([lat, lon], {
radius: (km_R * 1000)
}).addTo(map);
}
There should be no need for the app to reload. Everything worked fine, until I added those last two functions. I have debugged, and the functions run until the end. No error messages are ejected.
Is this just an issue with an Electron App's format? Will it go away, after I build and package the App?
Smells like you have an HTML <form> with a <button> or <input type="submit"/>, since you target an element with id "submit".
If that is the case, the HTML specified behaviour is to send the form data to the page indicated by the action attribute. If there no such indication, it sends the data to the current page, effectively reloading it.
See also javascript redirect not working anyway
The easy solution is simply to add event.preventDefault() at the start of your event listener.
Leaflet itself has no mean to reload your page.
For refererence for the case of a button: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button
If your buttons are not for submitting form data to a server, be sure to set their type attribute to button. Otherwise they will try to submit form data and to load the (nonexistent) response, possibly destroying the current state of the document.
I have one function called loadmap(){} where im creating map.Im loading this function with
ionViewDidEnter() {
this.loadmap();
}
Inside loadmap i have
this.map = leaflet.map("map").fitWorld();
thats how i initialize map
This is how i remove map when user changes tab.
ionViewDidLeave(){
this.map.remove();
}
This is my .locate function:
var usermarker;
this.map.locate({
setView: true,
maxZoom: 120,
watch:true,
enableHighAccuracy:true
}).on("locationfound", e => {
if (!usermarker) {
usermarker = new L.marker(e.latlng).addTo(this.map);
} else {
usermarker.setLatLng(e.latlng);
}
}).on("locationerror", error => {
if (usermarker) {
this.map.removeLayer(usermarker);
usermarker = undefined;
}
});
The problem is in first time .locate function works.but if i change tab and go back to map tab .locate function doesnt work.if i remove watch option it works.
Thanks
You have to call map.stopLocate() besides map.remove():
Stops watching location previously initiated by map.locate({watch: true})
Live demo: https://plnkr.co/edit/PKMPjfX3zD3QdWmEI0iX?p=preview (use the "Toggle map" button to simulate your changing tabs)
That being said, it is true that Leaflet could automatically do this when using the remove map method. => Merged in PR Leaflet/Leaflet#5893
i am using the ARCGIS Javascript API and trying to override the default right click behavior of the vertex points of a shape.
in ESRI's help it does list the onVertexClick event however from here it seems there is no way to determine if this is a right or left click event so i cannot override just the rightclick.
https://developers.arcgis.com/javascript/jsapi/edit.html
I am trying to set the right click behavour to just delete the current node/vertex instead of showing a menu with the option Delete.
EDIT
Here is the current event that exists within the ARCGIS api.
this.eventsList.push(dojo.connect(this._editToolbar, 'onVertexClick', $.proxy(this.addCustomVertexClickEvent, this)));
this event is already in the api however it does not return any way for me to determine left/right click.
your comment "listen for the click event then test the button attribute of the MouseEvent object" would work however i cant actually add a click event to the vertex points directly as these are inside the ARCGIS api code.
For anyone else who is looking for a way to do this without hacking around. You can listen to "contextmenu" (right click) events on the body, set a flag in the "contextmenu" handler to let the application know the current state. Simulate a click event to the "vertex handle" with a "mousedown", "mouseup" combination. In the "vertex-click" handler check for the right click flag set in the "contextmenu" handler
var editToolbar = new Edit(map, options);
var rightClick;
$('body').on('contextmenu', function(e) {
var target = e.target;
if(target.tagName === 'circle') {
// We only care about this event if it targeted a vertex
// which is visualized with an SVG circle element
// Set flag for right click
rightClick = true;
// Simulate click on vertex to allow esri vertex-click
// to fill in the data for us
var mouseDownEvt = new MouseEvent('mousedown', e.originalEvent);
target.dispatchEvent(mouseDownEvt);
var mouseUpEvt = new MouseEvent('mouseup', e.originalEvent);
target.dispatchEvent(mouseUpEvt);
// Since this event will be handled by us lets prevent default
// and stop propagation so the browser context menu doesnt appear
e.preventDefault();
e.stopPropagation();
}
});
editToolbar.on('vertex-click', function(e) {
if(rightClick) {
// Handle the right click on a vertex
rightClick = null;
}
});
after hearing back from ESRI it seems they do not provide this detail in their API so this is not possible yet.
I ended up doing this differently. I wanted to add a UI so the user could enter the XY of the point
// setup to allow editing
this.editToolbar = new EditToolbar(this.map, { allowDeleteVertices: false });
const rcMenuForGraphics = new RightClickVertexContextMenu();
const menu = rcMenuForGraphics.createMenu();
// bind to the map graphics as this is where the vertex editor is
this.map.graphics.on("mouse-over", (evt)=> {
// bind to the graphic underneath the mouse cursor
menu.bindDomNode(evt.graphic.getDojoShape().getNode());
});
this.map.graphics.on("mouse-out", (evt)=> {
menu.unBindDomNode(evt.graphic.getDojoShape().getNode());
});
this.editToolbar.on("vertex-click", (evt2) => {
rcMenuForGraphics.setCurrentTarget(evt2);
// evt2.vertexinfo.graphic.geometry.setX(evt2.vertexinfo.graphic.geometry.x - 1000);
})
// when the graphics layer is clicked start editing
gl.on("click", (evt: any) => {
this.map.setInfoWindowOnClick(false);
// tslint:disable-next-line: no-bitwise
const t: any = EditToolbar.MOVE | EditToolbar.EDIT_VERTICES;
this.editToolbar.deactivate();
this.editToolbar.activate(t, evt.graphic);
})
The code for the menu uses esri's vertex editor to grab the point, change its XY and then manually call the events to refresh the geometry. Only tested with polygon
import Menu = require("dijit/Menu");
import MenuItem = require("dijit/MenuItem");
import Graphic = require("esri/graphic");
import Edit = require("esri/toolbars/edit");
import Point = require("esri/geometry/Point");
class RightClickVertexContextMenu {
private curentTarget: { graphic: Graphic; vertexinfo: any; target: Edit; };
public createMenu() {
const menuForGraphics = new Menu({});
menuForGraphics.addChild(new MenuItem({
label: "Edit",
onClick: () => {
// this is a bit hooky. We grab the verx mover, change the x/y and then call the _moveStopHandler
console.log(this.curentTarget.vertexinfo);
const e: any = this.curentTarget.target;
const mover = e._vertexEditor._findMover(this.curentTarget.vertexinfo.graphic);
const g: Graphic = mover.graphic;
// add in a UI here to allow the user to set the new value. This just shifts the point to the left
g.setGeometry(new Point(mover.point.x - 1000, mover.point.y ))
e._vertexEditor._moveStopHandler(mover, {dx: 15});
this.curentTarget.target.refresh();
}
}));
menuForGraphics.addChild(new MenuItem({
label: "Delete",
onClick: () => {
// call the vertex delete handler
const ct: any = this.curentTarget.target;
ct._vertexEditor._deleteHandler(this.curentTarget.graphic)
}
}));
return menuForGraphics;
}
public setCurrentTarget(evt: { graphic: Graphic; vertexinfo: any; target: Edit; }) {
this.curentTarget = evt;
}
}
export = RightClickVertexContextMenu;