Lightning Web Component Specialist Super badge: Challenge 7 throwing error - lwc

I am facing error in LWC Specialist Superbadge Challenge:7
import { LightningElement, track, wire, api } from 'lwc';
import getBoatsByLocation from '#salesforce/apex/BoatDataService.getBoatsByLocation';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
const LABEL_YOU_ARE_HERE = 'You are here!';
const ICON_STANDARD_USER = 'standard:user';
const ERROR_TITLE = 'Error loading Boats Near Me';
const ERROR_VARIANT = 'error';
export default class BoatsNearMe extends LightningElement {
#api boatTypeId;
#track mapMarkers = [];
#track isLoading = true;
#track isRendered = false;
latitude;
longitude;
#wire(getBoatsByLocation, { latitude: '$latitude', longitude: '$longitude', boatTypeId: '$boatTypeId' })
wiredBoatsJSON({ error, data }) {
if (data) {
this.createMapMarkers(data);
} else if (error) {
this.dispatchEvent(
new ShowToastEvent({
title: ERROR_TITLE,
message: error.body.message,
variant: ERROR_VARIANT
})
);
this.isLoading = false;
}
}
renderedCallback() {
if (this.isRendered == false) {
this.getLocationFromBrowser();
}
this.isRendered = true;
}
getLocationFromBrowser() {
navigator.geolocation.getCurrentPosition(
position => {
this.latitude = position.coords.latitude;
this.longitude = position.coords.longitude;
},
(e) => {
}, {
enableHighAccuracy: true
}
);
}
createMapMarkers(boatData) {
this.mapMarkers = JSON.parse(boatData).map(rowBoat => {
return {
location: {
Latitude: rowBoat.Geolocation__Latitude_s,
Longitude: rowBoat.Geolocation__Longitude_s
},
title: rowBoat.Name,
};
});
this.mapMarkers.unshift({
location: {
Latitude: this.latitude,
Longitude: this.longitude
},
title: LABEL_YOU_ARE_HERE,
icon: ICON_STANDARD_USER
});
this.isLoading = false;
}
}
<template>
<lightning-card class="slds-is-relative">
<template if:true={isLoading}>
<lightning-spinner alternative-text="Loading" size="small" variant="brand"></lightning-spinner>
</template>
<!-- The lightning-map goes here -->
<template if:true={isRendered}>
<lightning-map map-markers={mapMarkers}> </lightning-map>
</template>
<div slot="footer">Top 10 Only!</div>
</lightning-card>
</template>
Challenge Not yet complete... here's what's wrong: We can't find the correct settings for the createMapMarkers() function in the component boatsNearMe JavaScript file. Make sure the component was created according to the requirements, including the right mapMarkers, title, Latitude (Geolocation__Latitude__s), Longitude (Geolocation__Longitude__s), the correct constants, stopping the loading spinner, and using the proper case-sensitivity and consistent quotation.

You have to assign newMarkers to mapMarkers.
newMarkers.unshift({
location: {
Latitude: this.latitude,
Longitude: this.longitude
},
title: LABEL_YOU_ARE_HERE,
icon: ICON_STANDARD_USER
});
this.mapMarkers = newMarkers;
this.isLoading = false;

In createMapMarkers method Double underscore is missing. Try:
Latitude: rowBoat.Geolocation__Latitude__s,
Longitude: rowBoat.Geolocation_Longitude__s

Related

How to render a Drawing manager in a Map or a polygon using "#react-google-maps/api"

