Pages

Showing posts with label LWC. Show all posts
Showing posts with label LWC. Show all posts

Saturday, August 1, 2026

Lightning Web Components Interview Questions- Part 5


36. What is the lightning/graphql module, and how is it different from the older lightning/uiGraphQLApi?

lightning/graphql is a newer LWC module for working with Salesforce’s GraphQL API, and it supersedes lightning/uiGraphQLApi. Its most notable addition is support for dynamic queries — instead of a fully static query string, you can build part of the query at runtime using JavaScript string interpolation (${variableName}) inside the gql tagged template literal, and the variable’s value is resolved and inserted into the query when it runs. This makes a single component’s query genuinely reusable across different objects or filter conditions, rather than needing a hardcoded query per use case.

import { gql } from 'lightning/graphql';

const query = gql`
  query getRecords {
    uiapi {
      query {
        ${this.objectName} {
          edges {
            node {
              Id
              Name { value }
            }
          }
        }
      }
    }
  }
`;

37. How do GraphQL mutations work in LWC as of Spring ’26?

Before Spring ’26, the GraphQL wire adapter in LWC was read-only — you could query data, but any create, update, or delete still had to go through Apex or lightning/uiRecordApi. Spring ’26 added an executeMutation function, imported from lightning/graphql, which lets you perform create/update/delete operations imperatively, directly from your JavaScript, using the same gql template literal syntax as queries.

import { executeMutation, gql } from 'lightning/graphql';

const UPDATE_ACCOUNT = gql`
  mutation UpdateAccount($input: AccountUpdateInput!) {
    uiapi {
      AccountUpdate(input: $input) {
        Record {
          Id
          Name { value }
        }
      }
    }
  }
`;

async function updateAccount(accountId, newName) {
    await executeMutation({
        query: UPDATE_ACCOUNT,
        variables: { input: { Id: accountId, Name: newName } }
    });
}

This matters practically because it cuts out an entire layer of Apex for straightforward CRUD operations — fewer controller classes, fewer test classes, and a component that can own its full data lifecycle (read and write) through one GraphQL surface instead of mixing GraphQL reads with Apex writes.

38. What is TypeScript support in LWC, and is it production-ready?

As of Spring ’26, TypeScript support for LWC is still in developer preview — not generally available. That’s an important nuance for an interview: candidates should know it exists, but also know not to overstate its maturity.

The most concrete addition is the @salesforce/lightning-types npm package, which provides official TypeScript type definitions for base components (like lightning-input or lightning-button). This replaces the custom type-definition files many teams previously wrote by hand. Salesforce DX’s MCP server also includes a tool that helps convert existing JavaScript-based LWC to TypeScript, which is meant to accelerate migration for larger codebases rather than requiring a full manual rewrite.

import type { LightningInput } from '@salesforce/lightning-types';

handleChange(event: CustomEvent) {
    const input = event.target as LightningInput;
    console.log(input.value, input.validity.valid);
}

39. What is Local Dev (Beta), and how does it change the LWC development workflow?

Local Dev is a local, single-component preview environment. As of Winter ’26, it supports access to platform modules directly during local preview — including Lightning Data Service wire adapters, @salesforce scoped modules, and Apex controllers. Previously, a lot of local component preview tooling couldn’t reach these platform-connected pieces, which meant developers had to deploy to a sandbox just to see how a component behaved against real data or Apex logic. Local Dev closes that gap, at least for single-component iteration.

40. What are complex template expressions in LWC, and what do they unlock?

Historically, LWC templates only allowed simple property references or getters — no inline logic beyond that. A beta feature expands what a template expression can contain, allowing more complex JavaScript expressions directly in the template rather than requiring every computed value to be pushed into a getter in the JavaScript class. This is a quality-of-life change more than an architectural one, but it reduces boilerplate for components with a lot of small derived display values.

41. Can Lightning Web Components be used as local actions in screen flows now?

Yes — as of Winter ’26, LWC can be used for local actions in screen flows. Local actions run entirely client-side (in the browser), which means they can access browser-native functionality using JavaScript without a server round-trip. Because a local action doesn’t need to render a visible UI element in the flow itself, components built for this purpose should use a blank HTML template — the component does its work invisibly as part of the flow’s execution.

