I am trying to have search control and onClick event handler to add Marker on my leaflet map. But search control is not working with onClick, but working fine when I don't have any onClick handlers.
class GeoSearch extends MapControl {
createLeafletElement(opts) {
const provider = new OpenStreetMapProvider()
const searchControl = new GeoSearchControl({
provider: provider,
position: 'bottomleft',
style: 'button',
})
return searchControl
}
componentDidMount() {
const {map} = this.props.leaflet
map.addControl(this.leafletElement)
}
}
export default withLeaflet(GeoSearch)
<Map
...
onClick={this.addMarker}
>
<Search />
</Map>
I guess addMarker event is also triggered once control is clicked, right? If so, this is the expected behavior since control elements emit events that are propagated to map. To suppress control events from propagation L.DomEvent.disableClickPropagation method could be utilized, here is a modified version of GeoSearch component:
class GeoSearch extends MapControl {
constructor(props, context) {
super(props);
}
createLeafletElement(opts) {
const provider = new OpenStreetMapProvider();
const searchControl = new GeoSearchControl({
provider: provider,
position: "topleft"
});
return searchControl;
}
componentDidMount() {
const { map } = this.props.leaflet;
map.addControl(this.leafletElement);
//To suppress control events from propagation
const containerDiv = this.leafletElement.getContainer();
L.DomEvent.disableClickPropagation(containerDiv);
}
}
Here is a demo
Related
I added a clustering service to my map, and i should remove this render event listener in some cases to avoid unnessesary map querys. Is there any usual way to do this?
export class MapClusterService {
clusterSource = 'search-results';
constructor(public mapService: MapService, private mapboxGlService: MapboxGlService, ) {this.updateMarkers = this.updateMarkers.bind(this);}
public async removeEventListeners() {
const map = await this.mapboxGlService.getMap();
map.off('render', this.updateMarkers); //this.updateMarkers() has error Argument of type 'Promise<void>' is not assignable to parameter of type '(ev: MapboxEvent<undefined> & EventData) => void'.
}
public async setUp() {
const map = await this.mapboxGlService.getMap();
console.log('setUP');
map.on('render', () => {
console.log('rendering');
this.updateMarkers();
});
}
updateMarkers is creating html divs for custom clusters.
Any idea would help, how to remove the event listener.
How/can we make number of visibleSlides responsive in CSS? / not have to use JS to alter visibleSlides based on breakpoints.
For example;
Each slide has min-width: 100px; min-height: 100px; ie. image we want to see detail so shouldn't be smaller than 100px.
We set visibleSlides to 8 (for desktop). On mobile we want to show only 2 slides. Because we don't want the individual slides to be less than 100px height and width, nor have the slides overlapping.
I know we could use react to check screen width and set visibleSlides, however it's not easy for all apps to have access to this, especially server side rendered like next.js.
See this sandbox https://codesandbox.io/s/pure-react-carousel-responsive-visible-slides-k8cui
(Forked from https://codesandbox.io/s/withered-wood-4bx36?fontsize=14&hidenavigation=1&theme=dark)
I had that problem for my app and actually couldn't find a way to do this with CSS. However, I've implemented this using ResizeObserver and react-hooks.
P.S. I'm using next js, and it's not a big issue to implement it on the server-side.
Here's my solution, hope it could help.
Step 1. Create an observer hook to listen for resize events from the app.
import { useEffect, useState, RefObject } from 'react';
import ResizeObserver from 'resize-observer-polyfill';
interface DOMRectReadOnly {
readonly bottom: number;
readonly height: number;
readonly left: number;
readonly right: number;
readonly top: number;
readonly width: number;
readonly x: number;
readonly y: number;
}
interface useResizeObserverProperties {
ref?: RefObject<Element> | null;
element?: Element | null | undefined;
callback?: (entry: ResizeObserverEntry) => void;
}
const IS_BROWSER = typeof window !== 'undefined';
/**
* Watch for the resizing of a React component or Element.
*
* #param hookProperties - Configuration optinos for the hook.
*
* #returns The `DOMRect` for the observed element.
*/
export const useResizeObserver = ({
ref,
element,
callback,
}: useResizeObserverProperties) => {
const [sizes, setSizes] = useState<DOMRectReadOnly>({
bottom: 0,
height: 0,
left: 0,
right: 0,
top: 0,
width: 0,
x: 0,
y: 0,
});
const handleResize = (entries: ResizeObserverEntry[]) => {
const [entry] = entries;
if (callback) callback(entry);
setSizes(entry.contentRect);
};
const [resizeObs] = useState(() =>
IS_BROWSER ? new ResizeObserver(handleResize) : undefined,
);
useEffect(() => {
if (!resizeObs) return;
let domNode;
if (ref) {
domNode = ref.current;
} else if (element) {
domNode = element;
}
if (domNode) {
resizeObs.observe(domNode);
}
return () => resizeObs.disconnect();
}, [ref, resizeObs, element]);
return sizes;
};
Step 2. In your component.tsx
import React, { useState, useRef } from 'react';
import { CarouselProvider, Slider, Slide } from 'pure-react-carousel';
import { useResizeObserver } from 'from previously created file';
const YourComponent = () => {
const [visibleSlides, setVisibleSlides] = useState(1);
const ref = useRef<HTMLDivElement>(null);
// Current width of element
const { width } = useResizeObserver({ ref });
switch (true) {
case width > 768 && width < 1280:
setVisibleSlides(2);
break;
/**
* Switch your cases here
*/
}
return (
<div ref={ref}>
<CarouselProvider
naturalSlideWidth={100}
naturalSlideHeight={125}
totalSlides={3}
visibleSlides={visibleSlides}
>
<Slider>
<Slide index={0}>Slide 1</Slide>
<Slide index={1}>Slide 2</Slide>
<Slide index={2}>Slide 3</Slide>
</Slider>
</CarouselProvider>
</div>
);
};
export default YourComponent;
Btw I'll recommend you throttle setVisibleSlides calls to avoid too much re-renders while resizing the window from dev-tools.
not an answer, but i'm trying to do the same.
How about using React hooks useState for the visibleSlides integer, and creating a window listener that listens for breakpoints and changes the state as needed....
How about setting a state and using useEffect to update the state on resize of window, then passing down the window size as a prop to the carousel component and choosing the number of visibleslides using the prop?
https://codesandbox.io/s/pure-react-carousel-responsive-visible-slides-forked-q29c1d?file=/src/App.js
Has anyone been able to use the React Leaflet Popup element to show a popup on mouseover rather than on click?
I can't seem to find a way to achieve this.
I've recently solved this problem using React Refs and the Leaflet API.
A barebones example:
import React, { Component } from 'react';
import { Circle } from 'react-leaflet';
class Foo extends Component {
render() {
const { center, radius } = this.props;
return (
<Circle
ref={circle => { this.circle = circle; }}
center={center}
radius={radius}
onMouseOver={() => {
this.circle.leafletElement.bindPopup('foo').openPopup();
}}/>
);
}
}
export default Foo;
I am new to CycleJS and I would like to subscribe 'click' events of a child component from its parent component; but, it's not working. I'm able to subscribe events inside the child component. Is it possible to subscribe events of a child component from its parent component? If it's possible, how can I do it? Here's the parent component:
import Rx from 'rx';
import Cycle from '#cycle/core';
import CycleDOM from '#cycle/dom';
import isolate from '#cycle/isolate';
import _ from 'underscore';
import Inboxmails from './../components/inboxmails';
const {div} = CycleDOM;
const Main = (sources) => {
const inboxmails=Inboxmails({DOM: sources.DOM});
sources.DOM.select('#inbox_1')
.events('click')
.do(event => event.preventDefault())
.subscribe(event => {
console.log(event);
});
const vtree$ = Rx.Observable.of(
div('.wrapper', [
inboxmails.DOM
]));
return {
DOM: vtree$
};
};
export default (sources) => isolate(Main)(sources);
And this is the child component
import Rx from 'rx';
import Cycle from '#cycle/core';
import CycleDOM from '#cycle/dom';
import isolate from '#cycle/isolate';
const { div} = CycleDOM;
const Inboxmails = function (sources) {
const inboxmails$ = Rx.Observable.of(div([
div("#inbox_1",[
"Click here"
])])
);
return {
DOM: inboxmails$
};
};
export default (sources) => isolate(Inboxmails)(sources);
Have the child return a sink of events that the parent needs.
const Inboxmails = function (sources) {
const inboxmails$ = Rx.Observable.of(div([
div("#inbox_1",[
"Click here"
])])
);
return {
DOM: inboxmails$,
clicks: sources.DOM.select('#inbox_1').events('click')
};
};
Then the parent can use inboxmails.clicks.
However, in Cycle.js there should never be any subscribe in your code (unless it's for debugging). Subscribe calls should only be in drivers.
I have a view that I created in my GWT application and I would like to embed/use one of the Twitter widgets that twitter provides (like this one http://twitter.com/about/resources/widgets/widget_search). They way they have it inserted is with a script that then writes out the appropiate html. I tried various ways to insert it but I was not able to get it to work -- we did get it working by putting it in an iFrame but that has presented other problems.
Here is some sample code that twitter provides to insert it:
<script src="http://widgets.twimg.com/j/2/widget.js"></script>
<script>
new TWTR.Widget({
version: 2,
type: 'search',
search: 'rainbow',
interval: 30000,
title: 'It\'s a double rainbow',
subject: 'Across the sky',
width: 250,
height: 300,
theme: {
shell: {
background: '#8ec1da',
color: '#ffffff'
},
tweets: {
background: '#ffffff',
color: '#444444',
links: '#1985b5'
}
},
features: {
scrollbar: false,
loop: true,
live: true,
behavior: 'default'
}
}).render().start();
</script>
So after looking directly at the twitter widget javascript code I saw that an id can be passed in so an existing element could be used. Too bad twitter didn't really document all of the different options available (at least not on the page I posted above), I may have figured this out earlier.
Here is a Sample Composite Widget that will insert a twitter widget and work in GWT, I have tested this code in GWT 2.4 and it worked in Firefox 6, Chrome 16 and IE9 (although IE had some weird styling issues in my environment).
import com.google.gwt.core.client.Callback;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.ScriptInjector;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
public class TwitterWidget extends Composite {
private JavaScriptObject widgetJsObj = null;
private final FlowPanel twPanel;
private final boolean destroyOnUnload;
public TwitterWidget() {
this(true);
}
public TwitterWidget(boolean destroyOnUnload) {
this.destroyOnUnload = destroyOnUnload;
twPanel = new FlowPanel();
twPanel.getElement().setId(DOM.createUniqueId());
initWidget(twPanel);
}
#Override
protected void onLoad() {
super.onLoad();
Callback<Void, Exception> callback = new Callback<Void, Exception>() {
#Override
public void onSuccess(Void result) {
if (nativeEnsureTwitterWidgetJsLoadedAndSetToWnd()) {
renderAndStart();
} else {
GWT.log("even though success has been called, the twitter widget js is still not available");
// some logic maybe keep checking every second for 1 minute
}
}
#Override
public void onFailure(Exception reason) {
// TODO Auto-generated method stub
GWT.log("exception loading the twitter widget javascript", reason);
}
};
boolean isTwitterWidgetAvailable = nativeEnsureTwitterWidgetJsLoadedAndSetToWnd();
if (isTwitterWidgetAvailable) {
renderAndStart();
} else {
ScriptInjector.fromUrl("http://widgets.twimg.com/j/2/widget.js")
.setWindow(ScriptInjector.TOP_WINDOW)
.setCallback(callback)
.inject();
}
}
#Override
protected void onUnload() {
super.onUnload();
if (widgetJsObj!=null) {
// need to manually destroy so that attached events get removed
if (destroyOnUnload) {
nativeDestroyTwitterWidget(widgetJsObj);
} else {
nativeStopTwitterWidget(widgetJsObj);
}
}
}
private native JavaScriptObject nativeRenderStartTwitterWidget(String domId) /*-{
var twObj = new $wnd.TWTR.Widget({
version: 2,
id: domId,
type: 'search',
search: 'rainbow',
interval: 30000,
title: 'It\'s a double rainbow',
subject: 'Across the sky',
width: 250,
height: 300,
theme: {
shell: {
background: '#8ec1da',
color: '#ffffff'
},
tweets: {
background: '#ffffff',
color: '#444444',
links: '#1985b5'
}
},
features: {
scrollbar: false,
loop: true,
live: true,
behavior: 'default'
}
}).render().start();
return twObj;
}-*/;
private native boolean nativeEnsureTwitterWidgetJsLoadedAndSetToWnd() /*-{
// this only works when TWTR has been properly loaded to $wnd directly
if (!(typeof $wnd.TWTR === "undefined") && !(null===$wnd.TWTR)) {
return true;
}
return false;
}-*/;
private native JavaScriptObject nativeStopTwitterWidget(JavaScriptObject twObj) /*-{
return twObj.stop();
}-*/;
private native JavaScriptObject nativeDestroyTwitterWidget(JavaScriptObject twObj) /*-{
return twObj.destroy();
}-*/;
private void renderAndStart() {
widgetJsObj = nativeRenderStartTwitterWidget(twPanel.getElement().getId());
// you can call other native javascript functions
// on twitWidgetJsObj such as stop() and destroy()
}
}
I found what I find to be a simpler solution here, no JSNI/pure gwt-java, easy to customize.