
Hello,
I hope everyone is safe and sound. As we all know the COVID-19 pandemic is causing lots of deaths around the world. It is a very difficult time for all of us. Stay at home and Be Safe.
I have developed a COVID-19 tracking app for India on the Salesforce platform and just want to share this with you.
Check out this DEMO link: https://covid19-ind-tracker-developer-edition.ap17.force.com/
In this app, I have created some lightning components, Apex classes, and a VF page. Some third-party APIs & libraries are used in this app.
- Covid19india.org open-source API was used to get the data.
- NewsAPI is used to show the latest news/articles about COVID-19.
- ChartJS library is used to draw the chart.
To host the page I have used the force.com site in the Salesforce org. Follow this link to Setting Up Salesforce Sites.
The force.com site can only host the Visulaforce page so a VF page is created. This VF page contains the lightning components.
Find the project source code below:
Covid19_IND_TrackerCmp:
<aura:component controller="Covid19_IND_TrackerController" implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global" > | |
<aura:handler name="init" value="{!this}" action="{!c.doInit}"/> | |
<aura:attribute name="total" type="String"/> | |
<aura:attribute name="confirmed" type="String"/> | |
<aura:attribute name="active" type="String"/> | |
<aura:attribute name="recovered" type="String"/> | |
<aura:attribute name="deaths" type="String"/> | |
<!-- attributes --> | |
<aura:attribute name="sortDirection" type="String" default="asc" /> | |
<aura:attribute name="defaultSortDirection" type="String" default="asc" /> | |
<aura:attribute name="sortedBy" type="String" /> | |
<aura:attribute name="data" type="Object"/> | |
<aura:attribute name="columns" type="List"/> | |
<aura:handler event="aura:waiting" action="{!c.showSpinner}"/> | |
<aura:handler event="aura:doneWaiting" action="{!c.hideSpinner}"/> | |
<aura:attribute name="Spinner" type="boolean" default="false"/> | |
<aura:attribute name="lastUpdated" type="String"/> | |
<div class="slds-page-header"> | |
<div class="slds-grid"> | |
<div class="slds-col slds-size--7-of-12"> | |
<span style="font-size:22px;">COVID 19 (State-Wise) Tracker</span> | |
</div> | |
<div class="slds-col slds-size--4-of-12"> | |
<span style="color:green; font-size:14px; font-style:bold; position:relative;top:10px;"><!--Last Update :--> <b>{!v.lastUpdated}</b></span> | |
</div> | |
<div class="slds-col slds-size--1-of-12"> | |
<lightning:buttonIcon iconName="utility:refresh" size="large" variant="bare" onclick="{! c.doInit }" alternativeText="Refresh" title="Refresh" /> | |
</div> | |
</div> | |
</div> | |
<!--loading spinner start... style=Brand Medium (blue dots)--> | |
<aura:if isTrue="{!v.Spinner}"> | |
<div aura:id="spinnerId" class="slds-spinner_container"> | |
<div class="slds-spinner--brand slds-spinner slds-spinner--large slds-is-relative" role="alert"> | |
<span class="slds-assistive-text">Loading</span> | |
<div class="slds-spinner__dot-a"></div> | |
<div class="slds-spinner__dot-b"></div> | |
</div> | |
</div> | |
</aura:if> | |
<lightning:tabset selectedTabId="one"> | |
<lightning:tab label="LIVE DATA" id="one"> | |
<div class="slds-grid"> | |
<div class="slds-col slds-size--3-of-12" style="background-color:red; padding:10px;"> | |
<span style="color:white; font-size:14px;">CONFIRMED : <b style="font-size:18px;">{!v.confirmed}</b></span> | |
</div> | |
<div class="slds-col slds-size--3-of-12" style="background-color:#009DDC; padding:10px;"> | |
<span style="color:white;font-size:14px;"> ACTIVE : <b style="font-size:18px;">{!v.active}</b></span> | |
</div> | |
<div class="slds-col slds-size--3-of-12" style="background-color:green; padding:10px;"> | |
<span style="color:white;font-size:14px;">RECOVERED : <b style="font-size:18px;">{!v.recovered}</b></span> | |
</div> | |
<div class="slds-col slds-size--3-of-12" style="background-color:gray; padding:10px;"> | |
<span style="color:white;font-size:14px;">DECEASED: <b style="font-size:18px;">{!v.deaths} </b></span> | |
</div> | |
</div> | |
<!-- the container element determine the height of the datatable --> | |
<div style="height: 100%; font-size: 18px; padding: 1%;"> | |
<lightning:datatable | |
keyField="id" | |
data="{! v.data }" | |
columns="{! v.columns }" | |
hideCheckboxColumn="true" | |
defaultSortDirection="{!v.defaultSortDirection}" | |
sortedDirection="{!v.sortDirection}" | |
sortedBy="{!v.sortedBy}" | |
onsort="{!c.handleSort}"> | |
</lightning:datatable> | |
</div> | |
</lightning:tab> | |
<lightning:tab label="CHART" id="three"> | |
<div id="confirmedCase"> | |
<c:Covid19_Tracking_Chart/> | |
</div> | |
</lightning:tab> | |
<lightning:tab label="NEWS" id="two"> | |
<c:Covid19_News/> | |
</lightning:tab> | |
<lightning:tab label="INFORMATION AND ADVISORY" id="four"> | |
<div style="padding:2%; "> | |
<b style="font-size:25px; color:blue;">INFORMATION AND ADVISORY:By GOI</b> <br/><br/> | |
<ul style="font-size:16px;"> | |
<li> | |
<a href="https://www.mohfw.gov.in/pdf/Poster1GHFanDHGA.pdf" target="_blank"> | |
What is Novel Coronavirus? | |
</a> | |
</li><br/> | |
</ul> | |
</div> | |
</lightning:tab> | |
</lightning:tabset> | |
<div class="slds-page-header"> | |
<div class="slds-grid"> | |
<div class="slds-col slds-size--3-of-12"> | |
Developed By: <b> <a href="https://sfdclesson.com/" target="_blank">SFDC LESSONS</a></b> | |
</div> | |
<div class="slds-col slds-size--5-of-12"> | |
COVID-19 stats and patient tracing in India (Unofficial). | |
</div> | |
<div class="slds-col slds-size--4-of-12" style="font-size:10px"> | |
Powered By: <a href="https://api.covid19india.org/">covid19india.org</a> & <a href="https://newsapi.org/">NewsAPI</a> | |
</div> | |
</div> | |
</div> | |
</aura:component> |
Covid19_IND_TrackerController:
({ | |
doInit : function(component, event, helper) { | |
helper.fetchData(component,event,helper); | |
helper.setColumns(component); | |
helper.setData(component); | |
}, | |
handleSort: function(component, event, helper) { | |
helper.handleSort(component, event); | |
}, | |
// this function automatic call by aura:waiting event | |
showSpinner: function(component, event, helper) { | |
// make Spinner attribute true for display loading spinner | |
component.set("v.Spinner", true); | |
}, | |
// this function automatic call by aura:doneWaiting event | |
hideSpinner : function(component,event,helper){ | |
// make Spinner attribute to false for hide loading spinner | |
component.set("v.Spinner", false); | |
} | |
}) |
Covid19_IND_TrackerHelper:
({ | |
COLUMNS:[ | |
{label: 'STATE/UT', fieldName: 'STATEUT', type: 'text'}, | |
{label: 'CNFMD', fieldName: 'CNFMD_CASE', | |
type: 'number', | |
sortable: true, | |
cellAttributes: { alignment: 'left' }}, | |
{label: 'ACTIVE', fieldName: 'ACTIVE_CASE', type: 'text'}, | |
{label: 'RCVRD', fieldName: 'RCVRD_CASE', type: 'text'}, | |
{label: 'DECEASED', fieldName: 'DEATH', type: 'text'} | |
], | |
DATA: [], | |
fetchData : function(component,event,helper) { | |
var action = component.get("c.fecthCovid19Data"); | |
action.setCallback(this,function(response){ | |
var state1 = response.getState(); | |
if(state1 =='SUCCESS'){ | |
var result = JSON.parse(JSON.stringify(response.getReturnValue())); | |
component.set('v.columns', [ | |
{label: 'STATE/UT', fieldName: 'STATEUT', type: 'text',sortable: true}, | |
{label: 'CNFMD', fieldName: 'CNFMD_CASE', | |
type: 'number',cellAttributes: { alignment: 'left' }}, | |
{label: 'ACTIVE', fieldName: 'ACTIVE_CASE', type: 'text'}, | |
{label: 'RCVRD', fieldName: 'RCVRD_CASE', type: 'text'}, | |
{label: 'DECEASED', fieldName: 'DEATH', type: 'text'} | |
]); | |
component.set('v.confirmed',result.statewise[0].confirmed); | |
component.set('v.active',result.statewise[0].active); | |
component.set('v.recovered',result.statewise[0].recovered); | |
component.set('v.deaths',result.statewise[0].deaths); | |
var dataArry = new Array(); | |
for(var i=0; i< result.statewise.length; i++){ | |
//alert(result.statewise[i].state); | |
console.log('count >> '+i +' '+JSON.stringify(result.statewise[i].state)); | |
var fetchData = { | |
id : i, | |
STATEUT: result.statewise[i].state, | |
CNFMD_CASE : result.statewise[i].confirmed, | |
ACTIVE_CASE : result.statewise[i].active, | |
RCVRD_CASE : result.statewise[i].recovered, | |
DEATH : result.statewise[i].deaths | |
}; | |
dataArry.push(fetchData); | |
} | |
dataArry.shift(); | |
console.log('data Array = '+dataArry); | |
component.set('v.data',dataArry); | |
this.DATA = dataArry; | |
//component.set('v.lastUpdated',result.key_values[0].lastupdatedtime); | |
} | |
}); | |
$A.enqueueAction(action); | |
}, | |
setColumns: function(component) { | |
component.set('v.columns', this.COLUMNS); | |
}, | |
setData: function(component) { | |
component.set('v.data', this.DATA); | |
}, | |
// Used to sort the 'Age' column | |
sortBy: function(field, reverse, primer) { | |
var key = primer | |
? function(x) { | |
return primer(x[field]); | |
} | |
: function(x) { | |
return x[field]; | |
}; | |
return function(a, b) { | |
a = key(a); | |
b = key(b); | |
return reverse * ((a > b) - (b > a)); | |
}; | |
}, | |
handleSort: function(component, event) { | |
var sortedBy = event.getParam('fieldName'); | |
var sortDirection = event.getParam('sortDirection'); | |
//alert(component.get('v.data')); | |
//var DATA = component.get('v.data'); | |
var cloneData = this.DATA.slice(0); | |
cloneData.sort((this.sortBy(sortedBy, sortDirection === 'asc' ? 1 : -1))); | |
component.set('v.data', cloneData); | |
component.set('v.sortDirection', sortDirection); | |
component.set('v.sortedBy', sortedBy); | |
} | |
}) |
Covid19_IND_TrackerCSS:
.THIS #one__item{ | |
font-size:20px; | |
} | |
.THIS #two__item{ | |
font-size:20px; | |
} | |
.THIS #three__item{ | |
font-size:20px; | |
} | |
.THIS #four__item{ | |
font-size:20px; | |
} |
APEX CLASSES:
Covid19_IND_TrackerController:
public class Covid19_IND_TrackerController { | |
@AuraEnabled | |
public static covid19DataParser fecthCovid19Data(){ | |
string baseURL='https://api.covid19india.org/data.json'; | |
HttpRequest reqest = new HttpRequest(); | |
reqest.setEndpoint(baseURL); | |
reqest.setMethod('GET'); | |
reqest.setHeader('Accept','application/json'); | |
Http h = new Http(); | |
HTTPResponse response = h.send(reqest); | |
system.debug('Response : ' +response.getBody()); | |
covid19DataParser prsr = covid19DataParser.parse(response.getBody()); | |
system.debug('Object = ' +prsr); | |
return prsr; | |
} | |
@AuraEnabled | |
public static JsonNewWrapper fetchNews(){ | |
string apiKey=''; // put your API Key (NewsAPI.org) | |
Integer day = system.today().day()-2; | |
String formDate = string.valueOf(system.today().year()+'-0'+system.today().month()+'-0'+day); | |
//string baseURL='http://newsapi.org/v2/everything?q=covid19&sources=Google News (India),BBC News,Financial Times,The Wall Street Journal,Reddit,Time,The Economist,National Geographic,Google News,Fortune,Business Insider UK,Cnn,The Times Of India,Techcrunch,The New York Times,The Washington Post,Cnbc,The Hindu,The Verge&language=en&from=2020-03-04&sortBy=publishedAt&apiKey='+apiKey; | |
string baseURL='http://newsapi.org/v2/everything?q=covid19&domains=ndtv.com,timesofindia.indiatimes.com,thehindu.com,indiatoday.in,republicworld.com,news.google.com,zeenews.india.com&language=en&from='+formDate+'&sortBy=publishedAt&apiKey='+apiKey://+system.today().format()+ | |
HttpRequest reqest = new HttpRequest(); | |
reqest.setEndpoint(baseURL); | |
reqest.setMethod('GET'); | |
reqest.setHeader('Accept','application/json'); | |
Http h = new Http(); | |
HTTPResponse response = h.send(reqest); | |
system.debug('status: '+response.getstatusCode()); | |
system.debug('body: '+response.getBody()); | |
JsonNewWrapper jnw = JsonNewWrapper.parse(response.getBody()); | |
return jnw; | |
} | |
} |
Note: You need to signup to get the NewsAPI Key.
covid19DataParser:
public class covid19DataParser { | |
@AuraEnabled | |
public List<Cases_time_series> cases_time_series; | |
@AuraEnabled | |
public List<Key_values> key_values; | |
@AuraEnabled | |
public List<Statewise> statewise; | |
@AuraEnabled | |
public List<Tested> tested; | |
public class Key_values { | |
@AuraEnabled | |
public String confirmeddelta; | |
@AuraEnabled | |
public String counterforautotimeupdate; | |
@AuraEnabled | |
public String deceaseddelta; | |
@AuraEnabled | |
public String lastupdatedtime; | |
@AuraEnabled | |
public String recovereddelta; | |
@AuraEnabled | |
public String statesdelta; | |
} | |
public class Delta { | |
@AuraEnabled | |
public Integer active; | |
@AuraEnabled | |
public Integer confirmed; | |
@AuraEnabled | |
public Integer deaths; | |
@AuraEnabled | |
public Integer recovered; | |
} | |
public class Statewise { | |
@AuraEnabled | |
public String active; | |
@AuraEnabled | |
public String confirmed; | |
@AuraEnabled | |
public String deaths; | |
@AuraEnabled | |
public Delta delta; | |
@AuraEnabled | |
public String deltaconfirmed; | |
@AuraEnabled | |
public String deltadeaths; | |
@AuraEnabled | |
public String deltarecovered; | |
@AuraEnabled | |
public String lastupdatedtime; | |
@AuraEnabled | |
public String recovered; | |
@AuraEnabled | |
public String state; | |
@AuraEnabled | |
public String statecode; | |
} | |
public class Cases_time_series { | |
@AuraEnabled | |
public String dailyconfirmed; | |
@AuraEnabled | |
public String dailydeceased; | |
@AuraEnabled | |
public String dailyrecovered; | |
@AuraEnabled | |
public String date1; | |
@AuraEnabled | |
public String totalconfirmed; | |
@AuraEnabled | |
public String totaldeceased; | |
@AuraEnabled | |
public String totalrecovered; | |
} | |
public class Tested { | |
@AuraEnabled | |
public String source; | |
@AuraEnabled | |
public String testsconductedbyprivatelabs; | |
@AuraEnabled | |
public String totalindividualstested; | |
@AuraEnabled | |
public String totalpositivecases; | |
@AuraEnabled | |
public String totalsamplestested; | |
@AuraEnabled | |
public String updatetimestamp; | |
} | |
public static covid19DataParser parse(String json) { | |
return (covid19DataParser) System.JSON.deserialize(json, covid19DataParser.class); | |
} | |
} |
JsonNewsWrapper:
public class JsonNewWrapper{ | |
@AuraEnabled | |
public String status; | |
@AuraEnabled | |
public Integer totalResults; | |
@AuraEnabled | |
public List<Articles> articles; | |
public class Articles { | |
@AuraEnabled | |
public Source source; | |
@AuraEnabled | |
public String author; | |
@AuraEnabled | |
public String title; | |
@AuraEnabled | |
public String description; | |
@AuraEnabled | |
public String url; | |
@AuraEnabled | |
public String urlToImage; | |
@AuraEnabled | |
public String publishedAt; | |
@AuraEnabled | |
public String content; | |
} | |
public class Source { | |
@AuraEnabled | |
public String id; | |
@AuraEnabled | |
public String name; | |
} | |
public static JsonNewWrapper parse(String json) { | |
return (JsonNewWrapper) System.JSON.deserialize(json, JsonNewWrapper.class); | |
} | |
} |
Covid19_News lightning component was created to show the news/articles about coronavirus from Indian news sources.
Covid19_News:
<aura:component controller="Covid19_IND_TrackerController" access="global"> | |
<aura:attribute name="newsData" type="Object"/> | |
<aura:handler name="init" value="{!this}" action="{!c.doInit}"/> | |
<div class="slds-grid"> | |
<div class="slds-col slds-size--12-of-12"> | |
<div aura:id="new_content" style="padding:20px;"> | |
<aura:iteration items="{!v.newsData}" var="news" > | |
<div class="contain" > | |
<a href="{!news.url}" id="link" target="_blank"> | |
<span id="title" style="font-size:25px;"><b>{!news.title}</b></span> | |
</a><br/> | |
<span style="color:blue; font-size:14px;">Source : {!news.source.name}, Author : {!news.author}<br/></span> | |
<span id="desc" style="font-size:20px;">{!news.description}</span><br/> | |
<a href="{!news.url}" id="link" target="_blank"> | |
<img src="{!news.urlToImage}" id="image" style="width:1000px; height:600px;"/> | |
</a> <br/><br/> | |
</div> | |
<br/> | |
</aura:iteration> | |
</div> | |
</div> | |
</div> | |
</aura:component> |
Covid19_NewsController:
({ | |
doInit : function(component, event, helper) { | |
helper.fetchNewsData(component, event, helper); | |
} | |
}) |
Covid19_NewsHelper:
({ | |
fetchNewsData : function(component, event, helper) { | |
var action=component.get("c.fetchNews"); | |
action.setCallback(this, function(response) { | |
var state = response.getState(); | |
if (state === "SUCCESS") { | |
var result = JSON.parse(JSON.stringify(response.getReturnValue())); | |
component.set('v.newsData',result.articles); | |
console.log('Articles = '+JSON.stringify(result.articles)); | |
//console.log(' Result : ' +JSON.stringify(result.articles[0].title)); | |
} | |
}); | |
$A.enqueueAction(action); | |
} | |
}) |
Covid19_Tracking_ChartCmp lightning component was created to show the charts based on the data provided by API.
Covid19_Tracking_ChartCmp:
<aura:component controller="Covid19_IND_TrackerController" access="global"> | |
<!-- load chart js library from static resource--> | |
<aura:handler name="init" value="{!this}" action="{!c.doInit}"/> | |
<ltng:require scripts="/resource/Chart_Js/Chart.js-2.9.3/dist/Chart.bundle.js" /> | |
<aura:attribute name="chartObj" type="object" access="public"/> | |
<aura:attribute name="property1" type="string"/> | |
<aura:attribute name="property2" type="string"/> | |
<div aura:id="chartContainer" style=" "> | |
<canvas aura:id="myChart" id="{!v.property1}" /><!--reportChart--> | |
</div> | |
</aura:component> |
Covid19_Tracking_ChartController:
({ | |
doInit : function(component, event, helper) { | |
helper.CreateChart(component); | |
} | |
}) |
Covid19_Tracking_ChartHelper:
({ | |
CreateChart : function(component) { | |
var chartType= 'bar'; | |
var action=component.get("c.fecthCovid19Data"); | |
action.setCallback(this,function(response){ | |
var state=response.getState(); | |
if(state=='SUCCESS'){ | |
var result = JSON.parse(JSON.stringify(response.getReturnValue())); | |
console.log('result : ' +JSON.stringify( result)); | |
var data = []; | |
var Label= []; | |
var data1 = []; | |
var Label1= []; | |
for(var i=0; i< result.statewise.length; i++){ | |
var tLabel = result.statewise[i].state; | |
Label.push(tLabel); | |
var tValue = result.statewise[i].confirmed; | |
data.push(tValue); | |
var tLabel = result.statewise[i].state; | |
Label1.push(tLabel); | |
var tValue = result.statewise[i].deaths; | |
data1.push(tValue); | |
} | |
Label.shift(); | |
data.shift(); | |
console.log('Label : '+Label); | |
console.log('data : '+data); | |
//Create chart1 | |
var el=component.find('myChart').getElement(); | |
var ctx=el.getContext('2d'); | |
if(window.bar!=undefined){ | |
window.bar.destroy(); | |
} | |
var background_color=new Array(); | |
for(var i=0 ; i<100 ; i++){ | |
var w,x,y,z; | |
w = parseInt(Math.random()*255); | |
x = parseInt(Math.random()*255); | |
y = parseInt(Math.random()*255); | |
z = Math.random(); | |
background_color.push('rgba('+w+','+x+','+y+','+z+')'); | |
} | |
window.bar = new Chart(ctx, { | |
type: chartType, | |
data: { | |
labels: Label, | |
datasets: [ | |
{ | |
label: "CONFIRMED CASE", | |
fillColor: background_color, | |
backgroundColor: background_color, | |
strokeColor: "rgba(220,220,220,1)", | |
data: data | |
} | |
] | |
}, | |
options: { | |
hover: { | |
mode: "none" | |
}, | |
scales: { | |
yAxes: [{ | |
ticks: { | |
beginAtZero:true | |
} | |
}] | |
} | |
} | |
}); | |
} | |
}); | |
$A.enqueueAction(action); | |
}, | |
}) |
This lightning app contains the ‘Covid19_IND_TrackerCmp’ main component.
COVID19_IND_LtngApp
<aura:application access="GLOBAL" extends="ltng:outApp" implements="ltng:allowGuestAccess"> | |
<aura:dependency resource="c:Covid19_IND_Tracker"/> | |
<c:Covid19_IND_Tracker /> | |
</aura:application> |
The visualforce contains lightning app and components.
Covid19_Tracker_App VF page:
<apex:page showHeader="false" sidebar="false"> | |
<apex:includeLightning /> | |
<div id="LcDisplayId"></div> | |
<script> | |
$Lightning.use("c:Covid19_IND_TrackerApp", function() { | |
$Lightning.createComponent("c:Covid19_IND_Tracker", | |
{ | |
}, | |
"LcDisplayId", | |
function(component) { | |
}); | |
}); | |
</script> | |
</apex:page> |
References:
https://github.com/covid19india/covid19india-react
Thanks
Arun Kumar
Hi, the data is not getting loaded on VF page , only table labels are coming.
LikeLike
Hello Ankit,
Hope you have implemented all the code correctly.
Here is the few things that you can do:
* Add remote site setting in your org for both (2) endpoint used in the code.
1. https://api.covid19india.org/data.json
2. http://newsapi.org/v2/everything?
Put API key for news API.
* Check the debug log what error you are getting.
Thanks
LikeLike
Thanks for the response.
Now I am getting Below error on VF page load:
An internal server error has occurred
Error ID: 34096863-58308 (-1249547377)
LikeLike
Do i need to create a static resource for Charts
LikeLike
This is the error i am getting on chart:
This page has an error. You might just need to refresh it.
Error in $A.getCallback() [Chart is not defined]
Callback failed: apex://Covid19_IND_TrackerController/ACTION$fecthCovid19Data
Failing descriptor: {markup://c:Covid19_Tracking_Chart}
LikeLike
Oh! Yes. You need to put the ChartJS in the static resource. https://www.chartjs.org/docs/latest/getting-started/installation.html
LikeLike
Arun,
This is not a file right,which JS i need to store in Static resoure.
Kindly help.
LikeLike