Wednesday, 13 October 2021

Getting started with Heroku

I am familiar with the heroku for quite long time, have seen lots of people are interested with it but not sure from where its need to Start.  

This post basically demonstrate basis prerequisite to start with Heroku.

Hope you would find my post useful !

What is Heroku?

Heroku is a cloud platform that lets companies build, deliver,
monitor and scale apps - heroku is the fastest way to go from
idea to URL, bypassing all those infrastructure headaches.

Basic Prerequisite :

 Required online Resource


 Required Tools

Git : Once installed check if you have git installed properly using this command, if you are not getting the expected
output please try again, or try to set your PATH parameter.

 git --version
 [Expected Output]git version 2.x.x


Heroku CLI Toolbelt : Once installed check if you have git installed properly using this command, if you are not getting
the expected output, please try installing the toolbelt again, or try to set your PATH parameter.

 heroku --version
 [Expected Output]heroku/7.x.x darwin-x64 node-v12.x.x

Node.Js :

  node --version
 [Expected Output]v12.x.x

  npm --version
  [Expected Output]6.x.x

Pre Work

 Complete this Trailmix  :Pre Work For Heroku Hands-On
 Complete this Tutorial  :  Getting Started on Heroku with Node.js




Saturday, 4 September 2021

Type ahead search component using Lightning web component(LWC)

Overview 

In this tutorial we are going to build a simple TypeAhead Search component using LWC which creates suggestions as soon as someone begins typing into a text box. We will architect the component in such a way that the final product will be configurable

Implementation

To implement Type-ahead Searcher we will use lightning datatable to display the search result. in this scope of article i will not explain how to search record within salesforce database. 


below 4 parameters you can pass though dynamically using lightning app builder which is defined in metadata xml file.


<targetConfig targets="lightning__RecordPage">
            <property name="header" type="String"/>
            <property name="object" type="String"/>
            <property name="fields" type="String"/>
            <property name="viewable" type="String"/>
</targetconfig


  • header: name of the section
  • object: API name of object
  • fields: comma separated API name of fields
  • viewable: name of exact label for comma separated fields
Lets drive into the code....

Parent component 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<template>

    <lightning-card title={header} icon-name="custom:custom63">
        <div class="slds-m-around_medium">
            
            <c-search-console 
                object={object} 
                fields={fields}
                ontypeahead={handleObjects}>
            </c-search-console>


            <c-mydata-table 
                objects={objects}
                viewable={viewable}>
            </c-mydata-table>
            
        </div>
    </lightning-card>

</template>


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/* eslint-disable no-console */
import { api, LightningElement, track } from 'lwc';

export default class RummageView extends LightningElement {

    @api header = ''
    @api object = ''
    @api viewable;
    @api fields = []

    @track objects;

    handleObjects(e){
        
        this.objects = e.detail
    }
}


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>47.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__RecordPage</target>
        <target>lightning__AppPage</target>
        <target>lightning__HomePage</target>

        <target>lightningCommunity__Page</target>
        <target>lightningCommunity__Default</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__RecordPage">
            <property name="header" type="String"/>
            <property name="object" type="String"/>
            <property name="fields" type="String"/>
            <property name="viewable" type="String"/>
        </targetConfig>
        <targetConfig targets="lightning__AppPage">
            <property name="header" type="String"/>
            <property name="object" type="String"/>
            <property name="fields" type="String"/>
            <property name="viewable" type="String"/>
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

Child Component 1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<template>

    <lightning-input 
        label="Search" 
        value={query} 
        type="search" 
        onchange={handleKeyChange} 
        class="slds-m-bottom_small">
    </lightning-input>

</template>


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/* eslint-disable no-console */
import { api, LightningElement, track, wire } from 'lwc'

import search from '@salesforce/apex/searchcontroller.search'

/** The delay used when debouncing event handlers before invoking Apex. */
const DELAY = 500;

export default class RummageBar extends LightningElement {

    @api object = '';
    @api fields = '';
    @api objects = [];

