Showing posts with label Lightning Components. Show all posts
Showing posts with label Lightning Components. Show all posts

Tuesday, August 30, 2022

Custom Salesforce Lightning component to Share Records Manually

There are cases where user manually share records with other users. However, users with whom a record is shared neither gets a notification, nor an email alert. How are users supposed to know a record has been shared with them?

The Share tables also do not support triggers. The only way to handle this use case is to schedule an Apex job and periodically query the table for new shares since the last check.

This is a custom component developed both in Lightning Aura and LWC (the main modal is in Aura, while the supporting components are in LWC). As soon as a record is shared manually with a user, he/she will be notified by email and a Salesforce notification. Currently, this works for practically all the Salesforce standard objects, including Accounts. Additionally, if you want to remove the share entries for a certain record, click the remove icon on the RHS of the table. To utilize this component, make sure to construct a Quick Action.

Since I haven't worked with Aura in a while, I blended LWC and Aura to render this component when it could have been rendered with only LWC. This component can be extended to work with custom objects as well.

To select multiple users, I’ve used a lookup component created by Philippe Ozil. Thanks to him.

Finally, the repo link for this component: https://github.com/iamsonal/share-records

Share This:    Facebook Twitter

Sunday, April 8, 2018

Salesforce Lightning: Fetch data from an external API using Ajax


We will be using a very simple API which takes a GET request formatted as a certain URL, returning some random Chuck Norris jokes...lol, without requiring Apex code. So user will enter a number, and on click of Search button, will make a request to http://api.icndb.com/jokes/random/ API, get the response, parse it and display it on the page.

<aura:application extends="force:slds">

    <aura:attribute name="number" type="Integer" default="5"/>
    <aura:attribute name="html" type="String"/>

    <div class="slds-p-left_large">

        <h2 class="slds-text-heading_large">Chuck Norris Jokes Generator</h2>

        <lightning:input type="number"
                         aura:id="number"
                         label="Enter a number"
                         class="slds-m-around_small slds-size_1-of-8"/>

        <lightning:button variant="brand"
                          label="Get Jokes"
                          class="slds-m-around_medium"
                          onclick="{!c.handleClick }"/>

        <aura:unescapedHtml value="{!v.html}"/>

    </div>

</aura:application>
({
    handleClick : function(component, event, helper) {
        const number = component.find('number').get('v.value');

        if (number) {
            component.set('v.number', number);
            helper.handleAjaxRequest(component, event, helper);
        }

    }
})
({
    handleAjaxRequest : function(component, event, helper) {
        const xhr = new XMLHttpRequest();

        const number = component.get('v.number');

        const url = 'http://api.icndb.com/jokes/random/' + number;

        xhr.open('GET', url, true);

        xhr.onload = function() {
            if(this.status === 200) {
                const response = JSON.parse(this.responseText);

                let output = '<ul class=\'slds-list--dotted\'>';

                if(response.type === 'success') {
                    response.value.forEach(function(joke){
                        output += `<li>${joke.joke}</li>`;
                    });
                }

                output += '<ul>';
                component.set('v.html', output);
            }
        };

        xhr.send();
    }
})

The XHR object has properties and methods associated with it, one of which is open, where we specify type of request we want to make and the URL we want to make it to. The 3rd parameter is set as true as we want to make it asynchronous. The rest of the code is self-explainable.

As a last step, we need to add http://api.icndb.com as a trusted site or else our Lightning component will not be able to make a request and you will notice an error message in console something like this: Refused to connect to 'https://api.icndb.com/jokes/random/6' because it violates the following Content Security Policy directive:....."

So navigate to Setup → CSP Trusted Sites, and add the following entry.


That's it. Enjoy the jokes.

Now authentication is required for a lot of external APIs which are intricate ones using OAuth, and that can get complicated. So just remember that not all APIs give you as much freedom as they do here. You normally have to register your application with their systems and then you will have to authenticate. Different rules, different APIs.
Share This:    Facebook Twitter

Thursday, January 4, 2018

Lightning: Star rating component


This is a simple, highly customizable star rating component.