42. What is SLDS 2.0, and does it break existing LWC styling?

SLDS 2.0 became generally available as of Winter ’26. It introduces updated component designs, styling hooks, and utilities, along with a beta dark mode option for custom themes (currently limited to the Starter edition). For LWC specifically, the practical concern is the design token variable syntax: the older --lwc- camelCase prefix syntax used in SLDS 1 doesn’t carry over the same way in SLDS 2, so components relying on that older syntax should migrate to the equivalent global styling hook rather than assuming it will keep working unchanged. Salesforce also improved the SLDS linter with additional validation rules and quick fixes specifically to help with this transition.

43. How does Lightning Out 2.0’s authentication model compare to the original Lightning Out (Beta)?

This is covered in depth in Part 3 of this series, but as a quick recap for anyone reading this part standalone: the original Lightning Out (Beta) used session-ID-based authentication passed to the external host page, which was fragile and offered weaker isolation. Lightning Out 2.0, generally available since Winter ’26, instead uses OAuth 2.0 combined with the UI Bridge API — the external app requests authorization, receives a token, and exchanges it for a short-lived, purpose-specific Frontdoor URL used to render the embedded component inside an isolated iframe with its own shadow DOM.

44. What is Lightning Web Security, and why does it matter for a 2026 interview specifically?

Also covered in depth in Part 2, but worth restating here because it’s one of the two most authentication/security-relevant topics on this list: Lightning Web Security (LWS) replaced Lightning Locker as Salesforce’s client-side security architecture, became the default for new orgs in Winter ’23, and reached full general availability for both LWC and Aura components in Summer ’23. It uses a JavaScript sandbox with runtime “distortions” instead of Locker’s secure wrapper objects, which allows custom elements and third-party web components that Locker used to block, while still preventing unsafe cross-namespace access.

45. If an interview focuses heavily on security and authentication, what should a candidate prioritize from this list?

Out of everything covered in this part, two topics carry the most weight for a security- or architecture-focused round: Lightning Web Security (client-side sandboxing and its replacement of Locker) and Lightning Out 2.0’s OAuth-based authentication model (replacing the old session-ID approach for embedding components externally). Being able to explain not just what changed, but why the new approach is more secure — shorter-lived, more narrowly scoped credentials, and stronger isolation boundaries — tends to distinguish a candidate who has actually kept up with the platform from one reciting older material.

Friday, July 31, 2026

How to Convert CSV Response in to Excel sheet with Download by using LWC

How to Convert CSV Response into an Excel Sheet with Download by Using LWC

Reporting screens often need two things at once: a readable table on screen, and a downloadable file the user can open in Excel. Rather than building two separate data pipelines, you can fetch a single CSV string from Apex, render it as a table in your Lightning Web Component, and let the same string be downloaded as a .csv file with one click.

This post walks through a working pattern for exactly that — fetching report data, parsing raw CSV text into a JavaScript object array, displaying it in the UI, and exporting it back out as a downloadable spreadsheet.

Step 1: Fetch the Report Data from Apex

The component calls an imperative Apex method, passing along whatever filters the user has selected (in this case, a searchWrapper object holding search parameters):

getReportData() {
    this.isLoading = true;

    getReportData({
        searchParams: this.searchWrapper
    })
    .then(result => {
        this.csvdata = result;
        this.formatReportData(result);
        this.isLoading = false;
    })
    .catch(error => {
        console.error('getReportData error', error);
        this.isLoading = false;
    });
}

The Apex method is expected to return a raw CSV string (header row + data rows). That string is stored as-is in this.csvdata — this is important, because it's what gets handed off later for the file download, untouched by any formatting done for display purposes.

Step 2: Turn the CSV String into an Array of Objects

To show the data in a table (or any other UI component), the raw CSV text needs to become structured JavaScript objects. The formatReportData method handles this:

formatReportData(info) {
    var resultInfo = info.trim().split(/\r?\n|\r/);
    var headerColumns = resultInfo[0].replaceAll(' ', '_').trim().split(',');
    var dataObj = [];

    for (let index = 0; index < resultInfo.length; ++index) {
        const element = resultInfo[index];
        if (index !== 0) {
            var rowData = this.CSVtoArray(element);
            var obj = {};
            for (let colIndex = 0; colIndex < rowData.length; ++colIndex) {
                obj[headerColumns[colIndex]] = rowData[colIndex];
            }
            dataObj.push(obj);
        }
    }

    this.reportData = dataObj;
    this.showDataResult = dataObj.length > 0;
}

A few things worth noting here:

  • Line splitting is resilient. split(/\r?\n|\r/) handles CSV files coming from Windows (\r\n), Mac (\r), or Unix (\n) line endings, which matters since exported reports can originate from different systems.
  • Header names are sanitized. Spaces in column headers are replaced with underscores, making them safe to use as JavaScript object keys.
  • The first row is skipped when building data rows, since it only contains column headers.
  • showDataResult acts as a simple flag to conditionally show or hide the results table in the template, depending on whether any rows came back.

Step 3: Parse Each Row Safely with a Regex-Based CSV Splitter

A simple .split(',') breaks as soon as a field contains a comma inside quotes (for example, "Doe, John"). To avoid that, the component uses a small, well-tested regex-based parser:

CSVtoArray(text) {
    var re_valid = /^\s*(?:'[^'\\]*(?:\\[\S\s][^'\\]*)*'|"[^"\\]*(?:\\[\S\s][^"\\]*)*"|[^,'"\s\\]*(?:\s+[^,'"\s\\]+)*)\s*(?:,\s*(?:'[^'\\]*(?:\\[\S\s][^'\\]*)*'|"[^"\\]*(?:\\[\S\s][^"\\]*)*"|[^,'"\s\\]*(?:\s+[^,'"\s\\]+)*)\s*)*$/;
    var re_value = /(?!\s*$)\s*(?:'([^'\\]*(?:\\[\S\s][^'\\]*)*)'|"([^"\\]*(?:\\[\S\s][^"\\]*)*)"|([^,'"\s\\]*(?:\s+[^,'"\s\\]+)*))\s*(?:,|$)/g;

    if (!re_valid.test(text)) return null;

    var a = [];
    text.replace(re_value, function(m0, m1, m2, m3) {
        if (m1 !== undefined) a.push(m1.replace(/\\'/g, "'"));
        else if (m2 !== undefined) a.push(m2.replace(/\\"/g, '"'));
        else if (m3 !== undefined) a.push(m3);
        return '';
    });

    if (/,\s*$/.test(text)) a.push('');
    return a;
}

This function:

  1. First validates the line against re_valid to confirm it's a well-formed CSV row — returning null immediately if not.
  2. Then walks through the line with re_value, correctly extracting values that are single-quoted, double-quoted, or unquoted.
  3. Unescapes any backslash-escaped quotes inside quoted values.
  4. Handles the edge case of a trailing empty value (a line ending in a comma).

Because this parser respects quotes, it can safely handle addresses, names, or notes fields that contain embedded commas — something a naive split would corrupt.

Step 4: Let the User Download the Same Data as a CSV File

Since the raw CSV string was preserved in this.csvdata, generating a downloadable file just means creating a temporary link element and triggering a click on it:

getDatasheet() {
    let csv = this.csvdata;
    if (csv == null) { return; }

    var hiddenElement = document.createElement('a');
    hiddenElement.href = 'data:text/csv;charset=utf-8,' + encodeURI(csv);
    hiddenElement.target = '_self';
    hiddenElement.download = 'Export.csv';
    document.body.appendChild(hiddenElement);
    hiddenElement.click();
}

Because the file is saved with a .csv extension and a text/csv MIME type, double-clicking it on most machines opens it directly in Excel or Google Sheets — no server round trip or extra Apex logic required for the export itself.

Step 5: Wiring Up Filter Selections

The component also supports multi-select filters for material and plant, which get converted into comma-separated strings before being sent to Apex:

handleMaterial(event) {
    let selectedMaterial = event.detail
        .map(x => x.selected === true ? x.value : null)
        .filter(v => v !== null);
    this.searchWrapper.material = selectedMaterial.toString();
}

handlePlant(event) {
    let selectedPlant = event.detail
        .map(x => x.selected === true ? x.value : null)
        .filter(v => v !== null);
    this.searchWrapper.plant = selectedPlant.toString();
}

These selections feed directly into searchWrapper, which is passed to getReportData() on the next fetch.

Key Takeaways

  • Keep the raw CSV string intact for downloading, and only transform a copy of it for on-screen display — this avoids double work and keeps the exported file identical to the source data.
  • Use a regex-based CSV parser instead of a plain split(',') whenever fields might contain commas or quoted values.
  • Normalize line endings with split(/\r?\n|\r/) so the parser works regardless of which OS generated the file.
  • Generating a downloadable file client-side with a data:text/csv URI avoids extra server calls — the browser handles the file creation.
  • Toggle a boolean flag (like showDataResult) based on whether parsed data exists, so your template can cleanly show empty states versus populated tables.

With this approach, a single Apex call and a lightweight JavaScript parser give you both an interactive report table and a one-click Excel-ready export — all inside one LWC.

How to Read CSV file and Insert record using Apex/LWC in Salesforce

How to Read CSV File and Insert Records Using Apex/LWC in Salesforce

Uploading data through CSV files is one of the most common requirements in Salesforce projects. Instead of relying on Data Loader or manual imports, you can build a simple Lightning Web Component (LWC) that lets users upload a CSV file directly from the UI, then have an Apex controller parse that file and create or update records automatically.

In this post, we'll walk through a working example that:

  • Lets a user upload a .csv file via lightning-file-upload
  • Reads the uploaded file's content in Apex
  • Parses CSV rows — including values that contain commas inside quotes
  • Uses the parsed data to create, update, and upsert related Salesforce records

Step 1: Build the LWC Upload Component

The front end is intentionally lightweight. It uses the standard lightning-file-upload base component wrapped inside a lightning-card, restricted to .csv files only.

HTML Template

<template>
    <lightning-card title="CSV Uploader">
        <div class="slds-box slds-m-around_medium">
            <lightning-file-upload
                accept={acceptedFormats}
                label="Attach CSV File"
                onuploadfinished={uploadFileHandler}>
            </lightning-file-upload>
        </div>
    </lightning-card>
</template>

JavaScript Controller

import { LightningElement, track } from 'lwc';
import loadCSVData from '@salesforce/apex/CSVController.loadCSVData';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { showToast, handleArraySort } from 'c/cOP_LWC_Utils';

export default class FileUploadExcel extends LightningElement {

    get acceptedFormats() {
        return ['.csv'];
    }

    @track contentDocumentId;
    @track recordCount;

    uploadFileHandler(event) {
        const uploadedFiles = event.detail.files;
        this.contentDocumentId = uploadedFiles[0].documentId;

        loadCSVData({ contentDocumentId: this.contentDocumentId })
            .then((result) => {
                this.recordCount = result;
            })
            .catch((error) => {
                // handle error, e.g. show a toast
            });
    }
}

Once the file finishes uploading, Salesforce automatically stores it as a ContentVersion record and returns a documentId. That ID is passed straight to an Apex method, which does all the heavy lifting.

Step 2: Read the File Content in Apex

The uploaded file is stored as a Blob on the ContentVersion.VersionData field. Since Apex doesn't have a built-in "read as string with encoding" helper for every use case, a small utility method converts the blob into a readable string using a specified character set (here, ISO-8859-1, which is a safe default for most CSV exports):

public static String blobToString(Blob input, String inCharset) {
    String hex = EncodingUtil.convertToHex(input);
    System.assertEquals(0, hex.length() & 1);
    final Integer bytesCount = hex.length() >> 1;
    String[] bytes = new String[bytesCount];
    for (Integer i = 0; i < bytesCount; ++i) {
        bytes[i] = hex.mid(i << 1, 2);
    }
    return EncodingUtil.urlDecode('%' + String.join(bytes, '%'), inCharset);
}

Once you have the string, splitting it into rows is as simple as:

string[] csvFileLines = data.split('\n');

Step 3: Handle Commas Inside Quoted Values

A naive split(',') breaks down the moment a CSV value itself contains a comma — for example, an address field like "Springfield, IL". To handle this correctly, a helper method temporarily replaces quotes and internal commas with placeholder tokens (:quotes: and :comma:), performs the split, and then restores the original characters:

public static string csvdata(string csinfo) {
    String csvLine = csinfo;
    Integer startIndex;
    Integer endIndex;

    while (csvLine.indexOf('"') > -1) {
        if (startIndex == null) {
            startIndex = csvLine.indexOf('"');
            csvLine = csvLine.substring(0, startIndex) + ':quotes:' + csvLine.substring(startIndex + 1);
        } else if (endIndex == null) {
            endIndex = csvLine.indexOf('"');
            csvLine = csvLine.substring(0, endIndex) + ':quotes:' + csvLine.substring(endIndex + 1);
        }

        if (startIndex != null && endIndex != null) {
            String sub = csvLine.substring(startIndex, endIndex).replaceAll(',', ':comma:');
            csvLine = csvLine.substring(0, startIndex) + sub + csvLine.substring(endIndex);
            startIndex = null;
            endIndex = null;
        }
    }
    return csvLine;
}

Each field is later restored with .replaceAll(':quotes:', '').replaceAll(':comma:', ',') before it's assigned to a record field.

Step 4: Map CSV Rows to Salesforce Records

With clean, split rows in hand, the controller builds sets and maps to minimize SOQL queries — a best practice to avoid governor limit issues when processing many rows:

set<string> supplierid = new set<string>();
map<Decimal, list<string>> mapincvdata = new map<Decimal, list<string>>();

for (Integer i = 1; i < csvFileLines.size(); i++) {
    string row = CSVController.csvdata(csvFileLines[i]);
    string[] csvRecordData = row.split(',');
    supplierid.add(csvRecordData[0]);
    mapincvdata.put(Decimal.valueOf(csvRecordData[0]), csvRecordData);
}

A single query then retrieves all matching Account records at once:

map<string, id> acMap = new map<string, id>();
for (Account acc : [SELECT Id, SupplierID__c FROM Account WHERE SupplierID__c IN :supplierid]) {
    acMap.put(acc.SupplierID__c, acc.Id);
}

Step 5: Create, Update, and Upsert Records

From here, the controller builds three separate collections depending on the business need:

  • Program_Association__c records are created fresh for each row, using an external key (SupplierNProgramId__c) so the same upsert call can safely run multiple times without creating duplicates.
  • InfSupplierDetail__c records are updated with new association start/end dates pulled from the CSV.
  • InfRequestInfo__c records are updated with the relevant program ID, using a set to avoid processing the same request twice.
if (!updateSupplier.isEmpty()) {
    update updateSupplier;
}
if (!updateRequestProgramid.isEmpty()) {
    update updateRequestProgramid;
}
if (!lstProgramAssociationsToUpsert.isEmpty()) {
    upsert lstProgramAssociationsToUpsert SupplierNProgramId__c;
}

Finally, the method returns a short summary string so the LWC can display feedback to the user:

string res = 'Total Record: ' + (csvFileLines.size() - 1) +
             '---Supplier Updated:' + updateSupplier.size() +
             '--Program Association Created:' + lstProgramAssociationsToUpsert.size() +
             '---Request Updated: ' + updateRequestProgramid.size();
return res;

A Simpler Visualforce Alternative

If you don't need the modern LWC experience, the same idea can be done with a classic Visualforce controller. This trimmed-down version reads an uploaded file and creates Account records directly from each CSV column:

public Pagereference ReadFile() {
    try {
        nameFile = blobToString(contentFile, 'ISO-8859-1');
        filelines = nameFile.split('\n');
        accstoupload = new List<Account>();

        for (Integer i = 1; i < filelines.size(); i++) {
            String[] inputvalues = filelines[i].split(',');
            Account a = new Account();
            a.Name = inputvalues[0];
            a.ShippingStreet = inputvalues[1];
            a.ShippingCity = inputvalues[2];
            a.ShippingState = inputvalues[3];
            a.ShippingPostalCode = inputvalues[4];
            a.ShippingCountry = inputvalues[5];
            accstoupload.add(a);
        }
    } catch (Exception e) {
        ApexPages.addMessage(new ApexPages.Message(ApexPages.severity.ERROR,
            'An error has occurred reading the CSV file: ' + e.getMessage()));
    }

    try {
        insert accstoupload;
    } catch (Exception e) {
        ApexPages.addMessage(new ApexPages.Message(ApexPages.severity.ERROR,
            'An error has occurred inserting the records: ' + e.getMessage()));
    }
    return null;
}

This works fine for straightforward files without embedded commas or quotes, but it lacks the more robust CSV parsing shown in the LWC/Apex example above.

Key Takeaways

  • Use lightning-file-upload for a modern, drag-and-drop-friendly upload experience.
  • Convert the uploaded Blob to a string with the correct character encoding before parsing.
  • Never assume a plain split(',') is safe — always account for quoted fields containing commas.
  • Batch your SOQL queries using sets and maps instead of querying inside loops.
  • Use external IDs with upsert so repeated file uploads don't create duplicate records.
  • Wrap both the parsing and DML logic in try/catch blocks so partial failures are reported clearly to the user.

With this pattern, you get a reusable, safe, and user-friendly way to bulk-load data into Salesforce straight from a CSV file — no Data Loader required.

Saturday, April 27, 2024

How to Share JavaScript Code in LWC

 In Lightning Web Components (LWC), there are two primary patterns for code sharing:

  1. Module Pattern:

    • Usage: This pattern involves creating reusable JavaScript modules that can be imported into LWC components.
    • Implementation: You create separate JavaScript files containing reusable code, then import these modules into your LWC components using the ES6 import statement.
    • Benefits: Encourages modularity, reusability, and separation of concerns. It allows you to organize your codebase effectively and promotes maintainability.
    • Example: As described in the previous response, creating a JavaScript module (sharedUtils.js) and importing it into components.

  2. Base Components Pattern:

    • Usage: This pattern involves creating base components that encapsulate common functionality and can be extended or customized by other components.
    • Implementation: You create base components that contain common logic or UI elements. Other components can then extend these base components to inherit their functionality.
    • Benefits: Promotes code reuse and consistency across the application. It simplifies development by providing a standardized way to implement common features.
    • Example: You might create a base component for a custom modal dialog that includes methods for showing and hiding the modal. Other components can extend this base component to create specific modal instances with customized content.

The first pattern is used in this instance. Only myComponent.js has the ability to import code from myFunction.js and utils.js.


lwc

└───myWgComponent

├──myWgComponent.html

├──myWgComponent.js

├──myWgComponent.js-meta.xml

├──myWgFunction.js

└──wgutils.js

 Utilize relative paths when importing the code.

// myComponent.js

import { getMonthlyAmount, calculateMonth} from ‘./myWgfunction;

import WgCalender from ‘./wgutils; 

Another illustration is a service component (library). The name of the folder and one JavaScript file must match. The name in this instance is wgUtils. 

lwc

├──wgUtils

├──wgUtils.js

└──wgUtils.js-meta.xml

└───wgComponent

├──wgComponent.html

├──wgComponent.js

└──wgComponent.js-meta.xml

 

For importing the code into other components, use c/componentName syntax.

// wgComponent.js

import { getOptions, calculatePayment } from ‘c/wgUtils’;


Both patterns have their strengths and are suitable for different scenarios. The choice between them depends on factors such as the complexity of the shared functionality, the level of customization required, and the architecture of your application. Using a combination of both patterns can often provide a flexible and scalable approach to code sharing in LWC.


Thursday, January 4, 2024

Issue in Lightning-progress-indicator in lwc

 Hello all today i will share one inserting issue, when we used conditional rendering within  

Lightning-progress-indicator


ProgressIndicatorBasic.html

<template>

    <p>

        A progress indicator displays the steps in a process. All steps preceding the step specified by currentStep are marked completed.

    </p>

    <lightning-progress-indicator current-step="3" type="base" has-error="true" variant="base">

        <lightning-progress-step label="Step 1" value="1"></lightning-progress-step>

     <template if:true={areDetailsVisible}>

            <lightning-progress-step label="Step 2" value="2"></lightning-progress-step>

        </template>

        <lightning-progress-step label="Step 3" value="3"></lightning-progress-step>

        <lightning-progress-step label="Step 4" value="4"></lightning-progress-step>

    </lightning-progress-indicator>

</template>

ProgressIndicatorBasic.js

import { LightningElement } from 'lwc';


export default class ProgressIndicatorBasic extends LightningElement {}


Issue: As per the above code if  <template if:true={areDetailsVisible}> is true then Lightning-progress-indicator step will

correctly otherwise current steps selection will display correctly.

Updated code:-

<template>

    <p>

        A progress indicator displays the steps in a process. All steps preceding the step specified by currentStep are marked completed.

    </p>

   <template if:true={areDetailsVisible}>

<lightning-progress-indicator current-step="3" type="base" has-error="true" variant="base">

        <lightning-progress-step label="Step 1" value="1"></lightning-progress-step>

        <lightning-progress-step label="Step 2" value="2"></lightning-progress-step>

        <lightning-progress-step label="Step 3" value="3"></lightning-progress-step>

        <lightning-progress-step label="Step 4" value="4"></lightning-progress-step>

    </lightning-progress-indicator>

<template>

<template if:false={areDetailsVisible}>

<lightning-progress-indicator current-step="3" type="base" has-error="true" variant="base">

        <lightning-progress-step label="Step 1" value="1"></lightning-progress-step>

       <lightning-progress-step label="Step 3" value="3"></lightning-progress-step>

        <lightning-progress-step label="Step 4" value="4"></lightning-progress-step>

    </lightning-progress-indicator>

<template>


</template>



Lightning-progress-indicator in lwc

 A lightning-progress-indicator component displays steps horizontally. It indicates the number of steps in a given process, the current step, as well as prior steps which is completed.

Steps are created using lightning-progress-step component along with label and value attributes. The current step is specified using the current-step attribute, The current step must match one of the value attributes on a lightning-progress-step component as shown below.

  • Set type="base" to create a component that implements the progress indicator blueprint in the Lightning Design System. 

          A progress indicator component communicates to the user the progress of a particular process.
     

  • Set type="path" to create a component that implements the path blueprint in the Lightning Design System. 
          The Path communicates to the user the progress of a particular process.

  • If the type is not specified, the default type base is used. 

lightningprogressindicator.html

<template>
    <p>
        A progress indicator displays the steps in a process. All steps preceding the step specified by currentStep are marked completed.
    </p>
    <lightning-progress-indicator current-step="3" type="base" has-error="true" variant="base">
        <lightning-progress-step label="Step 1" value="1"></lightning-progress-step>
        <lightning-progress-step label="Step 2" value="2"></lightning-progress-step>
        <lightning-progress-step label="Step 3" value="3"></lightning-progress-step>
        <lightning-progress-step label="Step 4" value="4"></lightning-progress-step>
    </lightning-progress-indicator>
</template>

lightningprogressindicator.js

import { LightningElement } from 'lwc';

export default class lightningprogressindicator extends LightningElement {}

Monday, November 27, 2023

How to use Google reCAPTCHA v3 in Lightning Web Component

We have many applications that do require some authentication to prevent the attack vectors. To stop flooding the application with unnecessary attacks(eg. Robots running the scripts), we have to adopt some sort of authentication mechanism to regularize the application access. Google reCAPTCHA can help in identifying if the requests are coming from human or not


Google Configuration

  1. Go to https://www.google.com/recaptcha
  2. Click on the ‘Admin console’ button
  3. Click the ‘+’ create icon if you already have sites configured
  4. Enter a Label and select reCAPTCHA v3, v2 Checkbox or v2 Invisible.
  5. Add your custom or force.com community domain. (You can also add additional domains so that it will function in Experience Builder)
  6. Accept the terms of service and click the Submit button

Screen Shot 2020-04-19 at 3.24.06 PM.png
You will need to use your resulting keys in the code examples below. The keys are only valid for the type of reCAPTCHA selected during creation. If you want to test all 3 examples below, you must create each type in the admin console.
Screen Shot 2020-04-18 at 12.47.50 PM.png Site Key - This public key is intended to be exposed on your site and should be pasted directly in the JavaScript Head Markup examples (replace text ‘reCAPTCHA_site_key’).

Secret Key - This private key should live on the server-side only and be stored in a Custom Setting, Custom Metadata Type or Apex class (replace text ‘reCAPTCHA_secret_key’).

 

Experience Builder Settings

You will see Content Security Policy (CSP) errors as you implement the reCAPTCHA code examples. In Experience Builder → Settings → Security, add the trusted sites shown below and click the ‘Whitelist’ button as items show in the ‘CSP Errors’ list.
Screen Shot 2020-04-18 at 2.16.33 PM.png
 

There are 4 steps to integrate reCAPTCHA v3 to a Lightning Web Component

1. Create html static resource with reCAPTCHA

reCAPTCHAv3.html

<html>
    <head>
        <title></title>reCAPTCHA html resource</title>
        <script src="https://www.google.com/recaptcha/api.js?render=reCAPTCHA_site_key"></script>
    </head>
    <body>
        <input type="hidden" name="recaptcha_response" id="recaptchaResponse"/>
        <script type="text/javascript">
            grecaptcha.ready(function() {
                var reCAPTCHA_site_key = "reCAPTCHA_site_key";
                grecaptcha.execute('reCAPTCHA_site_key', {action: 'submit'}).then(function(token) {
                    recaptchaResponse.value = token;
                    if (token == "") {
                        parent.postMessage({ action: "getCAPCAH", callCAPTCHAResponse : "NOK"}, "*");
                    } else {
                        parent.postMessage({ action: "getCAPCAH", callCAPTCHAResponse : token}, "*");
                    }
                });
            }
        </script>
    </body>
</html>

reCAPTCHAv3.resource-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<StaticResource xmlns="http://soap.sforce.com/2006/04/metadata">
    <cacheControl>Public</cacheControl>
    <contentType>text/html</contentType>
</StaticResource>

2. Create Lightning Web Component with an iframe to load the static resource

myLWC.html

<template>
    <iframe src={navigateTo} name="captchaFrame" onload={captchaLoaded}></iframe>
</template>

3. Create the Javascript controller for the Lightning Web Component

myLWC.js

import { LightningElement, track, api } from 'lwc';
import pageUrl from '@salesforce/resourceUrl/reCAPTCHAv3';
import isReCAPTCHAValid from '@salesforce/apex/reCAPTCHAv3ServerController.isReCAPTCHAValid';

export default class GoogleCapatcha extends LightningElement {
    @api formToken;
    @api validReCAPTCHA = false;

    @track navigateTo;
    captchaWindow = null;

    constructor(){
        super();
        this.navigateTo = pageUrl;
    }

    captchaLoaded(evt){
        var e = evt;
        console.log(e.target.getAttribute('src') + ' loaded');
        if(e.target.getAttribute('src') == pageUrl){

            window.addEventListener("message", function(e) {
                if (e.data.action == "getCAPCAH" && e.data.callCAPTCHAResponse == "NOK"){
                    console.log("Token not obtained!")
                } else if (e.data.action == "getCAPCAH" ) {
                    this.formToken = e.data.callCAPTCHAResponse;
                    isReCAPTCHAValid({tokenFromClient: formToken}).then(data => {
                        this.validReCAPTCHA = data;
                    });
                }
            }, false);
        } 
    }

}

4. Create Apex class to handle the server-side verification

reCAPTCHAv3ServerController.cls

public with sharing class reCAPTCHAv3ServerController {
    public reCAPTCHAv3ServerController(){

    }


    @AuraEnabled
    public static Boolean isReCAPTCHAValid(String tokenFromClient) {
        String SECRET_KEY = 'reCAPTCHA_secret_key';
        String RECAPTCHA_SERVICE_URL = 'https://www.google.com/recaptcha/api/siteverify';
        Http http = new Http();

        HttpRequest request = new HttpRequest();

        request.setEndpoint(RECAPTCHA_SERVICE_URL + '?secret=' + SECRET_KEY + '&response' + tokenFromClient);
        request.setMethod('POST');
        request.setHeader('Content-Length', '0');
        HttpResponse response = http.send(request);

        Map<String, Object> mapOfBody = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());

        Boolean success = (Boolean) mapOfBody.get('success');

        return success;
    }
}