    @track query = '';

    base = '';
    fieldArray = [];

    @wire(search, { fieldArray: '$fieldArray', base: '$base', query: '$query' })
    wiredResult(objects) { 
        console.log( 'RAN WIRE  objects =>' );
        console.log( JSON.parse( JSON.stringify( objects ) ) );
        
        if (objects.data) {
            
            this.objects = []
            this.error = false
            this.dispatchEvent(new CustomEvent('typeahead', { detail: objects.data }))
        }
        else if (objects.error) {
            this.error = objects.error
            this.dispatchEvent(new CustomEvent('whytherum', { detail: objects.error }))
        }
    }

    handleKeyChange(event) {

        // Debouncing this method: Do not update the reactive property as long as this function is
        // being called within a delay of DELAY. This is to avoid a very large number of Apex method calls.
        window.clearTimeout(this.delayTimeout)

        const query = event.target.value
        console.log('query..>'+query);
        console.log(this.fields)
        console.log(this.object)
        if(!query){
            this.objects = []
        }

        if(!this.base){
            this.base = `SELECT ${this.fields} FROM ${this.object} `
            console.log('base url..>'+this.base);
        }

        if( ! this.fieldArray.length ){
            this.fieldArray = this.fields.split(',')
        }

        if(!query || !this.fieldArray.length || !this.base){
            return false
        }

        // eslint-disable-next-line @lwc/lwc/no-async-operation
        this.delayTimeout = setTimeout(() => {
            this.query = query
            console.log('THIS>QUERY => ${this.query}')
        }, DELAY)

        return true
    }
}

Child component 2


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<template>
    
    <template if:true={isdataFound}>
        <lightning-datatable
            key-field="id"
            data={data}
            columns={columns}
            max-row-selection="1">
        </lightning-datatable>
    </template>

</template>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/* eslint-disable no-console */
import { api, LightningElement, track } from 'lwc';


export default class ReactiveTable extends LightningElement {

    @api viewable

    @track data =[]
    @track columns = []
    @track isdataFound=false
    
    @api 
    get objects(){
        return this.data
    }
    set objects(data){
       console.log('data==>'+JSON.stringify(data));
        if(data==null||Object.keys(data).length==0){ 
            this.isdataFound=false;
            this.data = data
            return 
        }
        console.log('length-->'+Object.keys(data).length);
        this.isdataFound=true;
        const [ record ] = data 

        const viewable = this.viewable.split(',').map(x => x.trim())
        console.log('viewable..'+viewable);
        const fields =  Object.keys(record).filter(x => viewable.includes(x))
        console.log('fields..'+fields);
        const columns = fields.map(x => ({ label: this.labeler(x), fieldName: x }))
        console.log('fields..'+columns);

        //console.dir( 'ReactiveTable.set.object data => ' )
        console.log( JSON.parse( JSON.stringify( {data} ) ) )

        this.columns = columns
        this.data = data
    }

    labeler(raw){

        const s = raw.replace('__c', '').replace(/_/gi, ' ')
        
        return s
    }
}


Apex Controller:

 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public with sharing class searchcontroller {
    
    @AuraEnabled(cacheable=true)
    public static List<sObject> search(List<String> fieldArray, String base, String query) {
        system.debug('fieldArr'+fieldArray);
        system.debug('query'+query);
        system.debug('base'+base);
        if(base == '' || fieldArray.size() == 0 || query == ''){ return null; }

        
        Integer LMT = 10; // -1 for no limit

        String key = '%' + query + '%';

        String search = base;
        
        // Build where clause, skip if invalid
        for(String field : fieldArray){

            if('ID' == field.trim().toUpperCase()){
                continue;
            }
            
            search += search.indexOf(' WHERE') == -1
                ? ' WHERE '+field+' LIKE :key '
                : ' OR '+field+' LIKE :key ';
        }

        search += LMT > -1 ? ' LIMIT '+LMT+' ' : '';

        // System.debug(base); System.debug(fieldArray); System.debug(query); System.debug(search);

        return Database.query( search );
    }
}


