lightning:carousel example

In this blog, I am going to explain how to use lightning: carousel example. Here I am using to display the user chatter profile image to display as the carousel.A lightning: carousel component displays a series of images in a single container. This example creates a basic carousel with three images. Auto-scrolling is enabled by default, and every image stays active for 5 seconds before moving on to the next one.

Apex Class

public class UserProfileController {

    @AuraEnabled 
    public static List<User> getUserProfiles(){
        return [Select Id,Address ,BannerPhotoUrl,CompanyName,Department,Email,FirstName , LastName ,FullPhotoUrl from User];
    }
}

Lightning Component 

<aura:component controller="UserProfileController">
    <aura:attribute name="users" type="List" />
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    <lightning:carousel >
        <aura:iteration items="{!v.users}" var="user">
            <lightning:carouselImage
                                     src = "{!user.FullPhotoUrl}" 
                                     header = "{!user.FirstName}" 
                                     description = "{!user.AboutMe}" 
                                     alternativeText = "{!user.LastName}"></lightning:carouselImage>
        </aura:iteration>
        
    </lightning:carousel>
    
</aura:component>

 

({
    doInit : function(component, event, helper) {
        var action = component.get("c.getUserProfiles"); 
        action.setCallback(this, function(response){
            var state = response.getState();
            if (component.isValid() && state === "SUCCESS") {
                var retrunRes = response.getReturnValue();
                component.set("v.users" ,retrunRes );
            }
        });
        $A.enqueueAction(action);
    }
})

Simple App for testing

<aura:application extends="force:slds">
    <c:CarouselExample />
</aura:application>

 

 

Usage Of lightning:datatable

Introduction

In this blog, I am going to explain how to use salesforce lightning:datatable component and its features. A lightning:datatable component displays tabular data where each column can be displayed based on the data type .lightning:datatable is not supported on mobile devices. Inline editing is currently not supported. Supported features include

  • Displaying and formatting of columns with appropriate data types
  • Infinite scrolling of rows
  • Header-level actions
  • Row-level actions
  • Resizing of columns
  • Selecting of rows
  • Sorting of columns by ascending and descending order
  • Text wrapping and clipping

Tables can be populated during initialization using the data, columns, and keyField attributes.The table data is loaded using the init handler. Selecting the checkbox enables you to select the entire row of data and triggers the onrowselection event handler. The below code shows how to use the lightning: datatable to initialize the data and columns which are passed by using attributes.

<aura:component>
    <aura:attribute name="data" type="Object"/>
    <aura:attribute name="columns" type="List"/>
    <aura:handler name="init" value="{! this }" action="{! c.init }"/>
    <lightning:datatable data="{! v.data}" 
        columns="{! v.columns}" 
        keyField="id"
        onrowselection="{! c.getSelectedName }"/>
</aura:component>

Here’s the client-side controller that creates the column’s object to their corresponding column data.

 component.set('v.columns', [
            {label: 'Name', fieldName: 'Name', type: 'text',sortable:true ,actions: headerActions},
            {label: 'URL', fieldName: 'URL__c', type: 'url',sortable:true,actions: headerActions},
            {label: 'Author Name', fieldName: 'Author_Name__c', type: 'text',sortable:true},
            {label: 'publish status', fieldName: 'publish_status__c', type: 'text',sortable:true},
            {label: 'Publisher Id', fieldName: 'Publisher_Id__c', type: 'text',sortable:true,actions: headerActions},
            { type: 'action', typeAttributes: { rowActions: actions } } 
        ]);

The below code used to set the data for the data table.

component.set("v.data", a.getReturnValue());

 Using an Apex Controller

Let’s say you want to display data in the Book object. Create an Apex controller that queries the fields you want to display.This apex controller is having different methods that are used in other data table features like total rows and infinite loading and row, header actions.

