Search code examples
reactjsgoogle-mapsreact-google-maps

React Google Map DirectionsRenderer Issue


As I want to create Route from my current location to a particular clicked marker in React. For Implementing this I am using React Google Map library (react-google-maps) and it's direction example (Direction Example) but unfortunately I am unable to call new google.maps.DirectionsService() method in React component. It is giving me error as google is undefined.There is already open issue for the same scenario (google is undefined)


Solution

  • To get access to google class at window level, I have created plain javascript file .

    UserDetails.js

    module.exports = {
       "Steepless": {
        directionsService: new google.maps.DirectionsService(),
        directionsRenderer: new google.maps.DirectionsRenderer(),
        elevationService: new google.maps.ElevationService(),
        travelMode: google.maps.TravelMode.DRIVING,
        directionStatus: google.maps.DirectionsStatus.OK,
        longestDistance: 0,
        highestElevation: 0,
        lowestElevation: Infinity,
        chartWidth: 400,
        chartBarWidth: 2
      },
      getOrigin: function (lat, lng) {
        var origin = new google.maps.LatLng(lat, lng);
        return origin;
      },
      getDestination: function (lat, lng) {
        var destination = new google.maps.LatLng(lat, lng);
        return destination;
      }
    };
    

    reactComponent.jsx

    import * as React from "react";
    var config = require("./Config");
    var $ = require('jquery');
    var _this;
    var userDetails = require("./api/UserDetails");
    var _ = require("lodash")
    var modelState = undefined;
    const { Button, Popover, Modal, Tooltip, OverlayTrigger, Accordion, Panel, Row, Col } = require("../../../../../../node_modules/react-bootstrap");
    var GoogleMapLib = require("../../../../../../node_modules/react-google-maps");
    var GoogleMap = GoogleMapLib.GoogleMap;
    var withGoogleMap = GoogleMapLib.withGoogleMap;
    var Marker = GoogleMapLib.Marker;
    var InfoWindow = GoogleMapLib.InfoWindow;
    var Direction = GoogleMapLib.DirectionsRenderer;
    var buyNow;
    
    
    import "../styles/glyphs-style.scss";
    
    import ReferFriend from "./ReferFriend";
    import SaleAlert from "./SaleAlert";
    import WishListButton from "./WishListButton";
    var ProductDetails = React.createClass({
      getInitialState: function () {
        return {
          appended: '',
          showModal: true,
          isLoding: 'none',
          cordinates: '',
          morelikethis: '',
          merchantCollection: '',
          getParent: false,
          acoordianData: '',
          markers: [{
            position: {
              lat: 40.712784, lng: -74.005941,
            },
            key: `New York`,
            defaultAnimation: 2
          }],
          center: {
            lat: parseFloat('40.712784'),
            lng: parseFloat('-74.005941'),
          },
          route: '',
          zoom: 7,
          showInfo: false,
          mywishlistLable: 'ADD TO LIST',
          saleAlertLable: 'GET SALE ALERT',
          wishListChecked: 'glyphicon-heart-empty',
          rawRelated: ''
        };
      },
    
      contextTypes: {
        router: React.PropTypes.object.isRequired
      },
    
      /*Model related stuffs */
      close() {
        userDetails.changeProductDetailsStatus(false);
        this.setState({ mywishlistLable: 'ADD TO LIST' });
        this.setState({ wishListChecked: 'glyphicon-heart-empty' });
        this.setState({ showModal: false });
        modelState = true;
      },
      open() {
        this.setState({ showModal: true });
      },
    
      /*Map related stuff*/
      handleMapLoad: function (map) {
        this._mapComponent = map;
        if (map) {
          console.log(map.getZoom());
        }
      },
    
      /*
       * This is called when you click on the map.
       * Go and try click now.
       */
      handleMapClick: function (event) {
        const nextMarkers = [
          ...this.state.markers,
          {
            position: event.latLng,
            defaultAnimation: 2
    
          },
        ];
        this.setState({
          markers: nextMarkers,
        });
    
        if (nextMarkers.length === 3) {
          this.props.toast(
            `Right click on the marker to remove it`,
            `Also check the code!`
          );
        }
      },
      // Toggle to 'true' to show InfoWindow and re-renders component
      handleMarkerClick: function (targetMarker) {
        var showType;
        var markers = this.state.markers.map(function (item, i) {
    
          if (item.key === targetMarker.key) {
            showType = true;
    
            var geoDetails = userDetails.getUserGeoInfo();
            var destination = userDetails.getDestination(targetMarker.position.lat,targetMarker.position.lng);
            var origin = userDetails.getOrigin(geoDetails.lat,geoDetails.lng);
            console.log(userDetails.Steepless.directionsService);
            userDetails.Steepless.directionsService.route({
              origin: origin,
              destination: destination,
              travelMode: userDetails.Steepless.travelMode,
            }, (result, status) => {
              if (status === userDetails.Steepless.directionStatus) {
                this.setState({
                  route: result,
                });
              } else {
                console.error(`error fetching directions ${result}`);
              }
            });
    
          } else {
            showType = item.showInfo;
          }
          return {
            position: {
              lat: parseFloat(item.position.lat),
              lng: parseFloat(item.position.lng)
            },
            key: i,
            showInfo: showType,
            infoContent: (
              item.infoContent)
          }
        }.bind(this));
        this.setState({ markers: markers })
    
    
      },
      handleMarkerClose: function (targetMarker) {
        var showType;
        var markers = this.state.markers.map(function (item, i) {
          if (item.key === targetMarker.key) {
            showType = false;
          } else {
            showType = item.showInfo;
          }
          return {
            position: {
              lat: parseFloat(item.position.lat),
              lng: parseFloat(item.position.lng)
            },
            key: i,
            showInfo: showType,
            infoContent: (
              item.infoContent)
          }
        }.bind(this));
        this.setState({ markers: markers })
      },
    
      handleMarkerRightClick: function (targetMarker) {
        /*
         * All you modify is data, and the view is driven by data.
         * This is so called data-driven-development. (And yes, it's now in
         * web front end and even with google maps API.)
         */
        const nextMarkers = this.state.markers.filter(marker => marker !== targetMarker);
        this.setState({
          markers: nextMarkers,
        });
      },
    
      getInfoContents: function (item) {
    
      },
      GetModelInfo: function () {
        this.GetProdInfo();
        var geoDetails = userDetails.getUserGeoInfo();
        if (geoDetails) {
          this.setState({
            center: {
              lat: parseFloat(geoDetails.lat),
              lng: parseFloat(geoDetails.lng)
            }
          });
          var lat = geoDetails.lat;
          var lng = geoDetails.lng;
        } else {
          var lat = null;
          var lng = null;
        }
        this.setState({ cordinates: geoDetails });
        $.ajax({
          url: config.magentoBaseUrl + config.MORE_LIKE_THIS,
          dataType: 'json',
          cache: false,
          type: 'POST',
          data: {
            category_name: this.props.productInfo.category_name,
            brand: this.props.productInfo.brand,
            product_category_ids: this.props.productInfo.product_category_ids,
            _id: this.props.productInfo._id,
            lat: lat,
            lng: lng,
            merchant_id: this.props.productInfo.merchant_id,
            storename: this.props.productInfo.store_name
          },
          success: function (data) {
            if (data.productData) {
              //this.setState({morelikethis:data.productData})
              this.setState({ rawRelated: data.productData })
              var merchantData = JSON.parse(data.merchantData);
              if (merchantData) {
                this.setState({ acoordianData: merchantData });
                var markers = merchantData.map(function (item, i) {
                  return {
                    position: {
                      lat: parseFloat(item.latitude),
                      lng: parseFloat(item.longitude)
                    },
                    key: item.storename,
                    showInfo: false,
                    infoContent: (
                      this.getInfoContents(item))
                  }
                }.bind(this));
                this.setState({ markers: markers })
              }
    
            }
          }.bind(this),
          error: function (xhr, status, err) {
    
          }.bind(this)
        });
      },
      GetProdInfo: function () {
        var details = userDetails.getUserInfo();
        if (details) {
          var prodCheckData = {
            cust_id: details.id,
            skuNumber: this.props.productInfo.sku_number,
            userEmail: details.email
          }
        }
        $.ajax({
          url: config.magentoBaseUrl + config.wishlist_action + 'prod_avail/',
          dataType: 'json',
          cache: false,
          type: 'POST',
          data: prodCheckData,
          success: function (data) {
            if (data.wishdata == "exist") {
              this.setState({ mywishlistLable: 'ADDED IN LIST' });
              this.setState({ wishListChecked: 'glyphicon-heart' });
            }
          }.bind(this),
          error: function (xhr, status, err) {
    
          }.bind(this)
        });
      },
    
      getHome: function () {
        this.setState({ getParent: true });
      },
      wishListAction: function () {
        var details = userDetails.getUserInfo();
        //userDetails.changeProductDetailsStatus(false);
        if (details) {
          if (this.state.wishListChecked === "glyphicon-heart-empty") {
            this.wishListTransactcion('add/', details);
            this.setState({ wishListChecked: 'glyphicon-heart' });
            this.setState({ mywishlistLable: 'ADDED IN LIST' });
          }
          if (this.state.wishListChecked === "glyphicon-heart") {
            this.wishListTransactcion('remove/', details);
            this.setState({ wishListChecked: 'glyphicon-heart-empty' });
            this.setState({ mywishlistLable: 'ADD TO LIST' });
          }
        } else {
          this.context.router.push('/');
          userDetails.setOpenLoginModal('true');
        }
      },
      wishListTransactcion: function (action, details) {
        var wishlistData = {
          cat_id: this.props.productInfo.product_category_ids[0],
          cust_id: details.id,
          skuNumber: this.props.productInfo.sku_number,
          userEmail: details.email,
          merchantName: this.props.productInfo.store_name,
          merchantId: this.props.productInfo.merchant_id,
          aprice: this.props.productInfo.actual_price,
          dprice: this.props.productInfo.discount_price
        }, url = config.magentoBaseUrl + config.wishlist_action + action;
        $.ajax({
          url: url,
          dataType: 'json',
          cache: false,
          type: 'POST',
          data: wishlistData,
          success: function (data) {
            userDetails.setWishlistCount(data);
            this.context.router.push('/');
            this.setState({ tempState: 'true' });
    
          }.bind(this),
          error: function (xhr, status, err) {
          }.bind(this)
        });
      },
      getSavedPrice: function (aPrice, dPrice) {
        var percentDiff, convertedPrice, diff = (aPrice - dPrice) / aPrice * 100;
        percentDiff = diff.toFixed(0);
        convertedPrice = (aPrice - dPrice);
        convertedPrice = this.addCommas(convertedPrice.toFixed(2));
        return "<span class='save-price'>You Save <span>$" + (convertedPrice + " " + "(" + percentDiff + "%)");
      },
    
      addCommas: function (nStr) {
        nStr += '';
        var x = nStr.split('.');
        var x1 = x[0];
        var x2 = x.length > 1 ? '.' + x[1] : '';
        var rgx = /(\d+)(\d{3})/;
        while (rgx.test(x1)) {
          x1 = x1.replace(rgx, '$1' + ',' + '$2');
        }
        return x1 + x2;
      },
      render() {
        var containerProps = {
          className: 'items-map'
        };
        var route = this.state.route;
        const GettingStartedGoogleMap = withGoogleMap(props => (
          <GoogleMap
            ref={props.onMapLoad}
            zoom={this.state.zoom}
            center={this.state.center}
            onClick={props.onMapClick}
            >
            <Marker
                icon={config.current_location_marker}
                key="100"
                position={this.state.center}
    
                >
              </Marker>
            {this.state.markers.map((marker, index) => (
              <Marker
                icon={config.marker}
                key={index}
                position={marker.position}
                onClick={() => props.onMarkerClick(marker)}
                >
                {marker.showInfo && (
                  <InfoWindow onCloseClick={() => props.onMarkerClose(marker)}>
                    <div dangerouslySetInnerHTML={{ __html: marker.infoContent }}></div>
                  </InfoWindow>
                )}
              </Marker>
            ))}
            {route ?
            <Direction
              directions={route}
              options={{ polylineOptions: { strokeColor: 'green' },suppressMarkers: true}}  />
              :
            null
             }
          </GoogleMap>
        ));
    
    
        var actualPrice = '';
        var discountPrice;
        var price_html = '';
    
        if (this.props.productInfo.discount_price == null) {
          discountPrice = 0;
        }
        if (parseFloat(this.props.productInfo.discount_price) != 0 && parseFloat(this.props.productInfo.actual_price) != 0) {
          if (parseFloat(this.props.productInfo.discount_price) < parseFloat(this.props.productInfo.actual_price)) {
    
            actualPrice = parseFloat(this.props.productInfo.actual_price).toFixed(2)
            actualPrice = this.addCommas(actualPrice);
            discountPrice = parseFloat(this.props.productInfo.discount_price).toFixed(2)
            discountPrice = this.addCommas(discountPrice);
          } else {
    
            actualPrice = parseFloat(this.props.productInfo.actual_price).toFixed(2)
            discountPrice = this.addCommas(actualPrice);
            discountPrice = '';
    
          }
        } else if (this.props.productInfo.discount_price != 0) {
          discountPrice = parseFloat(this.props.productInfo.discount_price).toFixed(2)
          discountPrice = this.addCommas(discountPrice);
          //discountPrice = '';
        } else if (this.props.productInfo.actual_price != 0) {
          actualPrice = parseFloat(this.props.productInfo.actual_price).toFixed(2)
          actualPrice = this.addCommas(actualPrice);
          discountPrice = '';
        }
        if (discountPrice != 0 && discountPrice != '') {
          var savedPrice = this.getSavedPrice(this.props.productInfo.actual_price, this.props.productInfo.discount_price);
        }
    
        return (
    
          <Modal onEnter={this.GetModelInfo} show={this.props.dataFlag === this.state.showModal ? this.props.dataFlag : this.state.showModal} onHide={this.close} bsSize="large" aria-labelledby="contained-modal-title-lg">
            <Modal.Header closeButton>
              <span onClick={this.close} data-dismiss="modal" aria-label="Close" className="visible-xs glyphicon arrow-left-css glyphicon-arrow-left"></span>
            </Modal.Header>
            <a onClick={this.close} className="hidden-xs prod-details-close"><img className="prod-details-close-img" src={require('../images/close_24.png')} /></a>
            <Modal.Body>
    
              <div>
                {!this.state.getParent ?
                  <div>
                    <div>
                      {this.state.acoordianData ?
                        <div id="map-canvas">
    
                          <GettingStartedGoogleMap
                            containerElement={
                              <div style={{ height: `100%` }} />
                            }
                            mapElement={
                              <div style={{ height: `100%` }} />
                            }
                            center={this.state.center}
                            markers={this.state.markers}
                            onMarkerClick={this.handleMarkerClick}
                            onMarkerClose={this.handleMarkerClose}
                            />
    
                        </div>
                        : null}
                      <div className="container-fluid">
                        <div className="row">
                          <div className="hidden-xs col-sm-12 col-md-3 col-lg-3 product-store-locator-div">
                            <div className="stiky-store-locator-div filter">
                              <div className="store-locator-section-title">
                                <span>Store Locator</span>
                              </div>
                              <hr className="store-locator-title-hr" />
    
    
                            </div>
                          </div>
                          <div className="col-sm-12 col-md-9 col-lg-9 product-details-div">
                            <div className="product-details-top-section">
                              <div className="row breadcrum-div">
                                <div className="col-xs-12 col-sm-12 col-md-12 col-lg-12 breadcrumbs-title">
                                </div>
                              </div>
                            </div>
    
                            <div className="row product-details-row-div">
                              <div className="col-xs-6 padding-0 col-sm-5 col-md-5 col-lg-4">
                                <div className="product-details-image-display-div center-block">
                                  <div className="dummy-placeholder"></div>
                                  <div className="product-details-display-section-style center-block">
                                    <img src={this.props.productInfo.product_image} className="img-responsive" alt="product image" />
                                  </div>
                                </div>
                              </div>
                              <div className="col-xs-6 col-sm-7 col-md-7 col-lg-5">
                                <div className="product-details-outer-div" >
                                  <div className="product-details-top-div">
                                    <div className="product-brand-name-div">
                                      <div className="product-details-brand-name">{this.props.productInfo.brand} </div>
                                      <div className="product-details-product-name">{this.props.productInfo.product_name}</div>
                                    </div>
                                    <div className="product-details-store-name">{this.props.productInfo.store_name}</div>
                                    {discountPrice ?
                                      <div className="old-price">
                                        <del className="product-listing-details-old-price-div-less" dangerouslySetInnerHTML={{ __html: "$" + actualPrice }}>
    
                                        </del>&nbsp;&nbsp;
                                    <span className="product-listing-details-price-div" dangerouslySetInnerHTML={{ __html: "$" + discountPrice }}>
    
                                        </span>
                                        <div dangerouslySetInnerHTML={{ __html: savedPrice }}></div>
                                      </div>
                                      :
                                      <div className="old-price" dangerouslySetInnerHTML={{ __html: "$" + actualPrice }}></div>
                                    }
                                    <hr className="madal-hr-gray" />
                                    <div className="product-details-description">
                                      <div className="product-details-description-content">
                                        <div className="product-details-description-title">Description</div>
                                        {this.props.productInfo.long_description}
                                      </div>
                                    </div>
                                  </div>
                                  <div className="product-details-bottom-div">
                                    <div className="buy-from-retailer-btn-div">
                                      <a target="_blank" href={this.props.productInfo.retailer_url} >
                                        <button type="button" className="btn buy-from-retailer-btn">
                                          <span className="btn-detail-style">GO TO {this.props.productInfo.store_name}</span>
                                        </button>
                                      </a>
                                    </div>
                                    <div className="add-to-list-btn-div">
                                      <span className="btn add-to-list-btn get-alert-detail-page" ><SaleAlert  {...this.props.productInfo} /></span>
                                    </div>
                                    <div className="button-seprator">&nbsp;</div>
                                    <div className="add-to-list-btn-div">
                                      <button onClick={this.wishListAction} type="button" className="btn add-to-list-btn">
                                        <span className={"add-to-list-btn-span glyphicon " + this.state.wishListChecked}></span>{this.state.mywishlistLable}</button>
                                    </div>
                                  </div>
                                </div>
                              </div>
                              <div className="col-xs-12 col-sm-12 col-md-12 col-lg-3">
    
                              </div>
                            </div>
                          </div>
    
                        </div>
                        <div className="more-from-brand-div">
                          <h1 className="more-from-brand-link">More Like This</h1>
                        </div>
                        <hr className="madal-hr-gray" />
    
                        <div className="row product-listing-row-div" id="relatedProduct-data">
                          {this.getMoreLikeThisHtml(this.state.rawRelated, this)}
    
                        </div>
                      </div>
                    </div>
                  </div>
                  :
                  null
                }
    
    
              </div>
            </Modal.Body>
          </Modal>
        );
      }
    });
    
    export default ProductDetails;