Demo Time:

for demo purpose i did pass following parameters within Account Record page as below.


    Let's search  account and see the result as below.



Thank you for your attention and hope that this article was interesting for you.. !
Cheers ..!




Friday, 9 July 2021

Display Bar Chart using Chart.js in Lightning Web Component(LWC)

Recently i have worked with a POC to display barchart using chart.js in lightning web component.

Use Case

For an an energy utility company , display electric usage information for an customer during notified outage (Event). Usage basically the actual consumption of electric  kilo watt(KW)  value on every 15 mins interval. 

Bar-chart should  present the usage data of the customer for the event day where 15-minute interval data (kW)  to be presented  in the chart .

X Axis will display : 15 mins interval Time
Y Axis will display : Usage(KW)

Color coding: 
  • The event intervals to be shaded in blue if the demand is below FSL(Target Threshold)   
  • The event intervals to be shaded in red if the demand is above FSL

Let's talk about how to implement it..?

Source Code:

As a prerequisite we would need chart.js framework  to be loaded as static resource  , download the the latest version from https://github.com/chartjs/Chart.js and upload as zip.

Now create the component called "barChartDemo"

barChartDemo.html :



1
2
3
4
5
<template>
    <div class ='slds-box slds-theme_default custom-background' >
        <canvas class="chart" width="200" height="100" lwc:dom="manual"></canvas>
    </div> 
</template>


barChartDemo.js :



  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/**
 * @File Name          : barChartDemo.js
 * @Description        : dynamic bar chart to display usage data on 15 mins interval
 * @Author             : Swarup satpati
 * @Modification Log   : 
 * Ver       Date            Author            Modification
 * 1.0    7/2/2020         swarup satpati     demo Version
**/
import { LightningElement, track, wire,api } from 'lwc';
/*chart.js library*/
import chartjs from '@salesforce/resourceUrl/ChartJsBIP';
import plugins  from '@salesforce/resourceUrl/chartjsannotation';
/*To import third-party JavaScript or CSS library, use the platformResourceLoader module.*/
import { loadScript, loadStyle } from 'lightning/platformResourceLoader';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import getusage from '@salesforce/apex/UsageData.getUsageData'

export default class BarChartDemo extends LightningElement {
    @api eventname='Event-011166';
    @api said='1449702025';
    @api eventstarttime;
    @api eventendtime;
    @api chartData;
    @api Data 
    @track fslvalue=75;
    @track starttime;
    @track endtime;
    @api label=[];
    @api usage=[];

    error;
    @api  chart;
    chartjsInitialized = false;
    
    renderedCallback() {
         
        this.init();

        console.log('initialized...'+this.chartjsInitialized);
          
          if (this.chartjsInitialized) {
              return;
           }
           this.chartjsInitialized = true;       
        
    }

    init(){
        getusage({
            EventName:this.eventname,
            SAID:this.said})
            .then(data => {
                console.log('****data***'+JSON.stringify(data));
                for(let key in data)
                {
                    //console.log('label.. data'+JSON.stringify(data[key].label));
                    this.label.push(data[key].label);
                    this.usage.push(data[key].usagekw);

                }
                console.log('usage..'+JSON.stringify(this.usage));
                console.log('label..'+JSON.stringify(this.label));
                
            Promise.all([
                    loadScript(this, chartjs + '/Chart.min.js'),
                    loadStyle(this, chartjs + '/Chart.min.css'),
                    loadStyle(this, plugins + '/chartjs-plugin-annotation.min.js'),
                    loadStyle(this, plugins + '/chartjs-plugin-annotation.js')  
                ])
               .then(() => {
                // disable Chart.js CSS injection
                console.log('draw chart..'+JSON.stringify(this.usage));
                window.Chart.platform.disableCSSInjection = true;
                this.chartjsInitialized = true;
                this.diaplayChart();
               } )
               .catch((error) => {
                this.error = error;
                });
        

            })
            .catch(error => {
                    console.log('The error ', error);
                  });
     }
    