public class BookController {
    @AuraEnabled
    public static List<Book_Categories__c> getBooksByAllCategories(){
        
        List<Book_Categories__c> categeroy = [Select  Name,(Select Name, Author_Name__c, Book_Categories__c,Publisher_Id__c from Books__r) from Book_Categories__c];
        return categeroy;
    } 
    
    @AuraEnabled
    public static List<Book_Categories__c> getBooksCategories(){
        List<Book_Categories__c> categeroy = [Select  Name,Id from Book_Categories__c];
        return categeroy;
    }
    @AuraEnabled
    public static List<Books__c> getBooks(Integer limits , Integer offsets){
        System.debug('limits'+limits);
        System.debug('offsets'+offsets);
        Integer intlimits = integer.valueof(limits);
        Integer intoffsets = integer.valueof(offsets);
        
        List<Books__c> books = [Select Name,Is_Available__c,CreatedDate,publish_status__c,
                                URL__c ,Author_Name__c, Book_Categories__c,Publisher_Id__c from Books__c Order by Name Limit :intlimits Offset :intoffsets];
        return books;
    } 
    
    @AuraEnabled
    public static void setBookStatus(String status , List<Books__c> books){
        System.debug('--'+status);
        System.debug('--books --'+books);
        for(Books__c b :books){
            b.publish_status__c = status ;
        }
        
        update books;
    } 
    
    @AuraEnabled
    public static Integer getTotalCount(){
        AggregateResult results = [select  count(Id) total  from Books__c ];
        Integer total =(Integer)results.get('total') ; 
        return total;
    } 
    @AuraEnabled
    public static void deleteBooks(String ids ){
        Delete [Select id from Books__c where  id=:ids];
    } 
    
    
}

 

Infinite Scrolling to Load More Rows

Infinite scrolling enables you to load a subset of data and then display more when users scroll to the end of the table. To enable infinite scrolling, set enableInfiniteLoading to true and provide an event handler using onloadmore. By default, data loading is triggered when you scroll down to 20px from the bottom of the table, but the offset can be changed using the loadMoreOffset attribute. Here is the list of attributes need to set for lightning:datatable for enabling the infinity loading.

enableInfiniteLoading="true"
                             loadMoreOffset="{! v.loadMoreOffset }"
                             onloadmore="{! c.loadMoreData }"

Here is the below controller logic

loadMoreData: function (component, event, helper) {
        //Display a spinner to signal that data is being loaded
        event.getSource().set("v.isLoading", true);
        //Display "Loading" when more data is being loaded
        component.set('v.loadMoreStatus', 'Loading');
        helper.fetchData(component, component.get('v.rowsToLoad')).then($A.getCallback(function (data) {
            if (component.get('v.data').length >= component.get('v.totalNumberOfRows')) {
                component.set('v.enableInfiniteLoading', false);
                component.set('v.loadMoreStatus', 'No more data to load');
            } else {
                var currentData = component.get('v.data');
                //Appends new data to the end of the table
                var newData = currentData.concat(data);
                component.set('v.data', newData);
                component.set('v.loadMoreStatus', 'Please wait ');
            }
            event.getSource().set("v.isLoading", false);
        }));
    },
 fetchData: function(component , rows){
        return new Promise($A.getCallback(function(resolve, reject) {
            var currentDatatemp = component.get('c.getBooks');
            var counts = component.get("v.currentCount");
            currentDatatemp.setParams({
                "limits": component.get("v.initialRows"),
                "offsets": counts 
            });
            currentDatatemp.setCallback(this, function(a) {
                resolve(a.getReturnValue());
                var countstemps = component.get("v.currentCount");
                countstemps = countstemps+component.get("v.initialRows");
                component.set("v.currentCount",countstemps);
                
            });
            $A.enqueueAction(currentDatatemp);
            
            
        }));
        
    } ,

 

Header-Level Actions

