Openlayers Popup in React | How to? - reactjs

Is there anyway to get popup overlay of OpenLayers working in react? I have no ideia how to get this working..

I've included a regular react openlayer overlay that stays over Viena on the map and a second overlay that pops up where ever a user clicks.
First install openlayers with npm i ol
Then create a class MapExample.js:
import React, { Component } from "react";
import ReactDOM from 'react-dom';
import Map from "ol/Map.js";
import View from "ol/View.js";
import Overlay from "ol/Overlay.js";
import LayerTile from "ol/layer/Tile.js";
import SourceOSM from "ol/source/OSM.js";
import * as proj from 'ol/proj';
import './MapExample.css';
const posViena = proj.fromLonLat([16.3725, 48.208889]);
export default class MapExample extends Component {
constructor(props) {
super(props);
this.state = { center: posViena, zoom: 3 };
this.map = new Map({
target: null, // set this in componentDidMount
layers: [
new LayerTile({
source: new SourceOSM()
})
],
view: new View({
center: this.state.center,
zoom: this.state.zoom
})
});
}
componentDidMount() {
this.map.setTarget("map");
// Listen to map changes
this.map.on("moveend", () => {
let center = this.map.getView().getCenter();
let zoom = this.map.getView().getZoom();
this.setState({ center, zoom });
});
// Basic overlay
const overlay = new Overlay({
position: posViena,
element: ReactDOM.findDOMNode(this).querySelector('#overlay'),
positioning: 'center-center',
stopEvent: false
});
this.map.addOverlay(overlay);
// Popup showing the position the user clicked
this.popup = new Overlay({
element: ReactDOM.findDOMNode(this).querySelector('#popup')
});
// Listener to add Popup overlay showing the position the user clicked
this.map.on('click', evt => {
this.popup.setPosition(evt.coordinate);
this.map.addOverlay(this.popup);
})
}
componentWillUnmount() {
this.map.setTarget(null);
}
render() {
return (
<div>
<div id="map" style={{ width: "100%", height: "360px" }}/>
<div className="blue-circle" id="overlay" title="overlay"/>
<div className="blue-circle" id="popup" title="Welcome to OpenLayers"/>
</div>
);
}
}
With a MapExample.css file like this:
.blue-circle {
width: 30px;
height: 30px;
border: 1px solid #088;
border-radius: 15px;
background-color: #0FF;
opacity: 0.5;
z-index: 9999; /* Watch out for this!!! */
}
Finally have your App.js like this:
import React from 'react';
import './App.css';
import MapExample from "./MapExample";
function App() {
return (
<div className="App">
<MapExample />
</div>
);
}
export default App;

Related

My embeded google map is not displaying in my ReactJS

I am building in ReactJs my intent is to embed a map for directions to a business.
I have zero errors in the console, but yet still no display of the map? I read to change 100% on height and width to 1000px tried that and it did not work.
import React, { Component, useEffect } from 'react'
import { Maps, InfoWindow, Marker, GoogleApiWrapper } from 'google-maps-react';
const mapStyles = {
width: '100%',
height: '100%'
};
export class Googlemap extends Component {
render() {
return (
<Maps
google={this.props.google}
zoom={14}
style={mapStyles}
initialCenter={
{
lat: 42.338131411433736,
lng: -83.3935704444211
}
}
/>
);
}
}
export default GoogleApiWrapper({
apiKey: 'AIzaSyAxGE47DeXkR5rBP_wnJ_FS815zcWNwFRE'
})(Googlemap);

react-custom-scrollbars jumps to top on any action

