Building Weather and Air Quality Check app using LWC and Weatherbit.io api

Use Case:

Sales rep want to see the current weather condition and air quality of particular city or particular pin code


Pre-requisite: 

Signed up a free version from weatherbit.io to get the connection key. 
Create a Named Credential in salesforce org to store the URL which we need to perform the callout.



Implementation:

Apex class:  

Method 1: airQualityCallOut
To get the values of Air Quality from weatherbit api

Method 2: weatherCallOut
To get the values of weather details from weatherbit api

global class WeatherAPIService {
@AuraEnabled (cacheable = true)
global static airQuality airQualityCallOut(String location){
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:WeatherApi/airquality?city=' + location + '&key=222f5227192e4be69aacb6c8deb11337');
req.setMethod('GET');
Http http = new Http();
HTTPResponse res = http.send(req);
JSONParser parser = JSON.createParser(res.getBody());
airQuality airQ = new airQuality();
while (parser.nextToken() != null) {
if(parser.getCurrentToken() == JSONToken.FIELD_NAME) {
parser.nextValue();
switch on parser.getCurrentName() {
when 'aqi' {
airQ.aqi = parser.getText();
}
when 'o3' {
airQ.o3 = Decimal.valueOf(parser.getText());
}
when 'so2' {
airQ.so2 = Decimal.valueOf(parser.getText());
}
when 'no2' {
airQ.no2 = Decimal.valueOf(parser.getText());
}
when 'co' {
airQ.co = Decimal.valueOf(parser.getText());
}
}
}
}
system.debug('airQ'+airQ);
return airQ;
}
@AuraEnabled (cacheable=true)
global static WeatherData weatherCallout(String location,String postalCode) {
HttpRequest req = new HttpRequest();
if (location != ''){
req.setEndpoint('callout:WeatherApi?city=' + location + '&key=222f5227192e4be69aacb6c8deb11337');
}
if (postalCode != ''){
req.setEndpoint('callout:WeatherApi?postal_code=' + postalCode + '&key=222f5227192e4be69aacb6c8deb11337');
}
req.setMethod('GET');
Http http = new Http();
HTTPResponse res = http.send(req);
JSONParser parser = JSON.createParser(res.getBody());
WeatherData weather = new WeatherData();
while (parser.nextToken() != null) {
if(parser.getCurrentToken() == JSONToken.FIELD_NAME) {
parser.nextValue();
system.debug('---->'+parser.getCurrentName() );
switch on parser.getCurrentName() {
when 'temp' {
weather.cityTemp = Decimal.valueOf(parser.getText());
}
when 'city_name' {
weather.cityName = parser.getText();
}
when 'state_code' {
weather.state = parser.getText();
}
when 'timezone' {
weather.cityTimeZone = parser.getText();
}
when 'wind_spd' {
weather.cityWindSpeed = Decimal.valueOf(parser.getText());
}
when 'lon' {
weather.longitude = parser.getText();
}
when 'lat' {
weather.latitude = parser.getText();
}
when 'sunrise' {
weather.sunRise = parser.getText() ;
}
when 'sunset' {
weather.sunSet = parser.getText() ;
}
}
}
}
return weather;
}
global class WeatherData {
@AuraEnabled public String cityName;
@AuraEnabled public String cityTimeZone;
@AuraEnabled public Decimal cityTemp;
@AuraEnabled public String sunRise;
@AuraEnabled public String sunSet;
@AuraEnabled public String state;
@AuraEnabled public Decimal cityWindSpeed;
@AuraEnabled public String latitude;
@AuraEnabled public String longitude;
}
global class airQuality{
@AuraEnabled public String aqi;
@AuraEnabled public Decimal o3;
@AuraEnabled public Decimal so2;
@AuraEnabled public Decimal no2;
@AuraEnabled public Decimal co;
}
}

Lightning web component: weatherForecasting

Here there are two ways to get the weather report based on selected city or based on selected pin code. In html there are two input tags for city and postal code.

Then there are buttons, one is for to check the air quality and another one is for to check the air quality. After clicking the button, just calling the appropriate callout methods using on click event.


weatherForecasting.html