Header-level actions refer to tasks you can perform on a column of data, such as displaying only rows that meet a criterion provided by the column. You can perform actions on a column and handle them using the onheaderaction event handler.Here is the markup which needs to set while initializing the data table on init.

 var headerActions = [
            {
                label: 'All',
                checked: true,
                name:'All'
            },
            {
                label: 'Completed',
                checked: false,
                name:'Completed'
            },
            {
                label: 'In Completed',
                checked: false,
                name:'In Completed'
            },
            {
                label: 'Pre Order',
                checked: false,
                name:'Pre Order'
            }
        ];
        
            {label: 'Publisher Id', fieldName: 'Publisher_Id__c', type: 'text',sortable:true,actions: headerActions},

 

Below is the controller action that used to handler the header action based on used is selected what type of data user wanted to see like completed or pre-order or in completed books based on the status.

handleHeaderAction: function (cmp, event, helper) {
        
        var actionName = event.getParam('action').name;
        var colDef = event.getParam('columnDefinition');
        var columns = cmp.get('v.columns');
        var activeFilter = cmp.get('v.activeFilter');
        console.log('activeFilter-->'+activeFilter);
        if (actionName !== activeFilter) {
            var idx = columns.indexOf(colDef);
            var actions = columns[idx].actions;
            console.log('actions'+actions)
            actions.forEach(function (action) {
                action.checked = action.name === actionName;
            });
            cmp.set('v.activeFilter', actionName);
            helper.updateBooks(cmp);
            cmp.set('v.columns', columns);
        }
    },

 

Static Row-Level Actions

Row-level actions refer to tasks you can perform on a row of data, such as updating or deleting the row. Static actions apply to all rows on the table. You can perform actions on each row and handle them using the onrowaction event handler.

  var actions = [
            { label: 'Show details', name: 'show_details' },
            { label: 'Delete', name: 'delete' }
        ];
      

            { type: 'action', typeAttributes: { rowActions: actions } } 

You must provide a list of actions to the data of the column, which can be done during initialization. This client-side controller initializes the actions column and handles the actions on each row, displaying the row details and deleting the row when the action is clicked.

  handleRowAction: function (cmp, event, helper) {
        var action = event.getParam('action');
        var row = event.getParam('row');
        switch (action.name) {
            case 'show_details':
                var navEvt = $A.get("e.force:navigateToSObject");
                navEvt.setParams({
                    "recordId": row.Id,
                    "slideDevName": "detail"
                });
                navEvt.fire();
                break;
            case 'delete':
                var rows = cmp.get('v.data');
                var rowIndex = rows.indexOf(row);
                console.log('rowIndex'+rowIndex);
                console.log('rowIndex row'+rows[rowIndex].Id);
                var deleteAct = cmp.get("c.deleteBooks");
                deleteAct.setParams({ ids : rows[rowIndex].Id });
                $A.enqueueAction(deleteAct);
                var toastEvent = $A.get("e.force:showToast");
                toastEvent.setParams({
                    "title": "Success!",
                    "message": "The record has been delete successfully."
                });
                toastEvent.fire();
                rows.splice(rowIndex, 1);
                cmp.set('v.data', rows);
                break;
        }
    },

 

Sorting Data By Column

To enable sorting of row data by a column label, set sortable to true for the column on which you want to enable sorting. Set sortedBy to match the fieldName property on the column. Clicking a column header sorts rows by ascending order unless the defaultSortDirection is changed, and clicking it subsequently reverses the order. Handle the onsort event handler to update the table with the new column index and sort direction.

 

 sortedBy="{! v.sortedBy }"
                             sortedDirection="{! v.sortedDirection }"
                             defaultSortDirection="{! v.defaultSortDirection }"
                             onsort="{! c.updateColumnSorting }"

