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 ..



Monday, 31 August 2020

Dynamically bind variable in Lightning Web Component (LWC)

 Use case

Once we will be working with generic component we may have to bind declared variables  dynamically in code to get expected result .

Based on below example we  can display  account name for an particular 'accountId' 

import { LightningElement, wire } from 'lwc';
import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
import NAME_FIELD from '@salesforce/schema/Account.Name';

export default class Example extends LightningElement {
    @wire(getRecord, { recordId: '0013xx065790ASZ', fields: [NAME_FIELD] })
    account;

     get name() {
        return getFieldValue(this.account.data, NAME_FIELD);
    }

 
    
}

 

Instead of importing the field definition in controller we can dynamically pass  fields array  within getrecord().


Lets have a look into the code.

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
 * @File Name          : dynamicbinding.js
 * @Description        : 
 * @Author             : Swarup Satpti
 * @Group              : 
 * @Last Modified By   : Swarup Satpati
 * @Last Modified On   : 8/31/2020, 5:00:00 PM
**/
import { LightningElement, api, wire, track } from 'lwc';
import { getFieldValue } from 'lightning/uiRecordApi';
import {getRecord} from 'lightning/uiRecordApi';

export default class Chevron extends LightningElement {
    @api objectName = 'Account';
    @api fieldName = 'Name';
    
    /*component with a recordId property is used on a Lightning record page, 
    the page sets the property to the ID of the current record.*/
    @api recordId='001W000000dls6ZIAQ'; 
    @track record;
    @track error;
    @track fieldArray;
    @track fieldValue;


    connectedCallback(){
        console.log("hi..");
        this.fieldArray = [`${this.objectName}.${this.fieldName}`,`${this.objectName}.RecordTypeId`];
    }
    
    /*Use this wire adapter to get a record’s data : 
       Recordtype ID and value of status field pass through API
    */
    @wire(getRecord, { recordId: '$recordId', fields:'$fieldArray'})
    wiredAccount({ error, data }) {
        if (data) {
            this.record = data;
            console.log(JSON.stringify(data));
           // this.fieldvalue=getFieldValue(data,`${this.objectName}.${this.fieldName}`);
            this.error = undefined;
        } else if (error) {
            console.log('error..');

            this.error = error;
            this.record = undefined;
        }
    }

    get name() {
        return getFieldValue(this.record,`${this.objectName}.${this.fieldName}`);
    }
}

  HTML Template

1
2
3
4
5
<template>
    <div class="slds-m-around_medium">
        <p>Account Name: {name}</p>
    </div>
</template>


Code Explanation:

within Js controller variables are dynamically bound using '$' sign as below.

  this.fieldArray = [`${this.objectName}.${this.fieldName}`,`${this.objectName}.RecordTypeId`]

 Finally  Code will return the  value of account name for given account ID .


Hope you got it useful....! cheers ..

Wednesday, 5 August 2020

Custom Chevron component using lightning web component(LWC)

Use case :

we can use sales force standard "Path" component to build chevron on record page showed as below.


                                                                                                                                                                           
But there is an problem 😟   with this approach . As you can see once stage is completed it shows the chevron with right check mark and not displaying the status  and also i don't want user to manually change the status by using "Mark Stage Completed"  button on  right which should not be available to end user.

To achieve this we have to use custom LWC component. Let's get into the code..!

Code:

Chevron.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
/**
 * @File Name          : chevron.js
 * @Description        : 
 * @Author             : Swarup Satpti
 * @Group              : 
 * @Last Modified By   : Swarup satpati
 * @Last Modified On   : 8/3/2020, 5:30:00 PM
**/
import { LightningElement, api, wire, track } from 'lwc';
import { getObjectInfo, getPicklistValues} from 'lightning/uiObjectInfoApi';
import { getFieldValue } from 'lightning/uiRecordApi';
import {getRecord} from 'lightning/uiRecordApi';

export default class Chevron extends LightningElement {
    @api objectName = 'Opportunity';
    @api fieldName = 'StageName';
    /*component with a recordId property is used on a Lightning record page, 
    the page sets the property to the ID of the current record.*/
    @api recordId; 

    @track picklistvalues;
    @track record;
    @track error;
    @track chevrondata=[];
    @track itemList;
    @track fieldValue;
    @track fieldArray;

    recordtypeId;
    index=0;
    isFound=false;
    isfoundindex=0;
    fieldapi;