    diaplayChart(){
        
        var chartColors = {
            red: 'rgb(255, 99, 132)',
            blue: 'rgb(54, 162, 235)'
          };
        var horizonalLinePlugin = {
            afterDraw: function(chartInstance) {
              var yScale = chartInstance.scales["y-axis-0"];
              var canvas = chartInstance.chart;
              var ctx = canvas.ctx;
              var index;
              var line;
              var style;
              var yValue;
          
              if (chartInstance.options.horizontalLine) {
                for (index = 0; index < chartInstance.options.horizontalLine.length; index++) {
                  line = chartInstance.options.horizontalLine[index];
          
                  if (!line.style) {
                    style = "   (169,169,169, .6)";
                  } else {
                    style = line.style;
                  }
          
                  if (line.y) {
                    yValue = yScale.getPixelForValue(line.y);
                  } else {
                    yValue = 0;
                  }
          
                  ctx.lineWidth = 1;
          
                  if (yValue) {
                    ctx.beginPath();
                    ctx.moveTo(0, yValue);
                    ctx.lineTo(canvas.width, yValue);
                    ctx.strokeStyle = style;
                    ctx.stroke();
                  }
          
                  if (line.text) {
                    ctx.fillStyle = style;
                    ctx.fillText(line.text, 0, yValue + ctx.lineWidth);
                  }
                }
                return;
              };
            }
          };
          Chart.pluginService.register(horizonalLinePlugin);
        
        console.log('usage kw..>>'+JSON.stringify(this.usage));
        console.log('label..>>'+JSON.stringify(this.label));
        var ctx = this.template.querySelector(".chart");
        var lineChart = new Chart(ctx, {
            type: 'bar',
            data: {
                labels: this.label,
                datasets: [{
                    label: 'KW',
                    backgroundColor: [],
                    data: this.usage,
                    borderColor: 'rgba(121, 159, 222, 1)',
                    fill: true,
                    pointBackgroundColor: "#26B99A",
                    pointBorderWidth: 2,
                    pointHoverRadius: 5,
                    pointRadius: 2,
                    bezierCurve: true,
                    pointHitRadius: 10
                }]
            },
            options: {
                legend: {
                    position: 'top',
                    padding: 10,
                },
                animation: {
                    //animateScale: true,
                    //animateRotate: true,
                    duration:0

                },

                scales: {
            yAxes: [{
              scaleLabel: {
                display: true,
                labelString: 'Usage(kW)'
              }
            }],
            xAxes: [{
                scaleLabel: {
                  display: true,
                  labelString: 'Timing'
                }
              }]
          },
                "horizontalLine": [{
                    "y": this.fslvalue,
                    "style":"rgba(30,139,195,0.5)",
                     "text": "FSL(KW)"
                  }],
                responsive: true
            }
        });
        console.log('chart update..');
        var dataset = lineChart.data.datasets[0];
        for (var i = 0; i < dataset.data.length; i++) {
            console.log('value..'+dataset.data[i]);

             if (dataset.data[i] > this.fslvalue) {
              //lineChart.data.datasets[0].backgroundColor[i] = chartColors.red;
              dataset.backgroundColor[i] = chartColors.red;
            } else{
                console.log('in else...');

               dataset.backgroundColor[i] = chartColors.blue;
               //lineChart.data.datasets[0].backgroundColor.push('rgb(54, 162, 235)');

                 console.log('in else...');

            }
        }
            
        lineChart.update();

    }

}