Below controller and helper code show sorting logics from the component controller.

 // Client-side controller called by the onsort event handler
    updateColumnSorting: function (cmp, event, helper) {
        var fieldName = event.getParam('fieldName');
        var sortDirection = event.getParam('sortDirection');
        // assign the latest attribute with the sorted column fieldName and sorted direction
        cmp.set("v.sortedBy", fieldName);
        cmp.set("v.sortedDirection", sortDirection);
        helper.sortData(cmp, fieldName, sortDirection);
    },
 sortData: function (cmp, fieldName, sortDirection) {
        var data = cmp.get("v.data");
        var reverse = sortDirection !== 'asc';
        //sorts the rows based on the column header that's clicked
        data.sort(this.sortBy(fieldName, reverse))
        cmp.set("v.data", data);
    },
    sortBy: function (field, reverse, primer) {
        var key = primer ?
            function(x) {return primer(x[field])} :
        function(x) {return x[field]};
        //checks if the two rows should switch places
        reverse = !reverse ? 1 : -1;
        return function (a, b) {
            return a = key(a), b = key(b), reverse * ((a > b) - (b > a));
        }
    },

Handling Selected  Rows 

The selectedRows attribute enables programmatic selection of rows, which is useful when you want to preselect rows.If maxRowSelection is set to a value less than the number of selected rows, only the specified number of rows will be selected.

 handleSelect: function (component, event, helper) {
        var arr = component.get('v.data');
        var obj =  component.get("v.selectedRowsList");
        console.log('obj '+JSON.stringify(obj) );
        var selectedButtonLabel = event.getSource().get("v.label");
        console.log('Button label: ' + selectedButtonLabel);
        var updateAction = component.get("c.setBookStatus");
        updateAction.setParams({ status : selectedButtonLabel , books: obj});
        updateAction.setCallback(this, function(a) {
            $A.get('e.force:refreshView').fire();
        });
        $A.enqueueAction(updateAction);
        
        
        
    },
Complete code is here
<aura:component controller="BookController" implements="force:appHostable">
    <!-- attributes -->
    <aura:attribute name="data" type="Object"/>
    <aura:attribute name="columns" type="List"/>
    <aura:attribute name="selectedRowsCount" type="Integer" default="0"/>
    <aura:attribute name="selectedRowsDetails" type="Object" />
    <aura:attribute name="selectedRowsList" type="List" />
    
    <aura:attribute name="maxRowSelection" type="Integer" default="5"/>
    <aura:attribute name="selectedRows" type="List" />
    
    <!--- enableInfiniteLoading  -->
    <aura:attribute name="enableInfiniteLoading" type="Boolean" default="true"/>
    <aura:attribute name="initialRows" type="Integer" default="30"/>
    <aura:attribute name="rowsToLoad" type="Integer" default="10"/>
    <aura:attribute name="totalNumberOfRows" type="Integer" default="10"/>
    <aura:attribute name="loadMoreStatus" type="String" default="Loading .... "/>
    <aura:attribute name="showRowNumberColumn" type="Boolean" default="false"/>
    <aura:attribute name="rowNumberOffset" type="Integer" default="0"/>
    <aura:attribute name="rowsToAdd" type="Integer" default="10"/>
    <aura:attribute name="currentCount" type="Integer" default="10"/>
    
    <aura:attribute name="activeFilter" type="string" default="All" description="The currently selected actions filter"/>
    
    <aura:attribute name="sortedBy" type="String"/>
    <aura:attribute name="sortedDirection" type="String"/>
    <aura:attribute name="defaultSortDirection" type="String"/>
    
    <!-- handlers-->
    <aura:handler name="init" value="{! this }" action="{! c.doInit }"/>
    <div class="my-custom-background">
        <lightning:button label="Complete"  variant="brand" onclick="{!c.handleSelect}"/>
        <lightning:button label="In Completed"  variant="brand" onclick="{!c.handleSelect}"/>
        <lightning:button label="Pre Order"  variant="brand" onclick="{!c.handleSelect}"/>
        
    </div>
    <!-- the container element determine the height of the datatable -->
    <div style="height: 600px">
        <h1> Total Rowns : {! v.totalNumberOfRows}</h1>
        <h1>Selected Rows: {! v.selectedRowsCount }</h1>
        <h1>Selected Objects: {! v.selectedRowsDetails }</h1>
        <h1>Current Offset : {! v.currentCount }</h1>
        
        
        <lightning:datatable columns="{! v.columns }"
                             data="{! v.data }"
                             keyField="id"
                             showRowNumberColumn="true"
                             rowNumberOffset="0"
                             onrowaction="{! c.handleRowAction }"
                             selectedRows="{! v.selectedRows }"
                             maxRowSelection="{! v.maxRowSelection }"
                             onrowselection="{! c.updateSelectedText }"
                             enableInfiniteLoading="true"
                             loadMoreOffset="{! v.loadMoreOffset }"
                             onheaderaction="{! c.handleHeaderAction }"
                             sortedBy="{! v.sortedBy }"
                             sortedDirection="{! v.sortedDirection }"
                             defaultSortDirection="{! v.defaultSortDirection }"
                             onsort="{! c.updateColumnSorting }"
                             onloadmore="{! c.loadMoreData }"/>
    </div>
    {! v.loadMoreStatus }
    