I've been trying to render a Map using #react-google-maps/api": "^2.12.1 in my react-hook-form": "^7.33.1. The requirement was to show the map onClick of the input, and the user should draw a polygon on the Map using Drawing Manager.
This is my code, I've been trying to get this working from past two days and I'm nowhere near to the solution. I've gone through stack overflow questions but most of the answers were outdated or I couldn't find anything since most of the people are using react-google-maps where it has been completely redesigned to #react-google-maps/api": "^2.12.1.
The weird thing is the exact code is working in codesandbox. I don't know what I'm
doing wrong here.
import React from "react";
import ReactDOM from "react-dom";
import { LoadScript, GoogleMap, DrawingManager } from "#react-google-maps/api";
const containerStyle = {
width: "400px",
height: "400px",
};
const API_KEY = "";
export default function GoogleMaps() {
const [state, setState] = React.useState({
drawingMode: "polygon",
});
const noDraw = () => {
setState(function set(prevState) {
return Object.assign({}, prevState, {
drawingMode: "maker",
});
});
};
return (
<div className="App">
<LoadScript
id="script-loader"
googleMapsApiKey={API_KEY}
libraries={["drawing"]}
language="en"
region="us"
>
<GoogleMap
mapContainerClassName={containerStyle}
center={{
lat: 38.9065495,
lng: -77.0518192,
}}
zoom={10}
version="weekly"
>
<DrawingManager
drawingMode={state.drawingMode}
options={{
drawingControl: true,
drawingControlOptions: {
drawingModes: ["polygon"],
},
polygonOptions: {
fillColor: `#2196F3`,
strokeColor: `#2196F3`,
fillOpacity: 0.5,
strokeWeight: 2,
clickable: true,
editable: true,
draggable: true,
zIndex: 1,
},
}}
onPolygonComplete={(poly) => {
/*const polyArray = poly.getPath().getArray();
let paths = [];
polyArray.forEach(function(path) {
paths.push({ latitude: path.lat(), longitude: path.lng() });
});
console.log("onPolygonComplete", polyArray);*/
console.log("onPolygonComplete", poly);
noDraw();
}}
/*onOverlayComplete={poly => {
const polyArray = poly.getPath().getArray();
let paths = [];
polyArray.forEach(function(path) {
paths.push({ latitude: path.lat(), longitude: path.lng() });
});
console.log("onOverlayComplete", polyArray);
}}*/
/>
</GoogleMap>
</LoadScript>
</div>
);
}

Rendering a menu in vue 3 after ajax method