Apex Class :


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public with sharing class UsageData {
    
        @AuraEnabled
        public static List<DataSet> getUsageData(String EventName,String SAID){
        List<Dataset> dataset=new List<dataset>();
        DRMI_BIP_Compliance_Report__c  CMReport= [ SELECT Id, SA_ID__c, Event_Name__c,isApproved__c,Event_Date__c,Event_KWh__c,Rep_Name__c,Load_Zone__c, Rep_Phone__c, Address__c, City__c, Name,Excess_Energy_Charge__c,FSL_ComplianceReport__c,Firm_Service_Level__c,Total_excess_energy_charge_c__c,Subject_to_Retest__c,Excess_enregy_in_kw__c,(SELECT id, Usage_Data_in_Kw__c,Hour_Ending__c ,Time_Ending__c  from DRMI_BIP_Compliance_Report_Usage__r order by Time_Ending__c),(SELECT id ,Charges__c,Incentives__c,Month_Year__c  from DRMI_BIP_Compliance_Report_Summary__r ) FROM DRMI_BIP_Compliance_Report__c where Event_Name__c=:EventName and SA_ID__c=:SAID limit 1 ];

        for(DRMI_BIP_Compliance_Report_Usage__c usageRow: CMReport.DRMI_BIP_Compliance_Report_Usage__r){
            String Tm=usageRow.Hour_Ending__c;
            decimal kw=usageRow.Usage_Data_in_Kw__c;
            dataSet.add(new DataSet(Tm ,kw));

     }
     return dataset;
 }
   
    public class DataSet{
        public DataSet(String label ,decimal usagekw){
            this.label  = label ; 
            this.usagekw = usagekw ;
        }
        @AuraEnabled
        public String label {get;set;}
        @AuraEnabled
        public decimal usagekw {get;set;}
        
        
    }
}

Code Explanation :

In the JavaScript class file, you need to include below two imports to access the functionality of the third-party JavaScript library.


1
2
3
4
5
/*chart.js library*/
import chartjs from '@salesforce/resourceUrl/ChartJsBIP';
import plugins  from '@salesforce/resourceUrl/chartjsannotation';
/*To import third-party JavaScript or CSS library, use the platformResourceLoader module.*/
import { loadScript, loadStyle } from 'lightning/platformResourceLoader';

The loadScript module is used to load the JavaScript file. The method returns a JavaScript promise which you need to resolve in your code. Below is an example of how to use the loadScript method:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
 Promise.all([
                    loadScript(this, chartjs + '/Chart.min.js'),
                    loadStyle(this, chartjs + '/Chart.min.css'),
                    loadStyle(this, plugins + '/chartjs-plugin-annotation.min.js'),
                    loadStyle(this, plugins + '/chartjs-plugin-annotation.js')  
                ])
               .then(() => {
                // disable Chart.js CSS injection
                console.log('draw chart..'+JSON.stringify(this.usage));
                window.Chart.platform.disableCSSInjection = true;
                this.chartjsInitialized = true;
                this.diaplayChart();
               } )

For horizontal Line  i have registered "horizonalLinePlugin "to draw the reference line in the bar chart.

Hope you are enjoying my post !

Output :





This is just an example to demonstrate how to use chart.js in advanced level .Happy Coding !! 

Sunday, 27 June 2021

Parse CSV file using Lightning Web Component(LWC)

Use case:

Need an functionality to import CSV file and parse  those records for further processing. This post will explain how to use LWC component to process CSV in client side without using apex code.

Hope you are going to like this post.

Lets get through the Code :

app.html


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!--
 * @File Name          : app.html
 * @Description        : 
 * @Author             : Swarup Satpati
 * @Last Modified By   : Swarup Satpati
 * @Last Modified On   : 6/25/2020, 4:57:24 PM
 * @Modification Log   : 
 * Ver       Date            Author            Modification
 * 1.0    6/25/2020      Swarup Satpati     Initial Version
-->

<template>
<lightning-card title="CSV Uploader" icon-name="custom:custom19">
<form class="slds-form--inline">
<div class="slds-form-element">
    <lightning-input type="file"
                     label="Choose File"
                     accept=".csv"
                     onchange={importcsv}
                     multiple>
    </lightning-input>
</div>  

<div class="slds-form-element slds-text-body_small slds-text-color_error">{filename}</div>