</aura:component>

 

Controller code
({
    doInit : function(component, event, helper) {
        
        var totalCnt = component.get("c.getTotalCount");
        totalCnt.setCallback(this, function(a) {
            component.set("v.totalNumberOfRows", a.getReturnValue());
        });
        $A.enqueueAction(totalCnt);
        
        
        var actions = [
            { label: 'Show details', name: 'show_details' },
            { label: 'Delete', name: 'delete' }
        ];
        var headerActions = [
            {
                label: 'All',
                checked: true,
                name:'All'
            },
            {
                label: 'Completed',
                checked: false,
                name:'Completed'
            },
            {
                label: 'In Completed',
                checked: false,
                name:'In Completed'
            },
            {
                label: 'Pre Order',
                checked: false,
                name:'Pre Order'
            }
        ];
        
        component.set('v.columns', [
            {label: 'Name', fieldName: 'Name', type: 'text',sortable:true ,actions: headerActions},
            {label: 'URL', fieldName: 'URL__c', type: 'url',sortable:true,actions: headerActions},
            {label: 'Author Name', fieldName: 'Author_Name__c', type: 'text',sortable:true},
            {label: 'publish status', fieldName: 'publish_status__c', type: 'text',sortable:true},
            {label: 'Publisher Id', fieldName: 'Publisher_Id__c', type: 'text',sortable:true,actions: headerActions},
            { type: 'action', typeAttributes: { rowActions: actions } } 
        ]);
        helper.getData(component);
    },
    updateSelectedText : function(component, event, helper){
        var selectedRows = event.getParam('selectedRows');
        //  console.log('selectedRows'+selectedRows);
        component.set("v.selectedRowsCount" ,selectedRows.length );
        let obj =[] ; 
        for (var i = 0; i < selectedRows.length; i++){
            
            obj.push({Name:selectedRows[i].Name});
            
        }
        
        
        component.set("v.selectedRowsDetails" ,JSON.stringify(obj) );
        component.set("v.selectedRowsList" ,event.getParam('selectedRows') );
        
    },
    handleSelect: function (component, event, helper) {
        var arr = component.get('v.data');
        var obj =  component.get("v.selectedRowsList");
        console.log('obj '+JSON.stringify(obj) );
        var selectedButtonLabel = event.getSource().get("v.label");
        console.log('Button label: ' + selectedButtonLabel);
        var updateAction = component.get("c.setBookStatus");
        updateAction.setParams({ status : selectedButtonLabel , books: obj});
        updateAction.setCallback(this, function(a) {
            $A.get('e.force:refreshView').fire();
        });
        $A.enqueueAction(updateAction);
        
        
        
    },
    loadMoreData: function (component, event, helper) {
        //Display a spinner to signal that data is being loaded
        event.getSource().set("v.isLoading", true);
        //Display "Loading" when more data is being loaded
        component.set('v.loadMoreStatus', 'Loading');
        helper.fetchData(component, component.get('v.rowsToLoad')).then($A.getCallback(function (data) {
            if (component.get('v.data').length >= component.get('v.totalNumberOfRows')) {
                component.set('v.enableInfiniteLoading', false);
                component.set('v.loadMoreStatus', 'No more data to load');
            } else {
                var currentData = component.get('v.data');
                //Appends new data to the end of the table
                var newData = currentData.concat(data);
                component.set('v.data', newData);
                component.set('v.loadMoreStatus', 'Please wait ');
            }
            event.getSource().set("v.isLoading", false);
        }));
    },
    
    
    handleRowAction: function (cmp, event, helper) {
        var action = event.getParam('action');
        var row = event.getParam('row');
        switch (action.name) {
            case 'show_details':
                var navEvt = $A.get("e.force:navigateToSObject");
                navEvt.setParams({
                    "recordId": row.Id,
                    "slideDevName": "detail"
                });
                navEvt.fire();
                break;
            case 'delete':
                var rows = cmp.get('v.data');
                var rowIndex = rows.indexOf(row);
                console.log('rowIndex'+rowIndex);
                console.log('rowIndex row'+rows[rowIndex].Id);
                var deleteAct = cmp.get("c.deleteBooks");
                deleteAct.setParams({ ids : rows[rowIndex].Id });
                $A.enqueueAction(deleteAct);
                var toastEvent = $A.get("e.force:showToast");
                toastEvent.setParams({
                    "title": "Success!",
                    "message": "The record has been delete successfully."
                });
                toastEvent.fire();
                rows.splice(rowIndex, 1);
                cmp.set('v.data', rows);
                break;
        }
    },
    handleHeaderAction: function (cmp, event, helper) {
        
        // helper.getData(cmp);
        
        
        var actionName = event.getParam('action').name;
        var colDef = event.getParam('columnDefinition');
        var columns = cmp.get('v.columns');
        var activeFilter = cmp.get('v.activeFilter');
        console.log('activeFilter-->'+activeFilter);
        if (actionName !== activeFilter) {
            var idx = columns.indexOf(colDef);
            var actions = columns[idx].actions;
            console.log('actions'+actions)
            actions.forEach(function (action) {
                action.checked = action.name === actionName;
            });
            cmp.set('v.activeFilter', actionName);
            helper.updateBooks(cmp);
            cmp.set('v.columns', columns);
        }
    },
    
    // Client-side controller called by the onsort event handler
    updateColumnSorting: function (cmp, event, helper) {
        var fieldName = event.getParam('fieldName');
        var sortDirection = event.getParam('sortDirection');
        // assign the latest attribute with the sorted column fieldName and sorted direction
        cmp.set("v.sortedBy", fieldName);
        cmp.set("v.sortedDirection", sortDirection);
        helper.sortData(cmp, fieldName, sortDirection);
    },
    
    
    
    
    
})

 