Attributes:
value: this is the actual value which represents the value of rating
readonly: when set to true, the rating cannot be edited.

To use this component:
<c:StarRating value="2" readonly="false"/>

Refer the github link for the code:
https://github.com/iamsonal/Star-Rating
Share This:    Facebook Twitter

Thursday, November 2, 2017

Saturday, October 21, 2017

Lightning: Popups / Modal


Few months back, I created a single page application to create and edit broker details in a popup. The code for this app is provided in the github link provided in the reference section of this post.

The popup is invoked on click of New or Edit button. This popup gets created dynamically during runtime for which I have created a helper function displayModal.
displayModal : function(component, modalComponentName, modalProperties) {
    $A.createComponent(
        modalComponentName,
        modalProperties,
        function(newModal, status, errorMessage) {
            if (status === "SUCCESS")
                component.set("v.modal", newModal);
            else if (status === "INCOMPLETE")
                console.log("No response from server or client is offline.")
                else if (status === "ERROR")
                    console.log("Error: " + errorMessage);
        }
    );
}

This is how the helper function is called on click of New button
helper.displayModal(component, "c:BrokerPopup", {
    "broker": {'sobjectType':'Broker__c', 
               'Name':'', 
               'Mobile_Phone__c':'', 
               'Email__c':''
              }
});

To exit the popup, I am setting modal attribute of the component to null like below:
component.set("v.modal", null);

References:
Github link: https://github.com/iamsonal/BrokerApp
Share This:    Facebook Twitter

Friday, September 22, 2017

Salesforce Lightning: Google Places Autocomplete Predictions Search



I have created a lookup component which on entering few characters of a location will display location predictions.

There are 3 methods that has been defined in client-side controller:
  • keyPressController: executed when the user enters a search key. I have taken into consideration that the user need to enter at least 3 characters to fetch the location predictions.
  • selectOption: executed when the user selects a value from the list box
  • clear: executed on click of cross icon to remove the selected value from the lookup

To fetch the results, I am calling Place Autocomplete service, a part of the Google Places API Web Service, using the below Apex method, which returns location predictions in response to this HTTP request. For more details about this web service, please check the link in the references section of this post.
@AuraEnabled
public static string getAddressAutoComplete(String input, String types) {
    String url = 'https://maps.googleapis.com/maps/api/place/autocomplete/json?input='
            + EncodingUtil.urlEncode(input, 'UTF-8')
            + '&types=' + types
            + '&key=' + GoogleMapsController.getGoogleMapsAPIKey();
    return GoogleMapsController.getHttp(url);
}

The response which is in JSON format is parsed in Javascript in the helper method of the component as below:
displayOptionsLocation: function (component, searchKey) {
    var action = component.get("c.getAddressAutoComplete");
    action.setParams({
        "input": searchKey,
        "types": '(regions)'
    });

    action.setCallback(this, function (response) {
        var state = response.getState();
        if (state === "SUCCESS") {
            var options = JSON.parse(response.getReturnValue());
            var predictions = options.predictions;
            var addresses = [];
            if (predictions.length > 0) {
                for (var i = 0; i < predictions.length; i++) {
                    addresses.push(
                        {
                            value: predictions[i].types[0],
                            label: predictions[i].description
                        });
                }
                component.set("v.filteredOptions", addresses);
            }
        }
    });
    $A.enqueueAction(action);
}

The look and feel of this component is controlled by $A.util.addClass and $A.util.removeClass methods.

Please check the github link for the component code below:
https://github.com/iamsonal/GoogleMapsAutocomplete

References:
https://developers.google.com/places/web-service/autocomplete
Share This:    Facebook Twitter

Friday, September 8, 2017

Salesforce Lightning: Lookup Component


I created a simple lookup component in Lightning. Its fully based on Lightning Design Systems. You can check the Github link in the reference section. During initialization, the component query for the list of strings (using apex code) to be displayed and store in an attribute. So when the user enters a search keyword, the request won't go to Salesforce again and will query the Javascript string array for the filtered results.

How to use?

From your Lightning component, add a lookup component using the following markup:
<c:DemoLookup placeholder="Enter partner Name" actionMethod="c.getAccountOptions"/>