<div class="slds-form-element">
    <lightning-button label="Import" title="Non-primary action" onclick={readFiles} class="slds-m-left_x-small"></lightning-button>
</div>

<div style="margin-left:4%" class="slds-form-element">
<template if:true={showLoadingSpinner}>
        <lightning-spinner alternative-text="Uploading......" size="medium"></lightning-spinner>
    </template>
</div>

</form>

<br/>
    <lightning-datatable
            data={data}
            columns={columns}
            key-field="SAID"
        >
    </lightning-datatable>
</lightning-card> 
</template>


app.js


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
/**
 * @File Name          : app.js
 * @Description        : 
 * @Author             : Swarup Satpati
 * @Last Modified By   : Swarup Satpati
 * @Last Modified On   : 6/25/2020, 4:57:24 PM
 * @Modification Log   : 
 * Ver       Date            Author            Modification
 * 1.0    6/25/2020      Swarup Satpati     Initial Version
**/
import { LightningElement, track, api } from 'lwc';

const COLS = [
    { label: 'SA ID', fieldName: 'SAID' },
    { label: 'UUID', fieldName: 'UUID'}
];
export default class App extends LightningElement {
    @track columns = COLS;
    @track data;
    @track showLoadingSpinner = false;

    MAX_FILE_SIZE = 2000000; //Max file size 2.0 MB
    filesUploaded = [];
    filename;
 
    importcsv(event){
        if (event.target.files.length > 0) {
            this.filesUploaded = event.target.files;
            this.filename = event.target.files[0].name;
            console.log(this.filename);
            if (this.filesUploaded.size > this.MAX_FILE_SIZE) {
                this.filename = 'File Size is to long to process';
            } 
    }
    }
    readFiles() {
        [...this.template
            .querySelector('lightning-input')
            .files].forEach(async file => {
                try {
                    const result = await this.load(file);
                    // Process the CSV here
                  this.showLoadingSpinner = false;

                    console.log(result);
                   // this.processData(result);
                     this.data=JSON.parse(this.csvJSON(result));
                    console.log('data..'+JSON.parse(this.data));

                } catch(e) {
                    // handle file load exception
                    console.log('exception....');
                }
            });
    }
    async load(file) {
        return new Promise((resolve, reject) => {
        this.showLoadingSpinner = true;
            const reader = new FileReader();
            // Read file into memory as UTF-8      
            //reader.readAsText(file);
            reader.onload = function() {
                resolve(reader.result);
            };
            reader.onerror = function() {
                reject(reader.error);
            };
            reader.readAsText(file);
        });
    }

     
//process CSV input to JSON
 
 csvJSON(csv){

  var lines=csv.split(/\r\n|\n/);

  var result = [];

  var headers=lines[0].split(",");
  console.log('headers..'+JSON.stringify(headers));
  for(var i=1;i<lines.length-1;i++){

   var obj = {};
   var currentline=lines[i].split(",");

   for(var j=0;j<headers.length;j++){
    obj[headers[j]] = currentline[j];
   }

   result.push(obj);

  }
  console.log('result..'+JSON.stringify(result));
  //return result; //JavaScript object
  return JSON.stringify(result); //JSON
}

}


Explanation:

Have created a CSV with two columns SAID,UUID and prepare almost 4k records for demo purpose.  After uploading this CSV once we click on import button component  is  invoking a method  to pass this file to fileReader  and in-turn it's invoke another method to parse this file-reader object into JSON format. Finally , we are displaying json data in lightning data table for further processing....... !.

Note: for big file we should be careful to utilize this approach but its work perfectly till 2 mb.


Output:





Thursday, 18 March 2021

Custom Multi select dropdown using LWC

Recently i have worked on " multi select picklist" component required for my project. Showing you how to create it using LWC and hopefully you can use this component as your need.

it looks great and work beautifully , Lets enjoy ..!