helper
({
    getData : function(component) {
        
        var action = component.get("c.getBooks");
        action.setParams({
            "limits": component.get("v.initialRows"),
            "offsets": component.get("v.rowNumberOffset")
        });
        action.setCallback(this, function(a) {
            component.set("v.data", a.getReturnValue());
            component.set("v.currentCount", component.get("v.initialRows"));
            
        });
        $A.enqueueAction(action);
    },
    sortData: function (cmp, fieldName, sortDirection) {
        var data = cmp.get("v.data");
        var reverse = sortDirection !== 'asc';
        //sorts the rows based on the column header that's clicked
        data.sort(this.sortBy(fieldName, reverse))
        cmp.set("v.data", data);
    },
    sortBy: function (field, reverse, primer) {
        var key = primer ?
            function(x) {return primer(x[field])} :
        function(x) {return x[field]};
        //checks if the two rows should switch places
        reverse = !reverse ? 1 : -1;
        return function (a, b) {
            return a = key(a), b = key(b), reverse * ((a > b) - (b > a));
        }
    },
    fetchData: function(component , rows){
        return new Promise($A.getCallback(function(resolve, reject) {
            var currentDatatemp = component.get('c.getBooks');
            var counts = component.get("v.currentCount");
            currentDatatemp.setParams({
                "limits": component.get("v.initialRows"),
                "offsets": counts 
            });
            currentDatatemp.setCallback(this, function(a) {
                resolve(a.getReturnValue());
                var countstemps = component.get("v.currentCount");
                countstemps = countstemps+component.get("v.initialRows");
                component.set("v.currentCount",countstemps);
                
            });
            $A.enqueueAction(currentDatatemp);
            
            
        }));
        
    } ,
    updateBooks: function (cmp) {
        var rows = cmp.get('v.data');
        var activeFilter = cmp.get('v.activeFilter');
        console.log('activeFilter Helper'+activeFilter)
        var filteredRows = rows;
        if (activeFilter == 'All') {
            return  rows; 
        }
        if (activeFilter !== 'All') {
            filteredRows = rows.filter(function (row) {
                console.log('Each Row'+row.publish_status__c);
                if(row.publish_status__c == activeFilter){
                    return  row; 
                }
                // return (activeFilter === 'In_Completed') ||(activeFilter === 'Pre_Order');
            });
        }
        cmp.set('v.data', filteredRows);
    },
    
})

 

 

 