I am using react-custom-scrollbars in a react web app because I need to have two independant scroll bars, one for the main panel and one for the drawer panel. My issue is that the content in the main panel is dynamic and whenever I take some action in the main panel that changes state the scroll bar jumps to the top of the panel again.
UPDATE:
I believe I need to list for onUpdate and handle the scroll position there. If it has changed then update if not do not move the position?
In code, I have a HOC call withScrollbar as follows:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import AutoSizer from 'react-virtualized-auto-sizer';
import { Scrollbars } from 'react-custom-scrollbars';
import { colors } from '../../theme/vars';
import { themes } from '../../types';
// This is a Higher Order Component (HOC) used to
// provide a scroll bar to other components
export default (ChildComponent, styling) => {
class ComposedComponent extends Component {
state = {
// position: ??
};
handleUpdate = () => {
//update position
//this.scrollbar.scrollToBottom();
};
render() {
return (
<AutoSizer>
{
({ width, height }) => (
<Scrollbars
style={{ width, height, backgroundColor: colors.WHITE, overflow: 'hidden', ...styling }}
onUpdate={() => this.handleUpdate()}
renderThumbVertical={props => <Thumb {...props} />}
autoHide
autoHideTimeout={1000}
autoHideDuration={200}
>
<ChildComponent {...this.props} />
</Scrollbars>
)
}
</AutoSizer>
);
}
}
return ComposedComponent;
};
const Thumb = styled.div`
background-color: ${props =>
props.theme.theme === themes.LIGHT ? colors.BLACK : colors.WHITE};
border-radius: 4px;
`;
in my MainView component I just wrap the export like this:
export default withScrollbar(LanguageProvider(connect(mapStateToProps, null)(MainView)));
I have read a few similar issues on this like this one: How to set initial scrollTop value to and this one scrollTo event but I cannot figure out how to implement in my case. Any tips or suggestions are greatly appreciated.
So I found a way to get this to work and it feels like a complete hack but I'm posting in hopes it might help someone else.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import AutoSizer from 'react-virtualized-auto-sizer';
import { Scrollbars } from 'react-custom-scrollbars';
import { colors } from '../../theme/vars';
import { themes } from '../../types';
// This is a Higher Order Component (HOC) used to
// provide a scroll bar to other components
export default (ChildComponent, styling) => {
class ComposedComponent extends Component {
state = {
stateScrollTop: 0,
};
onScrollStart = () => {
if (this.props.childRef) { // need to pass in a ref from the child component
const { scrollTop } = this.props.childRef.current.getValues();
const deltaY = Math.abs(scrollTop - this.state.stateScrollTop);
if (deltaY > 100) { // 100 is arbitrary. It should not be a high value...
this.props.childRef.current.scrollTop(this.state.stateScrollTop);
}
}
};
handleUpdate = () => {
if (this.props.childRef) {
const { scrollTop } = this.props.childRef.current.getValues();
this.setState({ stateScrollTop: scrollTop });
}
};
render() {
return (
<AutoSizer>
{
({ width, height }) => (
<Scrollbars
ref={this.props.childRef}
style={{ width, height, backgroundColor: colors.WHITE, overflow: 'hidden', ...styling }}
onScrollStart={e => this.onScrollStart(e)}
onUpdate={e => this.handleUpdate(e)}
renderThumbVertical={props => <Thumb {...props} />}
autoHide
autoHideTimeout={1000}
autoHideDuration={200}
>
<ChildComponent {...this.props} />
</Scrollbars>
)
}
</AutoSizer>
);
}
}
return ComposedComponent;
};
const Thumb = styled.div`
background-color: ${props =>
props.theme.theme === themes.LIGHT ? colors.BLACK : colors.WHITE};
border-radius: 4px;
`;
I use this HOC like this:
create a ref for the component you want to use it with
pass the ref to the component that will use the HOC:
class SomeChildComponent extends Component {
...
viewRef = React.createRef();
...
render() {
return ( <MainView childRef={this.viewRef} />)
}
import and wrap the component
import withScrollbar from '../../hoc/withScrollbar';
...
export default withScrollbar(MainView);
I tried the above solution and it didn't seem to work for me.
However what did work was making sure that my child components inside Scrollbars were wrapped in a div with a height of 100%:
<Scrollbars>
<div style={{ height: '100%' }}>
<ChildComponent />
<ChildComponent />
</div>
</Scrollbars>

Embed Typeform in React app

How can I embed a Typeform form in my React app?
The embed code that Typeform provide doesn't compile in JSX.
This is a sample of the embed code:
<div class="typeform-widget" data-url="https://sample.typeform.com/to/32423" style="width: 100%; height: 500px;"></div> <script> (function() { var qs,js,q,s,d=document, gi=d.getElementById, ce=d.createElement, gt=d.getElementsByTagName, id="typef_orm", b="https://embed.typeform.com/"; if(!gi.call(d,id)) { js=ce.call(d,"script"); js.id=id; js.src=b+"embed.js"; q=gt.call(d,"script")[0]; q.parentNode.insertBefore(js,q) } })() </script> <div style="font-family: Sans-Serif;font-size: 12px;color: #999;opacity: 0.5; padding-top: 5px;"> powered by Typeform </div>
You can use a React wrapper component I created: https://github.com/alexgarces/react-typeform-embed
It uses the official Typeform Embed SDK under the hood. Basically you have to pass your form url, but it also supports other SDK options.
import React from 'react';
import { ReactTypeformEmbed } from 'react-typeform-embed';
class App extends React.Component {
render() {
return <ReactTypeformEmbed url="https://demo.typeform.com/to/njdbt5" />;
}
}
You can view Typeform documentation for embedding with JavaScript here.
And their official npm module here.
Use React refs to trigger initialisation similarly to how you would initialise a jQuery plugin for instance.
import React from 'react';
import * as typeformEmbed from '#typeform/embed'
class Form extends React.Component {
constructor(props) {
super(props);
this.el = null;
}
componentDidMount() {
if (this.el) {
typeformEmbed.makeWidget(this.el, "https://sample.typeform.com/to/32423", {
hideFooter: true,
hideHeaders: true,
opacity: 0
});
}
}
render() {
return (
<div ref={(el) => this.el = el} style={{width: '100%', height: '300px'}} />
)
}
}
export default Form;
Inspired by Elise Chant's solution and using function based components, hooks and Typescript, this is what I come up with for my project. I did not want to install another thin wrapper on top of the official SDK.
import * as typeformEmbed from '#typeform/embed';
import React, { useEffect, useRef } from 'react';
interface EmbeddedTypeformProps {
link: string;
hideFooter?: boolean;
hideHeaders?: boolean;
opacity?: number;
}
export const EmbeddedTypeform: React.FunctionComponent<EmbeddedTypeformProps> = ({
link,
hideFooter = true,
hideHeaders = true,
opacity = 90,
}) => {
const elementRef = useRef(null);
useEffect(() => {
typeformEmbed.makeWidget(elementRef.current, link, {
hideFooter,
hideHeaders,
opacity,
});
}, [link]);
return (
<div
ref={elementRef}
style={{ width: '100%', height: '100%', flex: '1 1 auto' }}
/>
);
};
If you are using Typescript with react and you got this error just make sure that noImplicitAny set to false in tsconfig file :
"noImplicitAny": false,

Updating a div size based on props in react

I'm trying to implement a side nav bar like how it is shown on this website
https://adminlte.io/themes/AdminLTE/index2.html
Now I'm trying to basically emulate the expanded nav bar and mini navbar toggle. However, I'm having trouble updating the size of the navbar in react.
Here is my nav bar component
import React, { Component } from 'react';
class SideNavBar extends Component {
render() {
let sideBarStyle = {
'height': '100%',
'backgroundColor': 'lightblue',
'width': "80px",
'position': 'absolute',
'top': '0',
'left': '0'
}
setTimeout(() => {
sideBarStyle["width"] = "300px";
}, 1000)
return (
<div style={sideBarStyle}>
sidenavbar
</div>
);
}
}
export default SideNavBar;
I put a set timeout there just because I wanted to quickly test if it was possible to expand the div's width from a click event.
But I get the following error
TypeError: Cannot assign to read only property 'width' of object '#<Object>'
How do I go about updating an element's size based on click events?
Can you try below.
import React, { Component } from 'react';
class SideNavBar extends Component {
constructor(props){
super(props);
this.state = {
width : 80
}
}
componentDidMount(){
setTimeout(() => {
this.setState({
width: 300
})
}, 1000);
}
render() {
let sideBarStyle = {
'height': '100%',
'backgroundColor': 'lightblue',
'width': this.state.width,
'position': 'absolute',
'top': '0',
'left': '0'
}
return (
<div style={sideBarStyle}>
sidenavbar
</div>
);
}
}
export default SideNavBar;
One more thing you no need to specify px explicitly in React. Just no is enough. React will take care of px.

material-ui card with openlayers map inside?

I'm trying to place an openlayers map inside a material-ui card component. I've tried placing a div containing the map inside the card text and the card media sections.
Can anyone help me figure out the correct way to put a map inside the card?
import React, {PropTypes} from 'react'
import {connect} from 'react-redux'
import 'openlayers/dist/ol.css';
import ol from 'openlayers';
import {Card, CardActions, CardHeader, CardMedia} from 'material-ui/Card';
import FlatButton from 'material-ui/FlatButton';
import Paper from 'material-ui/Paper'
import baseTheme from 'material-ui/styles/baseThemes/lightBaseTheme'
import getMuiTheme from 'material-ui/styles/getMuiTheme'
import '../components/tap_events'
import styles from '../styles/ExportInfo.css'
import {updateExportInfo} from '../actions/exportsActions.js';
class ExportInfo extends React.Component {
constructor(props) {
super(props)
getChildContext() {
return {muiTheme: getMuiTheme(baseTheme)};
}
componentDidMount() {
this._initializeOpenLayers()
}
_initializeOpenLayers() {
const scaleStyle = {
background: 'white',
};
this._map = new ol.Map({
controls: [
new ol.control.ScaleLine(),
new ol.control.Attribution({
collapsible: false,
collapsed: false,
}),
new ol.control.Zoom({
className: styles.olZoom
})
],
interactions: ol.interaction.defaults({
keyboard: false,
altShiftDragRotate: false,
pinchRotate: false
}),
layers: [
// Order matters here
new ol.layer.Tile({
source: new ol.source.OSM()
}),
],
target: 'infoMap',
view: new ol.View({
projection: "EPSG:3857",
center: [110, 0],
zoom: 2.5,
minZoom: 2.5,
maxZoom: 22,
})
});
}
render() {
const providers = this.props.providers;
console.log("this is it"+providers[0])
return (
<div className={styles.wholeDiv}>
<div className={styles.root}>
<Paper className={styles.paper} zDepth={2} rounded>
<div className={styles.mapCard}>
<Card >
<CardHeader
title="Selected Area of Interest"
actAsExpander={true}
showExpandableButton={true}
/>
<CardMedia expandable={true}>
<div id="infoMap" className={styles.map} ref="olmap">
</div>
</CardMedia>
</Card>
</div>
</Paper>
</div>
</div>
)
}
}
ExportInfo.childContextTypes = {
muiTheme: React.PropTypes.object.isRequired,
}
export default (ExportInfo)
The id of your div (summaryMap) doesn't match target field in olmap configuration ('infoMap') both of them should be the same.
Additionally instead of calling _initializeOpenLayers in componentDidMount. I would recommend using ref callback and initialize target with actual control instead of string.
<div id="summaryMap" className={styles.map} ref={olmapDiv => this. _initializeOpenLayers(olmapDiv)}>
</div>

Resources