<template>
<lightning-card title={cityName}>
<lightning-layout>
<lightning-layout-item size="3" medium-device-size="4" padding="around-small">

<lightning-input type="text" value={cityValue} label="City Name" onchange={handleChange}>
</lightning-input>

<lightning-input type="text" value={postalValue} label="Postal code" onchange={handleChange}>
</lightning-input>


<lightning-button label="Weather check" onclick={weatherCheck}></lightning-button>
<lightning-button label="Air Quality" onclick={airQualityCheck}></lightning-button>

</lightning-layout-item>
</lightning-layout>

<template if:true={airQualityChk}>
<lightning-layout>

<lightning-layout-item size="5" medium-device-size="4" padding="around-small">
<lightning-card icon-name="standard:topic" title="Air Quality Index">
<div class="slds-align_absolute-center color-blue">
<template if:true={result}>
{aqi}
</template>
</div>
</lightning-card>
</lightning-layout-item>

<lightning-layout-item size="5" medium-device-size="4" padding="around-small">
<lightning-card icon-name="standard:topic" title="O3 Concentration">
<div class="slds-align_absolute-center color-blue">
<template if:true={result}>
{o3}
</template>
</div>
</lightning-card>
</lightning-layout-item>

<lightning-layout-item size="5" medium-device-size="4" padding="around-small">
<lightning-card icon-name="standard:topic" title="SO2 Concentration">
<div class="slds-align_absolute-center color-blue">
<template if:true={result}>
{so2}
</template>
</div>
</lightning-card>
</lightning-layout-item>

<lightning-layout-item size="5" medium-device-size="4" padding="around-small">

<lightning-card icon-name="standard:topic" title="NO2 Concentration">
<div class="slds-align_absolute-center color-blue">
<template if:true={result}>
{no2}
</template>
</div>
</lightning-card>
</lightning-layout-item>

<lightning-layout-item size="5" medium-device-size="4" padding="around-small">

<lightning-card icon-name="standard:topic" title="CO Concentration">
<div class="slds-align_absolute-center color-blue">
<template if:true={result}>
{co}
</template>
</div>
</lightning-card>
</lightning-layout-item>
</lightning-layout>

</template>

<template if:true={weatherchk}>

<lightning-layout>
<lightning-layout-item size="3" medium-device-size="4" padding="around-small">
<lightning-card icon-name="standard:topic" title="Temperature">
<div class="slds-align_absolute-center color-blue">
<template if:true={result}>
{temperature}
</template>
</div>
</lightning-card>
</lightning-layout-item>

<lightning-layout-item size="3" medium-device-size="4" padding="around-small">
<lightning-card icon-name="standard:topic" title="Sun Rise">
<div class="slds-align_absolute-center color-blue">
<template if:true={result}>
<lightning-formatted-time value={sunRise}></lightning-formatted-time>

</template>
</div>
</lightning-card>
</lightning-layout-item>

<lightning-layout-item size="3" medium-device-size="4" padding="around-small">
<lightning-card icon-name="standard:topic" title="Sun set">
<div class="slds-align_absolute-center color-blue">
<template if:true={result}>

<lightning-formatted-time value={sunSet}></lightning-formatted-time>
</template>
</div>
</lightning-card>
</lightning-layout-item>

</lightning-layout>

<lightning-layout>
<lightning-layout-item size="3" medium-device-size="4" padding="around-small">
<lightning-card icon-name="utility:flow" title="Current Wind Speed">
<div class="slds-align_absolute-center color-grey">
<template if:true={result}>
{currentWindSpeed}
</template>
</div>
</lightning-card>
</lightning-layout-item>

<lightning-layout-item size="3" medium-device-size="4" padding="around-small">
<lightning-card icon-name="utility:flow" title="Latitude">
<div class="slds-align_absolute-center color-grey">
<template if:true={result}>
{latitude}
</template>
</div>
</lightning-card>
</lightning-layout-item>

<lightning-layout-item size="3" medium-device-size="4" padding="around-small">
<lightning-card icon-name="utility:flow" title="Longitude">
<div class="slds-align_absolute-center color-grey">
<template if:true={result}>
{longitude}
</template>
</div>
</lightning-card>
</lightning-layout-item>