Usage Of lightning:dualListbox

In this blog, I am going to explain how to use salesforce lightning: dualListbox. A widget that provides an input list box, accompanied by a list box of selectable options. Order of selected options is saved. A lightning:dualListbox component represents two side-by-side list boxes. Select one or more options in the list on the left. Move selected options to the list on the right. The order of the selected options is maintained and you can reorder options. In this example, I am getting data from the Book_Categories__c objects and showing its as a  dualListbox available options.

Apex Class

public class BookController {
    @AuraEnabled
    public static List<Book_Categories__c> getBooksByAllCategories(){
        
        List<Book_Categories__c> categeroy = [Select  Name,(Select Name, Author_Name__c, Book_Categories__c,Publisher_Id__c from Books__r) from Book_Categories__c];
        return categeroy;
    } 
}

Component

<aura:component controller="BookController">
    <aura:attribute name="listOptions" type="List" default="[]"/>
    <aura:attribute name="defaultOptions" type="List" default="[]"/>
    <aura:attribute name="requiredOptions" type="List" default="[]"/>
    <aura:attribute name="selectedArray" type="List" default="[]"/>
    
    <aura:handler name="init" value="{! this }" action="{! c.doInit }"/>
    <aura:attribute name="min" type="Integer" default="5"/>
    <aura:attribute name="max" type="Integer" default="10"/>
    <lightning:dualListbox aura:id="selectOptions" name="Select Options"  label="Select Options" 
                           sourceLabel="Available Options" 
                           selectedLabel="Selected Options" 
                           options="{! v.listOptions }"
                           value="{! v.defaultOptions }"
                           requiredOptions="{! v.requiredOptions }"
                           onchange="{! c.handleChange }"
                           min="{!v.min}"
                           max="{!v.max}"
                           
                           />
    
    
    Selected Values : 
    
    <aura:iteration items="{!v.selectedArray}" var="val" indexVar="indvar">
        <li>
            {!val}
        </li>
    </aura:iteration>
</aura:component>

 