I've gotten this menu to work without filtering it, but now I'm doing an ajax request to filter out menu items the user isn't supposed to see, and I'm having some trouble to figure out how to set the resulting menu data, the line that is not working is commented below:
<script>
import { ref } from 'vue';
import axios from 'axios';
var currentSelected = 'device_access';
var menuData = [
{
text: 'Device Access',
id: 'device_access',
children: [
{
text: 'Interactive',
link: '/connection_center'
},{
text: 'Reservation',
link: '/reserve_probe'
}, {
text: 'Reservation Vue',
link: '/reservation.html'
}
]
}, {
text: 'Automation',
id: 'automation',
show: ['is_mxadmin', 'can_schedule_scripts'],
children: [
{
text: 'Builder',
link: '/builder',
},{
text: 'Execution Results',
link: '/test_suite_execution_results'
},
]
}
];
function hasMatch(props, list) {
var match = false;
for (var i=0; i < list.length && !match; i++) {
match = props[list[i]];
}
return match;
}
export default {
name: 'Header',
setup() {
const cursorPosition = ref('0px');
const cursorWidth = ref('0px');
const cursorVisible = ref('visible');
//the menu is zero length until I get the data:
const menu = ref([]);
return {
menu,
cursorPosition,
cursorWidth,
cursorVisible
}
},
created() {
let that = this;
axios.get('navigation_props')
.then(function(res) {
var data = res.data;
var result = [];
menuData.forEach(function(item) {
if (!item.show || hasMatch(data, item.show)) {
var children = [];
item.children.forEach(function (child) {
if (!child.show || hasMatch(data, child.show)) {
children.push({ text: child.text, link: child.link });
}
});
if (children.length > 0) {
result.push({ text: item.text,
children: children, lengthClass: "length_" + children.length });
}
}
});
//continues after comment
this is probably the only thing wrong, I've run this in the debugger and I'm getting the
correct data:
that.$refs.menu = result;
since the menu is not being rebuilt, then this fails:
//this.restoreCursor();
})
.catch(error => {
console.log(error)
// Manage errors if found any
});
},
this.$refs is for template refs, which are not the same as the refs from setup().
And the data fetching in created() should probably be moved to onMounted() in setup(), where the axios.get() callback sets menu.value with the results:
import { onMounted, ref } from 'vue'
export default {
setup() {
const menu = ref([])
onMounted(() => {
axios.get(/*...*/).then(res => {
const results = /* massage res.data */
menu.value = results
})
})
return {
menu
}
}
}
I finally figured out the problem.
This code above will probably work with:
that.menu = result;
You don't need: that.$refs.menu
You can't do it in setup because for some reason "that" is not yet defined.
In my working code I added a new method:
methods: {
setMenuData: function() {
this.menu = filterMenu();
},
}
And "this" is properly defined inside them.

Update google marker with watchposition in ionic

I am creating a map to track user position, i am using #geolocation plugin and showing map using:
<script src="https://maps.googleapis.com/maps/api/js?key=API_KEYscript>
Currently marker is being displayed and on watchposition marker is placing multiple times and isn’t removing last placed marker.
import { Component, ViewChild, ElementRef } from '#angular/core';
import { Geolocation, GeolocationOptions, Geoposition, PositionError } from '#ionic-native/geolocation';
declare var google;
#Component({
selector: 'app-tab1',
templateUrl: 'tab1.page.html',
styleUrls: ['tab1.page.scss']
})
export class Tab1Page {
geolocation = Geolocation;
options: GeolocationOptions;
currentPos: Geoposition;
#ViewChild('map', { static: false }) mapElement: ElementRef;
map: any;
constructor() { }
ionViewDidEnter() {
this.getUserPosition();
}
getUserPosition() {
this.options = {
enableHighAccuracy: false
};
//get location
this.geolocation.getCurrentPosition(this.options).then((pos: Geoposition) => {
this.currentPos = pos;
console.log(pos);
//add map
let latLng = new google.maps.LatLng(pos.coords.latitude, pos.coords.longitude);
let mapOptions = {
center: latLng,
zoom: 18,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
this.map = new google.maps.Map(this.mapElement.nativeElement, mapOptions);
this.addMarker(latLng);
}, (err: PositionError) => {
console.log("error : " + err.message);
})
let subscription = this.geolocation.watchPosition().subscribe(position =>{
let userPosition = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
this.addMarker(userPosition);
})
}
addMarker(position) {
let marker = new google.maps.Marker({
map: this.map,
animation: google.maps.Animation.DROP,
position: position
});
let content = "<p>This is your current position !</p>";
let infoWindow = new google.maps.InfoWindow({
content: content
});
google.maps.event.addListener(marker, 'click', () => {
infoWindow.open(this.map, marker);
});
}
}
I want to remove last marker before placing another one.
Thank you.

after successfull payment, paypal throw error - No Response from window. cleaned up

I'm implementing Paypal payment gateway with React. I'm following a a tutorial from robinwieruch.de.
I'm able to complete payment procedure, but after payment completed it throws no response from window. cleaned up. in firefox my app crashes, but in chrome, it only logs the error to console(no crashing).
Cart.js
import Paypal from '../common/Paypal';
const imgStyle = {
width: `150px`,
height: `60px`
}
class Cart extends Component{
state = {
courierClass: ''
}
isValidPayment = (e)=>{
e.preventDefault();
if(!this.props.isAuthenticated){
this.props.errorHandler('please signin to continue');
this.props.history.push('/user/signin');
}
else {
return true;
}
}
addOrders = (payment) => {
this.props.successHandler('payment successful. we are processing you order request');
this.setState({
isSubmitting: true
})
this.props.orderHandler(send order data to server)
.then(()=>{
this.setState({
isSubmitting: false
})
this.props.history.push('/')
})
.catch(()=> this.setState({
isSubmitting: false
}))
}
onPaymentCancel = (data) => {
this.props.errorHandler('you cancel the payment');
console.log('The payment was cancelled!', data);
}
onPaymentError = (err) => {
console.log(err, 'error');
this.props.errorHandler(`we can't process your payment now.`)
}
render(){
let { isAuthenticated,} = this.props;
let {courierClass, isSubmitting} = this.state;
let totalPrice = 0;
cart.items.forEach(({item, item_quantity})=>{
totalPrice += (item.price * item_quantity)
})
let env = 'sandbox';
let currency = 'GBP';
let style={
shape: 'rect',
size: 'medium',
label: 'checkout',
tagline: false
}
// let total = totalPrice;
const client = {
sandbox: process.env.REACT_APP_SANDBOX_APP_ID,
}
return (
<div className="container">
{ courierClass ? <Paypal
env={env}
client={client}
currency={currency}
total={totalPrice}
style={style}
onClick={this.isValidPayment}
onError={this.onPaymentError}
onSuccess={this.addOrders}
onCancel={this.onPaymentCancel} /> : null
}
</div>
)
}
}
function mapStateToProps(state){
return {
isAuthenticated: state.currentUser.isAuthenticated
}
}
export default connect(mapStateToProps, {})(Cart)
** Paypal.js **
import React from 'react';
import ReactDOM from 'react-dom';
import scriptLoader from 'react-async-script-loader';
const {NODE_ENV, REACT_APP_SANDBOX_APP_ID, REACT_APP_PAYPAL_APP_ID } = process.env
class Paypal extends React.Component {
constructor(props) {
super(props);
this.state = {
showButton: false,
};
window.React = React;
window.ReactDOM = ReactDOM;
}
componentDidMount() {
const { isScriptLoaded, isScriptLoadSucceed } = this.props;
if (isScriptLoaded && isScriptLoadSucceed) {
this.setState({ showButton: true });
}
}
componentWillReceiveProps(nextProps) {
const { isScriptLoaded, isScriptLoadSucceed } = nextProps;
const isLoadedButWasntLoadedBefore = !this.state.showButton && !this.props.isScriptLoaded && isScriptLoaded;
if (isLoadedButWasntLoadedBefore) {
if (isScriptLoadSucceed) {
this.setState({ showButton: true });
}
}
}
componentWillUnmount(){
// this.setState({
// showButton: false
// });
// this.paypal = null
}
render() {
const paypal = window.PAYPAL;
const { total, commit, onSuccess, onError, onCancel,} = this.props;
const { showButton} = this.state;
// let style = {
// size: 'small',
// color: 'gold',
// shape: 'pill',
// label: 'checkout: Just $2.99!'
// }
let env = NODE_ENV === 'production' ? 'live' : 'sandbox' ;
let currency = 'GBP';
const client = {
sandbox: REACT_APP_SANDBOX_APP_ID,
live: REACT_APP_PAYPAL_APP_ID
}
const payment = () =>
paypal.rest.payment.create(env, client, {
transactions: [
{
amount: {
total,
currency,
}
},
]}
);
const onAuthorize = (data, actions) =>
actions.payment.execute()
.then(() => {
const payment = {
paid: true,
cancelled: false,
payerID: data.payerID,
paymentID: data.paymentID,
paymentToken: data.paymentToken,
returnUrl: data.returnUrl,
};
onSuccess(payment);
});
return (
showButton ? <paypal.Button.react
env={env}
client={client}
commit={commit}
payment={payment}
onAuthorize={onAuthorize}
onCancel={onCancel}
onError={onError}
style={{
shape: 'rect',
size: 'medium',
label: 'pay',
tagline: false
}}
/> : null
);
}
}
export default scriptLoader('https://www.paypalobjects.com/api/checkout.js')(Paypal);
I'm using this Paypal.js in other Components also, the same issue is there also.
please help. it's become a headache for me now and i have to deploy this today.
I also search for it on paypal github page, but they said that they resolved the issue paypal issue.
do i need to update the payapl as documented at paypal docs
Update
my payment.returnUrl has error https://www.paypal.com/checkoutnow/error?paymentId=PAYID-syufsaddhew&token=EC-shdgasdPayerID=dasgda.
i setup the return url to localhost

Angular 2 Reactive form + directive validation

I'm trying to wrap my head around the following problem:
I have a 'google-place-autocomplete' directive that adds the autocomplete functionality to an input field.
Now I also wanted it to be able to force a google place selection and only be 'valid' if the user has selected a place.
E.g:
#Directive({
selector: '[googlePlace][formControlName], [googlePlace][ngModel]',
providers: [{provide: NG_VALIDATORS, useExisting: GooglePlaceDirective, multi: true}]
})
export class GooglePlaceDirective implements Validator, OnChanges {
valid = false;
#Output() googlePlaceAddressChange: any = new EventEmitter();
#Input() googlePlaceAddress: any;
#Output() ngModelChange: any = new EventEmitter();
private autocomplete: any;
constructor(private googleMapService: GoogleMapsService,
private element: ElementRef,
private zone: NgZone) {
}
ngOnInit() {
let self = this;
this.googleMapService
.load()
.subscribe(
() => {
this.autocomplete = new google.maps.places.Autocomplete(this.element.nativeElement);
this.autocomplete.addListener('place_changed', function () {
self.placeChanged(this.getPlace());
});
}
);
}
private placeChanged(place) {
this.zone.run(() => {
this.googlePlaceAddress = {
address: this.element.nativeElement.value,
formattedAddress: place.formatted_address,
latitude: place.geometry.location.lat(),
longitude: place.geometry.location.lng()
};
this.valid = true;
this.googlePlaceAddressChange.emit(this.googlePlaceAddress);
this.ngModelChange.emit(this.element.nativeElement.value);
});
}
ngOnChanges(changes): void {
let googlePlaceDefined = typeof (changes.googlePlaceAddress) !== 'undefined';
let modelDefined = typeof (changes.ngModel) !== 'undefined';
if(modelDefined && !googlePlaceDefined) {
this.valid = false;
} else if(googlePlaceDefined && !modelDefined) {
this.valid = true;
}
}
validate(control: AbstractControl) {
return this.valid === false ? {'googlePlaceAddress': true} : null;
}
}
If I use this directive in an template driven form:
...
<input name="addr" type="text" [(ngModel)]="textValue" [(googlePlaceAddress)]="googleAddress" required>
<p *ngIf="addr.errors.googlePlaceAddress">Please select a proposed address</p>
...
it works fine.
Now I need to use this in an Reactive Form using FormGroup
let groups = [
new FormControl('', [Validators.required])
];
/** HTML **/
...
<input [id]="addr"
[formControlName]="address"
class="form-control"
type="text"
googlePlace
[placeholder]="question.label"
[(googlePlaceAddress)]="googleAddress">
...
However in this case the validation from the directive is never triggered.
I suppose angular2 expects it to be given through, when using Reactive Forms:
new FormControl('', [Validators.required, ???])
I must have taken a wrong path somewhere.
For future reference:
I solved my problem creating a component out of it together with a Value accessor:
#Component({
selector: 'app-google-place',
templateUrl: './google-place.component.html',
styleUrls: ['./google-place.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => GooglePlaceComponent),
multi: true
}
]
})
export class GooglePlaceComponent implements OnInit, ControlValueAccessor {
#ViewChild('inputElement') inputElement: ElementRef;
#Input() public placeholder: string = "Address";
#Input() public textValue: string = "";
private autocomplete: any;
private _place = null;
constructor(
private googleMapService: GoogleMapsService,
private zone: NgZone
) {
}
ngOnInit() {
this.googleMapService
.load()
.subscribe(
() => {
this.autocomplete = new google.maps.places.Autocomplete(this.inputElement.nativeElement);
this.autocomplete.addListener('place_changed', () => this.placeChanged());
}
);
}
placeChanged() {
this.zone.run(() => {
let place = this.autocomplete.getPlace();
this._place = {
address: this.inputElement.nativeElement.value,
formattedAddress: place.formatted_address,
latitude: place.geometry.location.lat(),
longitude: place.geometry.location.lng()
};
this.propagateChange(this._place);
});
}
onNgModelChange($event) {
if(this._place !== null) {
if(this._place.address !== $event) {
this._place = null;
this.propagateChange(this._place);
}
}
}
onBlur() {
this.propagateTouched();
}
writeValue(obj: any): void {
if(obj !== undefined) {
this._place = obj;
}
}
propagateChange = (_: any) => {};
registerOnChange(fn) {
this.propagateChange = fn;
}
propagateTouched = () => {};
registerOnTouched(fn: any): void {
this.propagateTouched = fn;
}
}
Using this I can use it in a FormGroup with the Validators.required and it will only be valid if a user has selected a google place.
EDIT
The html:
<input type="text"
(blur)="onBlur()"
#inputElement
class="form-control"
[(ngModel)]="textValue"
(ngModelChange)="onNgModelChange($event)">
The service:
import {Injectable} from '#angular/core';
import {Subject} from 'rxjs/Subject';
import {Observable} from 'rxjs/Observable';
#Injectable()
export class GoogleMapsService {
private key = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
private loaded = false;
private currentRequest = null;
constructor() {
}
load() {
if (this.loaded) {
return Observable.create((observer) => {
observer.next();
observer.complete();
});
}
if (this.currentRequest === null) {
//http://reactivex.io/rxjs/manual/overview.html#multicasted-observables
const source = Observable.create((observer) => {
this.loadMaps(observer);
});
const subject = new Subject();
this.currentRequest = source.multicast(subject);
this.currentRequest.connect();
}
return this.currentRequest;
}
private loadMaps(observer: any) {
const script: any = document.createElement('script');
script.src = 'https://maps.googleapis.com/maps/api/js?key=' + this.key + '&libraries=places';
if (script.readyState) { // IE, incl. IE9
script.onreadystatechange = () => {
if (script.readyState == 'loaded' || script.readyState == 'complete') {
script.onreadystatechange = null;
this.loaded = true;
observer.next();
observer.complete();
this.currentRequest = null;
}
};
} else {
script.onload = () => { // Other browsers
this.loaded = true;
observer.next();
observer.complete();
this.currentRequest = null;
};
}
script.onerror = () => {
observer.error('Unable to load');
this.currentRequest = null;
};
document.getElementsByTagName('head')[0].appendChild(script);
}
}
The 'usage':
With template ngModel
<app-google-place ([ngModel)]="place"></app-google-place>