</lightning-layout>
</template>


<lightning-map map-markers={mapMarkers} zoom-level={zoomLevel}>
</lightning-map>
</lightning-card>
</template>


weatherForecasting.js

import { LightningElement, track } from 'lwc';
import weatherCallout from '@salesforce/apex/WeatherAPIService.weatherCallout';
import airQualityCallOut from '@salesforce/apex/WeatherAPIService.airQualityCallOut';

export default class WeatherDataLWC extends LightningElement {

@track lat;
@track lon;
@track mapMarkers = [];
zoomLevel = 10;
@track result;
cityValue = 'chennai';
postalValue ='';
cityName;
sunSet;
sunRise;
temperature;
currentWindSpeed;
latitude;
longitude;
aqi;
o3;
so2;
no2;
co;
airQualityChk = false;
weatherchk =false;

handleChange(event) {
if (event.target.label === 'City Name'){
this.cityValue = event.target.value;
}
if (event.target.label === 'Postal code'){
this.postalValue = event.target.value;
}
}

/* Method to check the air quality */
airQualityCheck (event){
airQualityCallOut({ location: this.cityValue })
.then(data => {
this.result = data;
if (this.result) {
this.airQualityChk = true;
this.aqi = this.result.aqi ;
this.o3 = this.result.o3;
this.so2 = this.result.so2;
this.no2 = this.result.no2;
this.co = this.result.co;
}
}).catch(err => console.log(err));

}

/* Method to check weather */
weatherCheck(event){
weatherCallout({ location: this.cityValue ,postalCode:this.postalValue })
.then(data => {

//To assign the longitude and latitude to map
this.mapMarkers = [{
location: {
Latitude: data['latitude'],
Longitude: data['longitude']
},
title: data['cityName'] + ', ' + data['state'],
}];
this.result = data;

if (this.result) {
this.weatherchk =true
this.cityName = this.result.cityName + ' Information';
this.sunSet = this.result.sunSet ;
this.sunRise = this.result.sunRise ;
this.temperature = this.result.cityTemp;
this.currentWindSpeed = this.result.cityWindSpeed;
this.latitude = this.result.latitude ;
this.longitude = this.result.longitude;
}
}).catch(err => console.log(err));

}

}



weatherForecasting.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>48.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__HomePage</target>
<target>lightning__Tab</target>
</targets>
</LightningComponentBundle>


GitHub :


Demo: 



Kanban Using Lightning Web Components

In this post, we will see how to implement Kanban view using Lightning web component 

Use Case:

Business has a requirement to change the case status using Kanban view. As developer we should implement this functionality by using Lightning web component in drag and drop approach .


Pre-requisite: 

pubsub component for event communication using publisher-subscribe pattern.

/**
* A basic pub-sub mechanism for sibling component communication
*
* TODO - adopt standard flexipage sibling communication mechanism when it's available.
*/
const events = {};
const samePageRef = (pageRef1, pageRef2) => {
const obj1 = pageRef1.attributes;
const obj2 = pageRef2.attributes;
return Object.keys(obj1)
.concat(Object.keys(obj2))
.every(key => {
return obj1[key] === obj2[key];
});
};
/**
* Registers a callback for an event
* @param {string} eventName - Name of the event to listen for.
* @param {function} callback - Function to invoke when said event is fired.
* @param {object} thisArg - The value to be passed as the this parameter to the callback function is bound.
*/
const registerListener = (eventName, callback, thisArg) => {
// Checking that the listener has a pageRef property. We rely on that property for
filtering purpose in fireEvent()
if (!thisArg.pageRef) {
throw new Error(
'pubsub listeners need a "@wire(CurrentPageReference) pageRef" property'
);
}
if (!events[eventName]) {
events[eventName] = [];
}
const duplicate = events[eventName].find(listener => {
return listener.callback === callback && listener.thisArg === thisArg;
});
if (!duplicate) {
events[eventName].push({ callback, thisArg });
}
};
/**
* Unregisters a callback for an event
* @param {string} eventName - Name of the event to unregister from.
* @param {function} callback - Function to unregister.
* @param {object} thisArg - The value to be passed as the this parameter to the callback function is bound.
*/
const unregisterListener = (eventName, callback, thisArg) => {
if (events[eventName]) {
events[eventName] = events[eventName].filter(
listener => listener.callback !== callback || listener.thisArg !== thisArg
);
}
};
/**
* Unregisters all event listeners bound to an object.
* @param {object} thisArg - All the callbacks bound to this object will be removed.
*/
const unregisterAllListeners = thisArg => {
Object.keys(events).forEach(eventName => {
events[eventName] = events[eventName].filter(
listener => listener.thisArg !== thisArg
);
});
};
/**
* Fires an event to listeners.
* @param {object} pageRef - Reference of the page that represents the event scope.
* @param {string} eventName - Name of the event to fire.
* @param {*} payload - Payload of the event to fire.
*/
const fireEvent = (pageRef, eventName, payload) => {
if (events[eventName]) {
const listeners = events[eventName];
listeners.forEach(listener => {
if (samePageRef(pageRef, listener.thisArg.pageRef)) {
try {
listener.callback.call(listener.thisArg, payload);
} catch (error) {
// fail silently
}
}
});
}
};
export {
registerListener,
unregisterListener,
unregisterAllListeners,
fireEvent
};