/** Client-Side Controller **/
({
    doInit: function (component, event, helper) {
        var options = [
        ];
        var action = component.get("c.getBooksCategories");
        component.set("v.listOptions", options);
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (component.isValid() && state == 'SUCCESS') {   
                var resultArray = response.getReturnValue();
                var options = [];
                resultArray.forEach(function(result)  { 
                    options.push({ value: result.Name, label: result.Name});
                });
                component.set("v.listOptions", options);
            } else {
                console.log('Failed with state: ' + state);
            }
        });
        $A.enqueueAction(action); 
    },
    handleChange: function (component, event) {
        var selectedOptionsList = event.getParam("value");
        console.log(selectedOptionsList);
        component.set("v.selectedArray", selectedOptionsList);
        
    },
    
})

Below image shows the result of the component .

 

 

Salesforce Custom Permissions

Introduction:- 

Currently, in Salesforce we have many features require access checks that specify which users can access certain functions. Permission set and profiles settings include built-in access settings for many entities, like objects, fields, tabs, and Visualforce pages etc. . . . However, permission sets and profiles don’t include access for some custom processes and apps. Custom permissions let you define access checks that can be assigned to users via permission sets or profiles, similar to how you assign user permissions and other access settings. For example, you can define access checks in Apex that make a button on a Visualforce page available only if a user has the appropriate custom permission.  Any time admin can revoke the custom permission from the profile or permission set to revoke the processor app access.

Let’s Define Custom permission 

Go to Setupà Develop -> Custom Permissions à Click on New and enter information as shown below.

1

Now you can assign this custom permission to profile or permission based on need. In this case, I assign it to the profile “System Admin “. Under system admin profile edit Custom Permissions section and assign the custom permission as shown below.

2.PNG

So far looks nice. we defined the custom permission and assigned it to the profiles.Let’s understand how to use it.
Usage 1: – Validation rules 

You can use custom permission in the number of ways namely from Apex, Formulas and Approvals and workflows and validation rules.

Now let us see how to use it in validation rule.let’s suppose if you wanted to edit the Opportunity Stage for only specific profiles.you can assign the custom permission to the profile and you can use in validation rules. Custom permission can be access by using $Permission” global variable.

Go to Opportunity Validation rules, create a new rule as shown below

3.PNG

 Once you save it, the user who is not having access to the Opportunity_Stage_Edit custom permission not able to update the stage and it will prompt an error as shown below.

4

 

Usage 2: – Formulas and Workflow and Approval process and Process Builder  

You can use custom permission in approval and formulas and workflows and Process Builder to perform the process actions based the custom permission assignment. like submitted discount approval for the only specific profile .what you can do is simply you can assign the custom permission and check the custom permission in approvals process entry criteria. same will be the application in the case of workflows and formulas.  

 Usage 3: – Visualforce page

In Visualforce you can use access by Using $ Permission global variable. you can use in different ways to visual force. let us suppose take you want to provide access to specific button based on the Custom permission assignment or wanted to render specific sections for profiles and etc.

The below logic will render the button based on the Custom permission

<apex:commandbutton action="{!saveStage}"
rendered="{!$Permission. Opportunity_Stage_Edit }"
value="Save Stage"> </apex:commandbutton>

The below logic renders the page block based on the custom permission.

<apex:pageBlock rendered="{!$Permission. Opportunity_Stage_Edit }">

</apex:pageBlock>

Usage 4: – Force.com Connected Apps 

You can use custom permission in the OAuth scope as shown below image

Usage 5: – Apex, use your own way 

You can use custom permission in the apex code as well. you can query custom permission by using CustomPermission and SetupEntityAccess object by using SOQL as shown below.

CustomPermission permission =[SELECT Id, DeveloperName
     FROM CustomPermission
     Where DeveloperName = 'Opportunity_Stage_Edit' Limit 1];

List<SetupEntityAccess> setupEntities =[SELECT SetupEntityId
                                        FROM SetupEntityAccess
                                        WHERE SetupEntityId=:permission.Id  AND
                                        ParentId IN (SELECT PermissionSetId
                                                     FROM PermissionSetAssignment)];