You need to pass a list of strings which will get displayed in the lookup options when the user enters an input. A sample method is as below:
@AuraEnabled
public static List<String> getAccountOptions() {
    List<String> options = new List<String>();
    for (Account a : [
            SELECT Name
            FROM Account
            LIMIT 100
    ]) {
        options.add(a.Name);
    }
    return options;
}

Reference:
https://github.com/iamsonal/DemoLookup
Share This:    Facebook Twitter

Saturday, September 2, 2017

Lightning: Fetch current location details using Google Maps API

Using Google Maps, I was trying to fetch the current city and other details in a Lightning component using Javascript, but I was getting some error in browser console. I believe that is because of Locker service as described in the reference link. So I had to take a different approach.

But first, we need the latitude and longitude of the user who is accessing the Lightning page. The HTML Geolocation API is used to get the geographical position of a user. Since this can compromise privacy, the position is not available unless the user approves it. The getCurrentPosition() method is used to return the user's position. The example below returns the latitude and longitude of the user's position.

checkGeoLocation : function(component, event, helper) {
    if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(function(position) {
            var lat = position.coords.latitude;
            var lon = position.coords.longitude;
            var action = component.get("c.getCityName");
            action.setParams({
                "latitude": lat,
                "longitude": lon
            });
            action.setCallback(this, function(response) {
                var state = response.getState();
                if (state === "SUCCESS") {
                    var location = response.getReturnValue();
                }
            });
            $A.enqueueAction(action);
        });
    } else {
        console.log('Your browser does not support GeoLocation');
    }
}

To fetch the city details, we need to invoke the Google maps API. We need to send the location details as parameters. The results element in the response needs to be parsed in Apex to extract the desired values. In my case, JsonReader class did the work for me.

Please go through the documentation of Google maps, the link of which has been provided in the reference section. The following fields of the results element are of significance:
1. address_components[]
2. types[]

@AuraEnabled
public static String getCityName (Decimal latitude, Decimal longitude) {
    String url = 'http://maps.googleapis.com/maps/api/geocode/json?latlng=' + latitude + ',' + longitude + '&sensor=true';
    JsonReader jsonobj = new JsonReader(getHttp(url));
    String status = jsonObj.getString('status');
    System.debug(status);
    
    JsonReader addressComponentObj = null;
    String locality= null;
    String adminAreaLevel2= null;
    List<Object> types = null;
    
    for(Object obj : jsonObj.getList('results[0].address_components')) {
        addressComponentObj = new JsonReader(obj);
        types = addressComponentObj.getList('types');
        for(Object typeObj : types) {
            String type = (String) typeObj;
            if (type.equalsIgnoreCase('locality')) {
                locality = addressComponentObj.getString('long_name');
            }
            if (type.equalsIgnoreCase('administrative_area_level_2')) {
                adminAreaLevel2 = addressComponentObj.getString('short_name');
            }
        }
    }
    return (locality + ', ' + adminAreaLevel2);
}

And that's it.


Reference: http://www.rattanpal.com/2017/03/19/salesforce-implement-google-maps-lightning-components/
https://developers.google.com/maps/documentation/geocoding/intro
Share This:    Facebook Twitter

Tuesday, August 1, 2017

Lightning Component for attaching files using input file component

A rough markup of this component using input type file component. Please refer the previous article to get an idea of how the helper JS and the apex class is formed.

<div class="slds-form-element">
    <span class="slds-form-element__label" id="file-selector-id">Attachment</span>
    <div class="slds-form-element__control">
        <div class="slds-file-selector slds-file-selector_files">
            <div class="slds-file-selector__dropzone">
                <input type="file" class="slds-file-selector__input slds-assistive-text" accept="image/png" id="file-upload-input-01" aria-describedby="file-selector-id" aura:id="file" onchange="{!c.showfile}"/>
                <label class="slds-file-selector__body" for="file-upload-input-01">
                    <span class="slds-file-selector__button slds-button slds-button_neutral">
                        <c:svgIcon class="slds-button__icon slds-button__icon_left" xlinkHref="/resource/slds232/assets/icons/utility-sprite/svg/symbols.svg#upload" />
                        Upload Files
                    </span>
                    <span class="slds-file-selector__text slds-medium-show">or Drop Files</span>
                </label>
            </div>
        </div>
    </div>