Apex class : kanbanUtilityClass


public class kanbanUtilityClass{
    @AuraEnabled(Cacheable = true)
 public static List<SObject> findRecords(String searchKey, String objectName,
String searchField, String columns){
        if (searchKey != ''){
            String key = '%' + searchKey + '%';
            String QUERY = 'SELECT Id, ' + columns + ' FROM ' + objectName +
' WHERE ' + searchField + ' LIKE :key' + ' ORDER BY ' + searchField;
            List<SObject> sObjectList = Database.query(QUERY);
            return sObjectList;
        } else{
            return null;
        }
    }
}

 Parent Component: kanbanView Component

 kanbanView.html

 This is the parent component which contain three child components. Each child component will refer  the different status of cases.


<template>
<div class="slds-grid slds-gutters">
<div class="slds-col">
<c-kanban-list-l-w-c class="New" onrefresh={refreshComp} object-name="Case" field-name="Status"
search-key="New" columns="Subject,Status,CaseNumber,Priority,Origin">
</c-kanban-list-l-w-c>
</div>
<div class="slds-col">
<c-kanban-list-l-w-c class="Working" onrefresh={refreshComp} object-name="Case" field-name="Status"
search-key="Working" columns="Subject,Status,CaseNumber,Priority,Origin">
</c-kanban-list-l-w-c>
</div>
<div class="slds-col">
<c-kanban-list-l-w-c class="Closed" onrefresh={refreshComp} object-name="Case" field-name="Status"
search-key="Closed" columns="Subject,Status,CaseNumber,Priority,Origin">
</c-kanban-list-l-w-c>
</div>
</div>
</template>

kanbanView.js

There is one js method refreshcomp, to refresh the child component . This method will fire automatically, if user changing the status using drag and drop. For this scenario, using child to parent event communication .

import { LightningElement } from 'lwc';

export default class KanbanViewLWC extends LightningElement {

refreshComp() {
this.template.querySelector(".New").refreshMethod();
this.template.querySelector(".Working").refreshMethod();
this.template.querySelector(".Closed").refreshMethod();
}
}

 

kanbanView.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>48.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__HomePage</target>
<target>lightning__Tab</target>
</targets>
</LightningComponentBundle>

 

ChildComponent: kanbanList Component

This is the child component which is having all the logic of drag & drop and updating the records.

kanbanList.html

To make the div as draggable one , added draggable=true and ondragstart={handleDragStart} event handler has been used.


<template>
<lightning-card class="" title={searchKey}>
<div class="slds-grid ">
<lightning-layout>
<lightning-layout-item padding="around-small">
<template for:each={data} for:item="item" for:index="indexVar">
<div draggable="true" key={item.Id} data-item={item.Id} ondragstart={handleDragStart}>
<div class="slds-text-heading_small slds-box">
<strong> case Number : </strong> {item.CaseNumber} <br />
<strong> Subject : </strong> {item.Subject} <br />
<strong> Origin : </strong> {item.Origin} <br />
<strong> Priority : </strong> {item.Priority} <br />
</div> <br />
</div>
</template>
</lightning-layout-item>
</lightning-layout>
</div>
</lightning-card>
</template>