multiselectdropdown.html


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<template>
    <div class="slds-form-element">
     <label class="slds-form-element__label" for="combobox-id-5">Relate to</label>
        <div class="slds-form-element__control">
          <div class="slds-combobox_container slds-size_small">
            <section id="maindiv" class="slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click slds-is-open" aria-expanded="true" aria-haspopup="listbox" role="combobox">
              <div onclick={handleClick} onmouseleave={handleMouseOut} class="slds-combobox__form-element slds-input-has-icon slds-input-has-icon_right" role="none">
                <input type="text" class="slds-input slds-combobox__input slds-has-focus slds-combobox__input-value" id="combobox-id-5" aria-controls="listbox-id-5"  role="textbox" placeholder={defaultText} readonly=""
                />
            
                <span class="slds-icon_container slds-icon-utility-down slds-input__icon slds-input__icon_right">
                    <lightning-icon icon-name="utility:down" size="x-small" alternative-text="Approved"></lightning-icon>
                </span>
              </div>
              <template if:true={iswindowOpen}>
              <div onmouseenter={handlemouseOver} onmouseleave={handleMouseLeave} id="listbox-id-5" class="slds-dropdown slds-dropdown_length-5 slds-dropdown_fluid" role="listbox">
                <ul class="slds-listbox slds-listbox_vertical" role="presentation">
                <template for:each={_options} for:item="option">
                    <c-list-items key={option.value}
                                         value={option.value}
                                         label={option.label}
                                         selected={option.selected}
                                         onselected={handleSelectedClick}></c-list-items>
                    
                  </template>
                  </ul>
              </div>
            </template>

            </section>
          </div>
        </div>
      </div>
</template>



multiselectdropdown.js



  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import { LightningElement, api, track } from 'lwc';

export default class Multiselectdropdown extends LightningElement {
@api options = [{label:'Resource 1',value:'Resource 1',selected:false},
    {label:'Resource 2',value:'Resource 2',selected:false},
    {label:'Resource 3',value:'Resource 3',selected:false},
    {label:'Resource 4',value:'Resource 4',selected:false},
    {label:'Resource 5',value:'Resource 5',selected:false},
    {label:'Resource 6',value:'Resource 6',selected:false},
    {label:'Resource 7',value:'Resource 7',selected:false},
    {label:'Resource 8',value:'Resource 8',selected:false}];
    @api selectedValues=[];
    @api label=' ';
    @track _options= [];
    @track iswindowOpen=false;

  /*Init() call..*/
    connectedCallback() {
        this._options = JSON.parse(JSON.stringify(this.options));
        //sort all array items befor display it to ui
        this._options=this._options.sort(function compare(a,b) {
            if (a.value == 'All'){
              return -1;
            }
            else if (a.value < b.value){
              return -1;
            }
            if (a.value > b.value){
              return 1;
            }
            return 0;
          });
        
      }
      get defaultText(){
        if (this.selectedValues.length === 0) {
          return "Select an option...";
        }
        if (this.selectedValues.length === 1) {
          return this.selectedValues[0].label;
        }
        else{
          return this.selectedValues.length+" Options Selected";

        }
      }

      handleClick(event){
          console.log('query selector..'+this.template.querySelector("section"));
        this.iswindowOpen=true;
        this.template.querySelector("section").classList.add("slds-is-open");
      }

      handleMouseOut(event){
       if(this.iswindowOpen){
        return;
       }
       this.template.querySelector("section").classList.remove("slds-is-open");
      }

      handleMouseLeave(event){
        this.iswindowOpen=false;
        this.template.querySelector("section").classList.remove("slds-is-open");

      }

      handlemouseOver(event){
        this.iswindowOpen=true;
      }