</div>

showfile:function(component,event,helper){
    helper.show(component,event);
    
    var f = event.target.files[0]; 
    var fileInput = component.find("file").getElement();
    var file = fileInput.files[0];
    
    var reader = new FileReader();
    reader.onloadend = function(e) {
        var contents = e.target.result;
        var base64Mark = 'base64,';
        var dataStart = contents.indexOf(base64Mark) + base64Mark.length;
        var fileContents = contents.substring(dataStart);
        
        helper.upload(component, file, encodeURIComponent(fileContents), function(answer) {
            if (answer) {
                helper.hide(component,event);
            }
            else{
            }
        });
    }
    reader.readAsDataURL(file);
}

Share This:    Facebook Twitter

Lightning Component for attaching files using lightning:input


<aura:component description="FileUploader" controller="FileUploadController">
    <aura:attribute name="fileToBeUploaded" type="Object[]"/>
    <lightning:spinner aura:id="mySpinner" class="slds-hide"/>
    
    <div class=" slds-box">
        <div class="slds-grid slds-wrap">
            <lightning:input aura:id="file-input" type="file"
                             files="{!v.fileToBeUploaded}"
                             onchange="{!c.onFileUploaded}"
                             accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
                             label="Attachment"
                             name="file" multiple="true"/>
        </div>
    </div>
</aura:component>

({
    onFileUploaded:function(component,event,helper){
        helper.show(component,event);
        var files = component.get("v.fileToBeUploaded");
        if (files && files.length > 0) {
            var file = files[0][0];
            var reader = new FileReader();
            reader.onloadend = function() {
                var dataURL = reader.result;
                var content = dataURL.match(/,(.*)$/)[1];
                helper.upload(component, file, content, function(answer) {
                    if (answer) {
                        helper.hide(component,event);
                        // Success
                    }
                    else{
                        // Failure
                    }
                });
            }
            reader.readAsDataURL(file);
        }
        else{
            helper.hide(component,event);
        }
    }
})

({
    upload: function(component, file, base64Data, callback) {
        var action = component.get("c.uploadFile");
        console.log('type: ' + file.type);
        action.setParams({
            fileName: file.name,
            base64Data: base64Data,
            contentType: file.type
        });
        action.setCallback(this, function(a) {
            var state = a.getState();
            if (state === "SUCCESS") {
                callback(a.getReturnValue());
            }
        });
        $A.enqueueAction(action);
    },
    show: function (cmp, event) {
        var spinner = cmp.find("mySpinner");
        $A.util.removeClass(spinner, "slds-hide");
        $A.util.addClass(spinner, "slds-show");
    },
    hide:function (cmp, event) {
        var spinner = cmp.find("mySpinner");
        $A.util.removeClass(spinner, "slds-show");
        $A.util.addClass(spinner, "slds-hide");
    }
})

public class FileUploadController {
    @AuraEnabled
    public static Id uploadFile(String fileName, String base64Data, String contentType) { 
        base64Data = EncodingUtil.urlDecode(base64Data, 'UTF-8');
        Attachment a = new Attachment();
        a.parentId = '0017F000007xoiVQAQ';
        a.Body = EncodingUtil.base64Decode(base64Data);
        a.Name = fileName;
        a.ContentType = contentType; 
        insert a;
        System.debug(a.Id);
        return a.Id;
    }
}

NOTE: For demo purpose, I have hardcoded the value of an account '0017F000007xoiVQAQ' in the apex class. In actual scenario, you should get this ID from the component.
Share This:    Facebook Twitter

Friday, July 21, 2017

Including SVG in a Lightning Component

This is a helper Lightning Component to render the Lightning Design System sprited icons.

1. Create svgIcon component.
<aura:component access="global">
  <aura:attribute name="class" type="String" access="global"/>
  <aura:attribute name="xlinkHref" type="String" access="global"/>
  <aura:attribute name="ariaHidden" type="String" default="true" access="global"/>