  connectedCallback(){
    this.fieldArray = [`${this.objectName}.${this.fieldName}`,`${this.objectName}.RecordTypeId`];
    this.fieldapi=`${this.objectName}.${this.fieldName}` ;
    }
    
    /*Use this wire adapter to get a record’s data : 
       Recordtype ID and value of status field pass through API
    */
    @wire(getRecord, { recordId: '$recordId', fields:'$fieldArray'})
    wiredAccount({ error, data }) {
        if (data) {
            this.record = data;
            console.log(JSON.stringify(data));
            this.fieldvalue=getFieldValue(data,`${this.objectName}.${this.fieldName}`);
            this.recordtypeId=this.record.fields.RecordTypeId.value;
            this.error = undefined;
            console.log('selected value..'+this.fieldvalue);
        } else if (error) {
            console.log('error..');

            this.error = error;
            this.record = undefined;
        }
    }

   /*Use this wire adapter to get the picklist values for a specified field.*/

    @wire(getPicklistValues, {
        fieldApiName: '$fieldapi',
        recordTypeId: '$recordtypeId'
    })
    fetchRecordTypeInfo({ data, error }) {
        if (data) {
            console.log('data..'+JSON.stringify(data.values));

           // this.picklistvalues = data.picklistFieldValues.Status_EI__c.values;
              this.picklistvalues = data.values ;

            this.picklistvalues.forEach(item=>{
               // console.log('rec..'+item.value);
                let classType;
                if(this.fieldvalue==item.value){
                    classType = 'slds-path__item slds-is-current slds-is-active';
                    this.isFound=true;
                    this.isfoundindex=this.index;
                }
                else{ 
                   classType='slds-path__item slds-is-incomplete';
                   this.index ++; 
                }
                this.chevrondata.push({
                 stages :item,
                 classType:classType
                });


            });
            if(this.isFound){
                for(let i=0;i<this.isfoundindex;i++){
                   this.chevrondata[i].classType='slds-path__item slds-is-complete';
                }

            }
           
             console.log('chevron data..'+JSON.stringify(this.chevrondata));
           
            //console.log(JSON.stringify(this.picklistvalues));
        }
        else if (error) {
            console.log(" Error  ---> " + JSON.stringify(error));
        }
    }
    
}

chevron.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
<template>
    <div class="slds-path">
        <div class="slds-grid slds-path__track">
          <div class="slds-grid slds-path__scroller-container">
            <div class="slds-path__scroller" role="application">
              <div class="slds-path__scroller_inner">
                <ul class="slds-path__nav" role="listbox" aria-orientation="horizontal">
                  <template for:each={chevrondata} for:item="stageIs" for:index="index">
                    <li key={stageIs} class={stageIs.classType} style="margin-left: 0em !important;" role="presentation">
                        <a aria-selected="flase" class="slds-path__link" href="javascript:void(0);"  role="option" tabindex="0">
                          <span class="slds-path__stage">
                            <!--<svg class="slds-icon slds-icon_x-small" aria-hidden="true">
                              <use xlink:href="/assets/icons/utility-sprite/svg/symbols.svg#check"></use>
                            </svg>
                          -->
                           {stageIs.stages.label}
                            <span class="slds-assistive-text">{stageIs.stages.label}</span>
                          </span>
                          <span class="slds-path__title">{stageIs.stages.label}</span>
                        </a>
                    </li>
                  </template>
                </ul>
            </div>
          </div>
        </div>
        </div>
    </div>
</template>

Metadata

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?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__RecordPage</target>
        <target>lightning__HomePage</target>
        <target>lightningCommunity__Page</target>
        <target>lightning__Tab</target>
    </targets>
</LightningComponentBundle>


Code Explanation:

Within js controller we are using two wire adapters.

getRecord() : by using this method we are  retrieving the present stage value and record type Id for  the current record

getPicklistValues() : dynamically fetching pick list values for the given field Api 
and recordtype.

Have created custom property called 'ClassType' and 'stage' to display 
the chevron in html.

Main tricky part is to set the claastype conditionally based on the current stage.

'slds-path__item slds-is-current slds-is-active' :   for current Stage
'slds-path__item slds-is-incomplete': for all incomplete stages
'slds-path__item slds-is-complete' : for all completed stages


Hope you get it right !

Output :



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...