      handleSelectedClick(event){

        var value;
        var selected;
        event.preventDefault();
        event.stopPropagation();
    
        const data = event.detail;
    
        value = data.value;
        selected = data.selected;
    
        //shift key ADDS to the list (unless clicking on a previously selected item)
        //also, shift key does not close the dropdown.
        if (data.shift) {
          this._options.forEach(function(option) {
            if (option.value === value) {
              option.selected = selected === true ? false : true;
            }
          });
        }
        else {
          this._options.forEach(function(option) {
            if (option.value === value) {
              option.selected = selected === "true" ? false : true;
            } else {
              option.selected = false;
            }
          });
         // this.closeDropdown();
        }
    
        this.selectedValues = this.getOptionsArray();
    
      }

      getOptionsArray(){
        var pills = [];
        this._options.forEach(function(element) {
          var interator = 0;
          if (element.selected) {
            pills.push({label:element.label, name:element.value, key: interator++});
          }
        });
        return pills;
      }
    

}


Child Component :

listItems.html



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<template>
    <li onclick={eventHandler} class="slds-listbox__item" role="presentation"  >
        <div class={listStyle} role="option">
          <span class="slds-media__figure">
            <lightning-icon icon-name="utility:check" size="x-small" alternative-text="selected" class="slds-icon-utility-check slds-current-color slds-listbox__icon-selected slds-icon_container"></lightning-icon>
          </span>
          <span class="slds-media__body">
            <span class="slds-truncate no-selection" title={label}>&nbsp;{label}</span>
          </span>
        </div>
      </li>
</template>

listItems.js



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { LightningElement,api,track } from 'lwc';

export default class ListItems extends LightningElement {

  @api key = '';
  @api value = '';
  @api label = '';
  @api selected = false;

  get listStyle() {
    var initial = ' slds-media  slds-listbox__option_plain slds-media_small slds-listbox__option ';
    return this.selected === true ? initial + ' slds-is-selected ' : initial ;
  }

  eventHandler(event) {
    event.preventDefault();
    event.stopPropagation();
    console.log('selcted value..'+this.selected);
    const selectedEvent = new CustomEvent('selected', { detail: {label:this.label,value:this.value,selected:this.selected,shift:event.shiftKey} });
    this.dispatchEvent(selectedEvent);
  }
}

Explanation..

in code we are using Shift key to select multiple options from dropdown. on select option(s) , events is getting fired from child component and being handled on parent component 
handleSelectedClick(event)

one tricky part is handling showing and hiding of drop down list using below two events:
  • onmouseenter
  • onmouseleave

OutPut :

Lets see the output and hope you will like it.

Wednesday, 30 September 2020

ESlint in LWC

 Before we jump into "ESlint" let me explain about "linter"

what is linter ?

A linter is a tool that identifies issues in your code. Running a linter against your code can tell you many things:

  • if the code adheres to a certain set of syntax conventions
  • if the code contains possible sources of problems
  • if the code matches a set of standards you or your team define

It will raise warnings that you, or your tools, can analyze and give you actionable data to improve your code. ESLint is basically a linter for the JavaScript programming language, written in Node.js

ESlint for LWC

In salesforce ESlint is available by default with the Lightning Web Components extension .Salesforce provides specific ESLint rules out of the box for you as a Lightning Web Component developer so that you can write great code. And if you make a mistake, the linting rules help you see it before you deploy your code.

probably you may remember when create a new project, in lwc metadata folder there is file called ".eslintrc" which define the salesforce specific linting rules.




you may have experienced with the Restricted async operation 'setTimeOut' in LWC. lets take a look into below code snippet which is restricted by ESlint due to performance impact.

 
1
2
3
4
5
connectedCallback() {
   setTimeout(() => {
       this.variable = true;
   }, 3000);
} 


The ESLint for Lightning Web Components will complain about setTimeout and setInterval. if you wanted to use it for any reason you can stop this by adding a comment before the line of code .

 
1
2
3
4
5
6
// eslint-disable-next-line @lwc/lwc/no-async-operation
connectedCallback() {
   setTimeout(() => {
       this.variable = true;
   }, 3000);
} 


Hope you find this post informative ..



Getting started with Heroku

I am familiar with the heroku for quite long time, have seen lots of people are interested with it but not sure from where its need to Start...