</aura:component>



({
    render: function(component, helper) {
        var classname = component.get("v.class");
        var xlinkhref = component.get("v.xlinkHref");
        var ariaHidden = component.get("v.ariaHidden");
        
        var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
        svg.setAttribute('class', classname);
        svg.setAttribute('aria-hidden', ariaHidden);
        svg.innerHTML = '<use xlink:href="'+xlinkhref+'"></use>';
        return svg;
    }
})

2. Upload the SLDS zip file as static resource.

3. Use this new component whenever you need an Lightning Design System SVG icon like below:
<c:svgIcon class="slds-icon slds-icon_x-small" xlinkHref="/resource/slds232/assets/icons/utility-sprite/svg/symbols.svg#user" />

Reference: https://www.lightningdesignsystem.com/resources/lightning-svg-icon-component-helper/
Share This:    Facebook Twitter

Friday, July 7, 2017

lightning:spinner component

<aura:application controller="echoController" extends="force:slds">
    <lightning:button label="Toggle" variant="brand" onclick="{!c.showSpinner}"/>
    <div class="exampleHolder">
        <lightning:spinner aura:id="mySpinner" class="slds-hide"/>
    </div>
</aura:application>

({
    showSpinner: function (component, event, helper) {
        helper.showSpinner(component);
        var action = component.get('c.echo');
        action.setCallback(this,function(response){
            var state = response.getState();
            if (state === "SUCCESS") {
                helper.hideSpinner(component);
            }
        });
        $A.enqueueAction(action);
    }
})

({
    showSpinner: function (component, event, helper) {
        var spinner = component.find("mySpinner");
        $A.util.removeClass(spinner, "slds-hide");
    },
    
    hideSpinner: function (component, event, helper) {
        var spinner = component.find("mySpinner");
        $A.util.addClass(spinner, "slds-hide");
    }
})

public class echoController {
    @AuraEnabled
    public static String echo(String message) {
     return message;
    }
}

Share This:    Facebook Twitter

Total Pageviews

My Social Profiles

View Sonal's profile on LinkedIn

Tags

__proto__ $Browser Access Grants Accessor properties Admin Ajax AllowsCallouts Apex Apex Map Apex Sharing AssignmentRuleHeader AsyncApexJob Asynchronous Auth Provider AWS Callbacks Connected app constructor Cookie CPU Time CSP Trusted Sites CSS Custom settings CustomLabels Data properties Database.Batchable Database.BatchableContext Database.query Describe Result Destructuring Dynamic Apex Dynamic SOQL Einstein Analytics enqueueJob Enterprise Territory Management Enumeration escapeSingleQuotes featured Flows geolocation getGlobalDescribe getOrgDefaults() getPicklistValues getRecordTypeId() getRecordTypeInfosByName() getURLParameters Google Maps Governor Limits hasOwnProperty() Heap Heap Size IIFE Immediately Invoked Function Expression Interview questions isCustom() Javascript Javascript Array jsForce Lightning Lightning Components Lightning Events lightning-record-edit-form lightning:combobox lightning:icon lightning:input lightning:select LockerService Lookup LWC Manual Sharing Map Modal Module Pattern Named Credentials NodeJS OAuth Object.freeze() Object.keys() Object.preventExtensions() Object.seal() Organization Wide Defaults Override PDF Reader Performance performance.now() Permission Sets Picklist Platform events Popup Postman Primitive Types Profiles Promise propertyIsEnumerable() prototype Query Selectivity Queueable Record types Reference Types Regex Regular Expressions Relationships Rest API Rest Operator Revealing Module Pattern Role Hierarchy Salesforce Salesforce Security Schema.DescribeFieldResult Schema.DescribeSObjectResult Schema.PicklistEntry Schema.SObjectField Schema.SObjectType Security Service Components Shadow DOM Sharing Sharing Rules Singleton Slots SOAP API SOAP Web Services SOQL SOQL injection Spread Operator Star Rating stripInaccessible svg svgIcon Synchronous this Token Triggers uiObjectInfoApi Upload Files VSCode Web Services XHR
Scroll To Top