How to make number of visibleSlides responsive in CSS in pure-react-carousel - pure-react-carousel

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

Related

My geosearch is not working with leaflet onClick

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

React Leaflet: Show popup on mouseover

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;

CycleJS - subscribing click events of a child component

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.

How to insert Twitter widget into a GWT view

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.

Tooltips for GWT tree: adding mouseovers to nodes

I'm trying to add tooltips for the nodes of a Tree in GWT. As such, I'd like to add a mouseover listener for the nodes of a tree rather than on the tree itself.
The Treelistener interface seems to be what I want but this is now deprecated in lieu of the handler system. I don't quite understand how to get mouseover behaviour on the cell as I only seem to be able to add a MouseOverHandler to the tree itself.
Any help would be appreciated, thank you.
I'm going to stick my neck out a bit here since I haven't actually used a Tree in GWT yet, but I see that the TreeItem class is a subclass of UIObject. Any UIObject can have its setTitle() method called. Under the hood, this method sets the standard HTML title attribute to be whatever string you pass into setTitle().
This should give you the tooltip behavior you seek. As an added bonus, the browser does all of the mouse event handling for you:
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Tree;
import com.google.gwt.user.client.ui.TreeItem;
public class TreeTest extends Composite {
public TreeTest() {
Tree root = new Tree();
initWidget(root);
TreeItem dogs = new TreeItem("canines");
dogs.addItem("Fido").setTitle("faithful");
dogs.addItem("Lassie").setTitle("starlet");
dogs.addItem("Touser").setTitle("ruthless killer");
root.addItem(dogs);
TreeItem cats = new TreeItem("felines");
cats.addItem("Boots").setTitle("needy");
cats.addItem("Fabio").setTitle("aloof");
cats.addItem("Mandu").setTitle("bob seger");
root.addItem(cats);
}
}
Edit: Now let's imagine that you don't want to use the browser's built-in tool-tip mechanism described above, and that you would like to handle the mouse events yourself.
TreeItem might look, on the surface, as a non-starter. After all, it inherits directly from UIObject and not from Widget. (The key difference that a Widget adds to UIObject is, after all, the ability to handle events. So one would think that we cannot add handlers to the TreeItem!)
While this is strictly true, notice that TreeItem gives us the following constructor:
public TreeItem(Widget widget)
When we make each instance, then, we can pass a real Widget into it (such as a Label, perhaps, or maybe your own class MyWidget extends Composite) and we can add event handlers directly to that:
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Tree;
import com.google.gwt.user.client.ui.TreeItem;
public class AnotherTreeTest extends Composite {
public AnotherTreeTest() {
Tree root = new Tree();
initWidget(root);
TreeItem dogs = new TreeItem("canines");
makeItem(dogs,"Fido","faithful");
makeItem(dogs,"Lassie","starlet");
makeItem(dogs,"Touser","ruthless killer");
root.addItem(dogs);
TreeItem cats = new TreeItem("felines");
makeItem(cats,"Boots","needy");
makeItem(cats,"Fabio","aloof");
makeItem(cats,"Mandu","bob seger");
root.addItem(cats);
}
private void makeItem(TreeItem parent, String name, final String tip) {
Label label = new Label(name);
TreeItem animal = new TreeItem(label);
label.addMouseOverHandler(new MouseOverHandler() {
#Override
public void onMouseOver(MouseOverEvent event) {
GWT.log("mouse over " + tip); // do something better here
}
});
label.addMouseOutHandler(new MouseOutHandler() {
#Override
public void onMouseOut(MouseOutEvent event) {
GWT.log("mouse out " + tip); // do something better here
}
});
parent.addItem(animal);
}
}
Note that there may be other ways to accomplish this that are less expensive. If you have an enormous tree, then creating a Widget for each node can get expensive. Then you might want to explore a more sophisticated way of dealing with your mouse events, perhaps by having one handler that checks to see which element it is in.
A TreeItem can contains a Widget object. So add a MouseOverHandler and a MouseOutHandler on a widget (i.e. a Label) and put the widget inside the TreeItem to add :
Label myItemContent = new Label("My content");
myItemContent.addMouseOverHandler(new MouseOverHandler() {
public void onMouseOver(MouseOverEvent event) {
// construct and/or open your tooltip
}
});
myItemContent.addMouseOutHandler(new MouseOutHandler() {
public void onMouseOut(MouseOutEvent event) {
// close your tooltip
}
});
//put your Label inside a TreeItem
TreeItem myItem = new TreeItem(myItemContent);
// let's assume that parentNode is an ItemTree
parentNode.addItem(myItem);
An other solution can be to use GwtQuery. GwtQuery allows to bind event handler to any DOM element :
import static com.google.gwt.query.client.GQuery.$;
...
TreeItem myItem = new TreeItem("My content");
$(myItem.getElement()).hover(new Function() {
//method called on mouse over
public void f(Element e) {
// construct and/or open your tooltip
}
}, new Function() {
//method called on mouse out
public void f(Element e) {
//close your tooltip
}
});
parentNode.addItem(myItem);
Julien
An alternative way that I settled upon is to make use of CSS.
/**
* #return a label with a CSS controlled popup
*/
public static Widget labelWithHTMLPopup(String text, String description)
{
FlowPanel p = new FlowPanel();
p.addStyleName("tooltipLabel");
p.add(new Label(text));
HTML contents = new HTML(description);
contents.setStyleName("tooltip");
p.add(contents);
return p;
}
With accompanying css:
/*************** Tooltip **************/
div.tooltip
{
display: none;
position: absolute;
border: 1px outset black;
left: 90%;
top: -20px;
background: white;
padding: 5px;
box-shadow: 3px 3px 2px 0px #555;
overflow-y: auto;
max-height: 150px;
font-size: 80%;
z-index: 99;
}
div.tooltipLabel
{
position: relative;
}
div.tooltipLabel:hover div.tooltip
{
display: block;
position: absolute;
}
Of course, you can change the style, add fades etc as you like.
Less javas/javascript and more css.