kanbanList.js

In js only have implemented all the logic's.  Some important points highlighted as below.

  •      Importing api ,wire ,track variables
  •      Importing the apex class method KanbanUtilityClass.findRecords to return the list
  •      Importing the methods fireEvent, registerListener, unregisterAllListeners from pub sub     component
  •       Importing update record from uiRecordApi to update the record
  •      Importing the ShowToastEvent to display the Success and error messages
  •      Importing refreshApex to refresh the js list
  •    Importing handleDragStart ,get the selected record id and fire the event to subscribers.
  •    handleDropMethod will listen the event which is fired from method handleDragStart, once it listen the event, using uiRecordApi will update the record to appropriate status. 
  •    Once status updated, dispatch the child to parent event (refresh) to refresh the all the child components.


import { LightningElement, api, wire, track } from 'lwc';
import findRecords from '@salesforce/apex/kanbanUtilityClass.findRecords';
import { CurrentPageReference } from 'lightning/navigation';
import { fireEvent } from 'c/pubsub';
import { registerListener, unregisterAllListeners } from 'c/pubsub';
import { updateRecord } from 'lightning/uiRecordApi';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { refreshApex } from '@salesforce/apex';

export default class KanbanListLWC extends LightningElement {

@api objectName;
@api fieldName;
@api searchKey;
@api columns;
@track data;
@track refreshTable;
@track dragRecordId;
@track updateRecord;

@wire(CurrentPageReference) pageRef;

// retrieving the data using wire service
@wire(findRecords, {
searchKey: '$searchKey', objectName: '$objectName',
searchField: '$fieldName', columns: '$columns'
})
relations(result) {
this.refreshTable = result;
if (result.data) {
this.data = result.data;
this.emptyList = true;
}
}

constructor() {
super();
//register dragover event to the template
this.template.addEventListener('dragover', this.handleDragOver.bind(this));
this.template.addEventListener('drop', this.handleDrop.bind(this));
}

//method which firimng on drag of the component
handleDragStart(event) {
event.dataTransfer.dropEffect = 'move';
let itemId = event.target.dataset.item;
//fire an event to the subscribers
fireEvent(this.pageRef, 'dropSelectedItem', itemId);

}

handleDragOver(event) {
event.dataTransfer.dropEffect = 'move';
event.preventDefault();
}

handleDrop(event) {
if (event.stopPropagation) {
event.stopPropagation();
}
event.preventDefault();

this.updateRecord = {
Id: this.dragRecordId,
Status: this.template.querySelector('lightning-card').title
};

//To update the record
updateRecord({ fields: this.updateRecord })
.then(() => {
this.dispatchEvent(
new ShowToastEvent({
title: 'Success',
message: 'Record Updated',
variant: 'success'
})
);

//Child to parent event to refresh all child components , after the
updation of records
const selectEvent = new CustomEvent('refresh');
this.dispatchEvent(selectEvent);
})
.catch((error) => {
this.dispatchEvent(
new ShowToastEvent({
title: 'There is an Error',
message: error.body.message,
variant: 'error'
})
);
});

}

connectedCallback() {
// subscribe to dropSelectedItem event
registerListener('dropSelectedItem', this.handledropSelectedItem, this);
}

disconnectedCallback() {
// unsubscribe from dropSelectedItem event
unregisterAllListeners(this);
}

//method is called due to listening dropSelectedItem event
handledropSelectedItem(accountInfo) {
this.dragRecordId = accountInfo;
}

@api
refreshMethod() {
//To refrsh the list using refreshapex
return refreshApex(this.refreshTable);
}
}

kanbanList.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>48.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__HomePage</target>
<target>lightning__Tab</target>
</targets>
</LightningComponentBundle>

kanbanList.css

.slds-box{
width: 400px;
background-color: darksalmon;
}



GitHub :

https://github.com/rcsaravananmkd/Saravanan/tree/master/LWC/kanbanViewLWC

https://github.com/rcsaravananmkd/Saravanan/tree/master/LWC/kanbanListLWC


 Demo :