Tuesday 19 December 2017

Salesforce: Restrict User from Deleting Files Once Record Status Changed

Salesforce: Restrict User from Deleting Files Once Record Status Changed

Business Requirement: 

There is a custom object in Salesforce having a status field. If the status of that record is marked as "Completed" user is not allowed to attach/upload new files with it, nor user can upload new version of file nor he can delete file.

Solution:

On the front end we see Files object ,its related list and tab and in my requirement data is saved from front end in files. From back-end  Salesforce use 3 diffrent object.
  1. Content Document
  2. Content Version
  3. Content Document Link

ContentDocument object is to retrieve, query, update, and delete the latest version of a document, but not a content pack, in a library or a file in Chatter.

Use the ContentVersion object to create, query, retrieve, search, edit, and update a specific version of a Salesforce CRM Content document or Chatter file.
So in order to achieve this functionality we have to write trigger on all three objects.

Content Document Trigger:

trigger ContentDocumentTrg on ContentDocument (before delete,before update) {
    
    Map<Id,String> MapOfStatus = new Map<Id,String>();
    Set<Id> SetOfCdl = new Set<Id>();
    Set<Id> SetOfCd = new Set<Id>();
    map<Id,Id> MapOfCdNCdl = new map<Id,Id>();
    if(Trigger.IsUpdate){
    for(ContentDocument cd :Trigger.new){
    SetOfCd.add(cd.Id);
    }
    }
    if(Trigger.Isdelete){
    for(ContentDocument cd :Trigger.old){
    SetOfCd.add(cd.Id);
    }
    }
   
    for(ContentDocumentLink cdl : [SELECT LinkedEntityId,ContentDocumentId FROM ContentDocumentLink WHERE ContentDocumentId IN:SetOfCd]){
    string cdlentityId = cdl.LinkedEntityId;
    if (cdlentityId.substring(0,3) == 'a0h') {
    SetOfCdl.add(cdl.LinkedEntityId);
    MapOfCdNCdl.put(cdl.ContentDocumentId, cdl.LinkedEntityId);
    }
    }
MapOfStatus = ContectDocLinkHandler.CheckStatus(SetOfCdl);
if(Trigger.IsUpdate){
for(ContentDocument cd :Trigger.new){
Id cdlId = MapOfCdNCdl.get(cd.Id);
if(MapOfStatus.get(cdlId) == 'Completed'){
cd.addError('Sorry you cannot add/update this file.');
}
}
}
if(Trigger.Isdelete){
for(ContentDocument cd :Trigger.old){
Id cdlId = MapOfCdNCdl.get(cd.Id);
if(MapOfStatus.get(cdlId) == 'Completed'){
cd.addError('Sorry you cannot delete this file.');
}
}
}
}

Content Version Trigger


trigger ContentVersionTrg on ContentVersion (before update) {
    
    if(Trigger.IsBefore && (Trigger.IsInsert || Trigger.IsUpdate)){
   
    Map<Id,String> MapOfStatus = new Map<Id,String>();
    Set<Id> SetOfCdl = new Set<Id>();
    Set<Id> SetOfCd = new Set<Id>();
    map<Id,Id> MapOfCdNCdl = new map<Id,Id>();
    for(ContentVersion cv :Trigger.new){
    SetOfCd.add(cv.ContentDocumentId);
    }
    system.debug('Set Of Content Doc'+SetOfCd);
    for(ContentDocumentLink cdl : [SELECT LinkedEntityId,ContentDocumentId FROM ContentDocumentLink WHERE ContentDocumentId IN:SetOfCd]){
    string cdlentityId = cdl.LinkedEntityId;
//Hard coding start 3 character of my custom object
    if (cdlentityId.substring(0,3) == 'a0h') {
   
    SetOfCdl.add(cdl.LinkedEntityId);
    MapOfCdNCdl.put(cdl.ContentDocumentId, cdl.LinkedEntityId);
    }
    }
MapOfStatus = ContectDocLinkHandler.CheckStatus(SetOfCdl);
for(ContentVersion cv :Trigger.new){
Id cdlId = MapOfCdNCdl.get(cv.ContentDocumentId);
if(MapOfStatus.get(cdlId) == 'Completed'){
cv.addError('Sorry you cannot add/update this file');
}
}
    }
}

Content Document Link Trigger:

trigger ContectDocLinkTrg on ContentDocumentLink (before delete,before update) {
    set<Id> SetOfCdlLinkId = new set<Id>();
    map <Id,String> MapOfStatus = new map<Id,String>();
    
for(ContentDocumentLink cdl :Trigger.old){
SetOfCdlLinkId.add(cdl.LinkedEntityId);
}
MapOfStatus = ContectDocLinkHandler.CheckStatus(SetOfCdlLinkId);
for(ContentDocumentLink cdl :Trigger.old){
if(MapOfStatus.get(cdl.LinkedEntityId) == 'Completed'){
cdl.addError('Sorry you cannot delete this file');
}
}
}

Content Document Link Handler :

public without sharing class ContectDocLinkHandler {

       
 // function used to check status of Jira Issue object as complete
    public Static Map<Id,String> CheckStatus(set<Id> SetOfCdl)
    {
    map <Id,String> MapOfJIStatus = new map<Id,String>();
   
   
    if(SetOfCdl.size() > 0){
    for(Jira_Issues__c js : [select Id, Status__c from Jira_Issues__c where Id IN: SetOfCdl]){
    MapOfJIStatus.put(js.Id, js.Status__c);
    }
    }
    return MapOfJIStatus;
    }
}

Happy Coding !!! 

Sunday 10 December 2017

Lightning Components Basics: Connect Components with Events

Lightning Components Basics: Connect Components with Events 

CampingListComp:


<aura:component controller="CampingListController"
implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,force:lightningQuickAction" access="global" >
<aura:handler name="init" action="{!c.doInit}" value="{!this}"/>
    <aura:handler name="addItem" event="c:addItemEvent"
    action="{!c.handleAddItem }"/> 


    <aura:attribute name="items" type="Camping_Item__c[]"/>
    <!--<aura:attribute name="newItem" type="Camping_Item__c" default="{ 'sobjectType': 'Camping_Item__c',
                        'Name': '',
                        'Quantity__c': 0,                                          
                        'Price__c': 0,
                        'Packed__c': false }"/>-->
    <ol>
        <li>Bug Spray</li>
        <li>Bear Repellant</li>
        <li>Goat Food</li>
    </ol> 
    <!-- PAGE HEADER -->
    
    <c:campingHeader />
    <lightning:layout >
<lightning:layoutItem padding="around-small" size="6">
<c:campingListForm />
        </lightning:layoutItem>
    </lightning:layout>

    
    <c:campingHeader />

<div class="slds-card slds-p-top--medium">
        <header class="slds-card__header">
            <h3 class="slds-text-heading--small">Items</h3>
        </header>
         
        <section class="slds-card__body">
            <div id="list" class="row">
                <aura:iteration items="{!v.items}" var="items">
                    <c:campingListItem item="{!item}"/>
                </aura:iteration>
            </div>
        </section>
    </div>

</aura:component>

CampaignListController:


({
     // Load expenses from Salesforce
    doInit: function(component, event, helper) {
    
        // Create the action
        var action = component.get("c.getItems");
    
        // Add callback behavior for when response is received
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                component.set("v.items", response.getReturnValue());
            }
            else {
                console.log("Failed with state: " + state);
            }
        });
    
        // Send action off to be executed
        $A.enqueueAction(action);
    },
     handleAddItem: function(component, event, helper) {
         var newItem = event.getParam("item");
         var action = component.get("c.saveItem");
         action.setParams({"item": newItem});
action.setCallback(this, function(response){
                var state = response.getState();
                if (component.isValid() && state === "SUCCESS") {
                    // all good, nothing to do.
                }
            });
            $A.enqueueAction(action);
       
     }
})

Camping List Form Component

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId" access="global" >
<aura:attribute name="newItem" type="Camping_Item__c" default="{ 'sobjectType': 'Camping_Item__c',
                        'Name': '',
                        'Quantity__c': 0,                                          
                        'Price__c': 0,
                        'Packed__c': false }"/>
    <aura:registerEvent name="addItem" type="c:addItemEvent"/> 
    <lightning:layout >
        <lightning:layoutItem padding="around-small" size="6">
<div aria-labelledby="newcampaignform">
                <fieldset class="slds-box slds-theme--default slds-container--small">
    <legend id="newcampaignform" class="slds-text-heading--small slds-p-vertical--medium">
                    Add Campaign List
                    </legend> 
                    <form class="slds-form--stacked">
                        <lightning:input aura:id="campaignform" label="Campaign Item Name"
                         name="campaignitemname"
                         value="{!v.newItem.Name}"
                         required="true"/>
                        <lightning:input type="number" aura:id="expenseform" label="Quantity"
                         name="campaignitemprice"
                         min="1"
                         formatter="number"
                         step="0.1"
                         value="{!v.newItem.Quantity__c}"
                         messageWhenRangeUnderflow="Enter quantity that's at least 1."/>
<lightning:input type="number" aura:id="expenseform" label="Price"
                         name="campaignitemprice"
                         min="0.1"
                         formatter="currency"
                         step="0.01"
                         value="{!v.newItem.Price__c}"
                         messageWhenRangeUnderflow="Enter an amount that's at least $0.10."/>
<lightning:input type="checkbox" aura:id="expenseform" label="Packed?" 
                         name="expreimbursed"
                         checked="{!v.newItem.Packed__c}"/>
<lightning:button label="Create Camping" class="slds-m-top--medium"
                                 variant="brand" onclick="{!c.clickCreateItem}"/>
                    </form>
                </fieldset>
            </div>
            
</lightning:layoutItem>
    </lightning:layout>
</aura:component>

Camping List Form Controller

({
clickCreateItem: function(component, event, helper) {
var validCamping = component.find('campingform').reduce(function (validSoFar, inputCmp) {
            // Displays error messages for invalid fields
            inputCmp.showHelpMessageIfInvalid();
            return validSoFar && inputCmp.get('v.validity').valid;
        }, true);
if(validCamping){
            var addItm = event.getParam("v.newItem");
            helper.createItem(component, addItm);
        }
}
})

Camping List Form Helper

({
addItem : function(component, campaign) {
        var createEvent = component.getEvent("addItem");
     createEvent.setParams({ "item": campaign });
    createEvent.fire();
component.set("v.newItem",{ 'sobjectType': 'Camping_Item__c',
                    'Name': '',
                    'Quantity__c': 0,
                    'Price__c': 0,
                    'Packed__c': false });
}
})

AddItemEvent

Create File | New | Lightning Event,
<aura:event type="COMPONENT">
    <aura:attribute name="item" type="Camping_Item__c"/> 
</aura:event>

Wednesday 6 December 2017

Visualforce: Showing nearest account locations on google map

Visualforce: Showing nearest account locations on google map

Few days back i got a requirement from my client that salesperson need a app in salesforce which allows them to do following.

Requirements:
  • When they are onsite to visit an account or prospect this salesforce app will tell them other nearby(get current location as they will be using this app from mobile) accounts location on google map.This will help them to visit more than 1 account in a day.
  • Allow them to enter a dynamic address and on the bases of that address find out nearby accounts.
Problem:
  • In order to get current/dynamic location latitude and longitude to show on map we have to use any API which will provide us latitude and longitude values in return of address.
  • All the account in the system should have their latitude and longitude values saved in a field in order to show them on map.
  • Client do not want to pay for any API.
Solution:

  • Many of you know about data integration rule. This feature is released by Salesforce few releases back. You can get a better idea of this feature from this link.
  • This will help us to get lat and lon values of all accounts resit in our database. We will use javascript to get lat and lon values of current location and dynamic entered address.
Enough of talk lets do some code :)

Visualforce page:

<apex:page docType="html-5.0" sidebar="false" Controller="accountMappingController" showHeader="true" id="page">  
    <apex:pagemessages id="message"/>
<apex:stylesheet value="{!URLFOR($Resource.bootstrap, '/dist/css/bootstrap.min.css')}"/>
<apex:includeScript value="/support/console/34.0/integration.js"/>
<script type="text/javascript" src="https://maps.google.com/maps/api/js?sensor=false"></script>



  <script> var previousOnload = window.onload; window.onload = function() { if (previousOnload) { previousOnload(); } setUserLocation(); } </script> 

<!-- Javascript to get the user's current location and set appropriate URL call-->
<script type="text/javascript">

    
      function geocodeAddress() {
        var street = document.getElementById('page:MyId:Pb:street1').value;
        var city = document.getElementById('page:MyId:Pb:city1').value;
        var state = document.getElementById('page:MyId:Pb:state1').value;
        var country = document.getElementById('page:MyId:Pb:country1').value;
        var address = street + ' ' + city + ' ' +state + ' ' +country;
        var geocoder = new google.maps.Geocoder();
        geocoder.geocode({'address': address}, function(results, status) {
          if (status === 'OK') {
            
            var array = results[0].geometry.location.toString();
            array = array.replace(/\(|\)/g,'').split(",");
            document.getElementById("page:MyId:Pb:Lat").value = array[0];
            document.getElementById("page:MyId:Pb:Long").value = array[1];
            NewCurrentLocation();
          } else {
            alert('Geocode was not successful for the following reason: ' + status);
          }
        });
}

    

    function setUserLocation() {
        if("{!street}" && "{!city}" && "{!state}"){
        }
        else{
            if (navigator.geolocation) { 
                navigator.geolocation.getCurrentPosition(function(loc){
                    var latlon = loc.coords.latitude + "," + loc.coords.longitude;
                    var el = document.querySelector("input.currentPosition");
                    el.value = latlon;
                    generateMap();
                });
            }
            
            var gURL;
            var el2 = document.querySelector("input.googleURL");
            
            if(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
                gURL = "comgooglemaps://?"; // if User device is mobile, call Google Maps mobile app url
            }
            else {
                gURL = "https://www.google.com/maps?"; // else, call Google Maps for Desktop url
            } 
            
            el2.value =  gURL;
        }
    }
    

</script>

<div class="bs" id="firstdiv">

    <div class="row">        
        <div class="col-xs-12"> <!--Block of code used to show accounts coming from controller on page  -->
            <apex:outputPanel rendered="{!IF(accounts.size==0,false,true)}" id="sectionsPanel" >              
                <apex:map width="100%" height="500px" center="{!userLocation}">   
                    <apex:repeat value="{!accounts}" var="a">
                        <apex:mapMarker icon="http://maps.google.com/intl/en_us/mapfiles/ms/micons/red-dot.png" id="accountMapMarker"
                        position="{latitude: {!a.BillingLatitude}, longitude: {!a.BillingLongitude}}">
                            
                            <apex:mapInfoWindow >
                                <apex:outputPanel layout="block" style="font-weight: bold;  font-size: 14px;">
                                    <apex:outputText >{!a.Name}</apex:outputText>
                                </apex:outputPanel>                              
                                <a href="tel:{!a.Phone}">{!a.Phone}</a>                                                             
                                <apex:outputPanel layout="block">
                                    <apex:outputText ><b>Account Type</b> {!a.Type} </apex:outputText>
                                </apex:outputPanel>
                                
                                <apex:outputPanel layout="block">
                                    <apex:outputText >{!a.BillingStreet}, {!a.BillingCity}, {!a.BillingState}</apex:outputText>
                                </apex:outputPanel>
                                <button type="button" onclick="window.open('{!googleURL}daddr={!a.BillingLatitude},{!a.BillingLongitude}&saddr={!currentPosition}'); return false;" >Get Directions</button>
                                
                               
                            </apex:mapInfoWindow>
                        </apex:mapMarker>
                    </apex:repeat>
             
                       <apex:mapMarker id="UserLocationMapMarker" position="{!currentPosition}" icon="http://maps.google.com/mapfiles/ms/micons/green-dot.png" > 
                       
                        <apex:mapInfoWindow >
                            <apex:outputPanel layout="block" style="font-weight: bold;  font-size: 14px;">
                                <apex:outputText >Current Location</apex:outputText>
                            </apex:outputPanel>
                            <apex:outputPanel layout="block">
                                <apex:outputText >Search Google Maps<BR/>for "current Location"<BR/>in your area</apex:outputText>
                            </apex:outputPanel>
                            <button type="button" onclick="window.open('{!googleURL}q=Garage+Doors&center={!currentPosition}&zoom=20'); return false;">Get Directions</button>
                            
                        </apex:mapInfoWindow>
                    </apex:mapMarker>  
                </apex:map>
            </apex:outputPanel>
        </div>
    </div>
</div> 

<apex:form id="MyId">
    <apex:outputText rendered="{!IF(accounts.size==0,true,false)}"><b>Loading Account Map...</b></apex:outputText>
    
    <apex:actionFunction action="{!findbyNewCurrentLocation}" name="NewCurrentLocation"/>
    
    <apex:actionFunction action="{!redirectByLocation}" name="generateMap" rerender="page"/> 
    <apex:input size="30" id="currentPosition" styleClass="currentPosition"  style="visibility: hidden;"  value="{!currentPosition}" />
    <apex:input size="30" id="resultsAvailable" styleClass="resultsAvailable" style="visibility: hidden;"   value="{!resultsAvailable}" />
   
    <apex:input size="1" id="googleURL" styleClass="googleURL" style="visibility: hidden;" value="{!googleURL}" /> 
    
    
  
    <apex:pageBlock title="Change Current Location" id="Pb">
            <table width="60%" align="center">
                <tr><td>
                    <b><apex:outputLabel value="Street " for="street" style="width:5%" /></b></td><td>
                    <apex:inputtext value="{!street}" id="street1" style="width:65%"/></td><td>
                    <b><apex:outputLabel value="City"  for="city" style="width:10%"/></b></td><td>
                    <apex:inputtext value="{!city}" id="city1" style="width:55%"/></td><td>
                    <b><apex:outputLabel value="State"  for="state" style="width:10%"/></b></td><td>
                    <apex:inputtext value="{!state}" id="state1" style="width:55%"/></td><td>
                    <b><apex:outputLabel value="Country"  for="country" style="width:10%"/></b></td><td>
                    <apex:inputtext value="{!country}" id="country1" style="width:55%"/></td>
                    </tr>
            </table>
  <apex:inputHidden id="Long" value="{!CusLong}" />
  <apex:inputHidden id="Lat" value="{!CusLat}" />
 
        <apex:commandButton value="Change Current Location" styleClass="buttonStyle" onclick = "geocodeAddress();"
                style="font-weight: bold;" rerender="page,MyId,message" status="loadingStatus" id="submit"/>
    </apex:pageBlock>
<apex:actionstatus id="loadingStatus">
          <apex:facet name="start">
              <div style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; opacity: 0.25; z-index: 1000; background-color: black;">
                  &nbsp;
              </div>
              <div style="position: fixed; left: 0; top: 0; bottom: 0; right: 0; z-index: 1001; margin: 15% 50%">
                  <div style="display: inline-block; padding: 2px; background-color: #fff; width: 125px;">
                      <img src="/img/loading.gif" style="float: left; margin: 8px;" />
                      <span style="display: inline-block; padding: 10px 0px;">Please Wait...</span>
                  </div>
              </div>
          </apex:facet>
      </apex:actionstatus>
</apex:form>

</apex:page>
Note: Billing address is used to show location on map. This works on SF1 too.

Controller Code:

public with sharing class accountMappingController {

    public Map<String, Double> userLocation { get; private set; }  
    
    public List<Account> accounts { get; set; } 
    public List<Account> prospects { get; private set; }
    public List<Account> suspects { get; private set; }
    public String googleURL { get; set; }
    public String street{ get; set; }
    public String city{ get; set; }
    public String state{ get; set; }
    public String country {get;set;}
    public string CusLat {get;set;}
    public string CusLong {get;set;}
    
    public accountMappingController (){
        Apexpages.currentPage().getHeaders().put('X-UA-Compatible', 'IE=10');
        
        
    }
    
    public String currentPosition {
        get {
            if(String.isBlank(street) && String.isBlank(city) && String.isBlank(state)){
                if (String.isBlank(currentPosition)) {
                    currentPosition = '0,0';
                }
            }
            return currentPosition;
        }
        set;
    }
    
    public Boolean resultsAvailable {
        get {
            if(accounts == Null) {
                return false;
            }
            return true;
        }
        
        set;
    }
    

    public PageReference findNearby() {
        String lat, lon;
        
        System.debug('Inside findNearby, Position='+street);
        
        List<String> latlon;
      
        latlon = currentPosition.split(',');
       
        lat = latlon[0].trim();
        lon = latlon[1].trim(); 
        
        userLocation = new Map<String, Double>{
            'latitude' =>  decimal.valueOf(lat),
            'longitude' =>  decimal.valueOf(lon)
        };
        
        accounts = new List<Account>(); 

        
        //Limited to 99 Results, Update LIMIT code Lines to change results ratio
        
         // SOQL query to get Accounts
        String queryString1 = 
            'SELECT Id, Name, Type, Phone, BillingLatitude,BillingLongitude,BillingStreet, BillingCity, BillingState ' +
            'FROM Account ' +
            'WHERE BillingLatitude != null AND BillingLongitude != null ' + 
            'ORDER BY DISTANCE(BillingAddress, GEOLOCATION('+lat+','+lon+'), \'mi\') NULLS LAST ' +
            'LIMIT 60';

            
        // run query
        accounts = database.Query(queryString1);
        
        if (0 == accounts.size()) 
            System.debug('No results. Query: ' + queryString1);
         
                 
        return null;
    }
    
    //This method is a copy of findNearby() method with the only difference that current
    //location is calculated on the basis of street, city, and state field on the UI
    public PageReference findbyNewCurrentLocation() {
        try{
            String lat, lon;
                      
            accounts = new List<Account>(); 

            currentPosition = CusLat +','+ CusLong;
            lat = CusLat.trim();
            lon = CusLong.trim();

            userLocation = new Map<String, Double>{
                'latitude' =>  decimal.valueOf(lat),
                'longitude' =>  decimal.valueOf(lon)
            };
           
       
            //Limited to 99 Results, Update LIMIT code Lines to change results ratio
            
             // SOQL query to get Accounts
            String queryString1 = 
                  'SELECT Id, Name, Type, Phone,BillingLatitude,BillingLongitude,BillingStreet, BillingCity, BillingState '+ 
                'FROM Account ' +
                'WHERE BillingLatitude !=null AND BillingLongitude !=null ' + 
                'ORDER BY DISTANCE(BillingAddress, GEOLOCATION('+lat+','+lon+'), \'mi\') NULLS LAST ' + 
                'LIMIT 60'; 
  
            // run query
            accounts = database.Query(queryString1);
            
            
            if (0 == accounts.size()) 
                System.debug('No results. Query: ' + queryString1); 
                    
            return null;
        }
        catch(exception e){ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, Label.ERR_Location_Not_found)); system.debug('Error'+ e);return null;}
        
   
        
    }
    
    //returns location data on the condition of whether street field is empty or not on UI
    public PageReference redirectByLocation(){
     
        
        //if(street !='' && city != '' && state != ''){
        if(!String.IsBlank(street) && !String.IsBlank(city) && !String.IsBlank(state) && !String.IsBlank(country)){
            return findbyNewCurrentLocation();
        }
        else{
            return findNearby();
        }
    }
    
    
    
}
Note: Label is used for showing error message.

Page View:



Sunday 26 November 2017

Lightning Data Service Basics: Handle Record Changes and Errors

Lightning Data Service Basics: Handle Record Changes and Errors

accEdit Component Code


<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId" access="global" >
<aura:attribute name="record" type="Object" 
      description="The record object to be displayed"/>
    <aura:attribute name="simpleRecord" type="Object" 
      description="A simplified view record object to be displayed"/>
    <aura:attribute name="recordSaveError" type="String" 
      description="An error message bound to force:recordData"/>
    
    <force:recordData aura:id="accountRecord"
        layoutType="FULL"
        recordId="{!v.recordId}"
        targetError="{!v.recordSaveError}"
        targetRecord="{!v.record}"
        targetFields="{!v.simpleRecord}"
        recordUpdated="{!c.recordUpdated}"
        fields="Name"
        mode="EDIT"/>
    
    <!-- Display an editing form -->
        <div class="Record Details">
            <lightning:card iconName="action:edit" title="Edit Account">
                <div class="slds-p-horizontal--small">
                    <lightning:input label="Account Name" value="{!v.accountRecord.Name}"/>
                    <br/>
                    <lightning:button label="Save Account" variant="brand" onclick="{!c.handleSaveRecord}" />
                </div>
            </lightning:card>
        </div>
    <!-- Display Lightning Data Service errors, if any -->
    <aura:if isTrue="{!v.recordSaveError}">

        <div class="recordError">
            {!v.recordSaveError}</div>
    </aura:if>
</aura:component>

accEdit Controller Component


({
    recordUpdated: function(component, event, helper) {
        var eventParams = event.getParams();
        if(eventParams.changeType === "CHANGED") {
            // get the fields that are changed for this record
            var changedFields = eventParams.changedFields;
            //console.log('Fields that are changed: ' + JSON.stringify(changedFields));
            // record is changed so refresh the component (or other component logic)
            var resultsToast = $A.get("e.force:showToast");
            resultsToast.setParams({
                "title": "Saved",
                "message": "The record was updated."
            });
            resultsToast.fire();
        } else if(eventParams.changeType === "LOADED") {
        } else if(eventParams.changeType === "REMOVED") {
           
        } else if(eventParams.changeType === "ERROR") {
         
        }
    },
    handleSaveRecord: function(cmp, event, helper) {
        cmp.find("accountRecord").saveRecord($A.getCallback(function(saveResult) {
            if (saveResult.state === "ERROR") {
                var errMsg = "";
                for (var i = 0; i < saveResult.error.length; i++) {
                    errMsg += saveResult.error[i].message + "\n";
                }
                cmp.set("v.recordSaveError", errMsg);

            } else {
                cmp.set("v.recordSaveError", "");

            }
        }));}
    
})

Note:If you keep getting this error "The 'accEdit' Lightning Component JS Controller does not appear to be setting 'v.recordSaveError' with an error message" then make sure in handleSaveRecord function you are using "cmp" not any other name.

Wednesday 22 November 2017

Lightning Data Service Basics: Manipulate Records with force:recordData

Lightning Data Service Basics: Manipulate Records with force:recordData

Manipulation of records with forcerecordData featsure. You can do insert update and delete using this.

accDisplay Component Code


<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId" access="global" >
<aura:attribute name="record" type="Object" 
      description="The record object to be displayed"/>
    <aura:attribute name="simpleRecord" type="Object" 
      description="A simplified view record object to be displayed"/>
    <aura:attribute name="recordError" type="String" 
      description="An error message bound to force:recordData"/>
    
    <force:recordData aura:id="accountRecord"
        layoutType="FULL"
        recordId="{!v.recordId}"
        targetError="{!v.recordError}"
        targetRecord="{!v.record}"
        targetFields ="{!v.simpleRecord}"
        mode="VIEW"/>
    <!-- Display a lightning card with details about the record -->
    <div class="Record Details"> 
        <lightning:card iconName="standard:account" title="{!v.accountRecord.Name}" >
            <div class="slds-p-horizontal--small">
                <p class="slds-text-heading--small">
                    <lightning:formattedText title="Name" value="{!v.accountRecord.Name}" /></p>
                <p class="slds-text-heading--small">
                    <lightning:formattedText title="Industry" value="{!v.accountRecord.Industry}" /></p>
                <p class="slds-text-heading--small">
                    <lightning:formattedText title="Description" value="{!v.accountRecord.Description}" /></p>
                <p class="slds-text-heading--small">
                    <lightning:formattedPhone title="Phone" value="{!v.accountRecord.Phone}" /></p>
            </div>
        </lightning:card>
    </div>
        
    <!-- Display Lightning Data Service errors, if any -->
    <aura:if isTrue="{!not(empty(v.recordError))}">
        <div class="recordError">
            {!v.recordError}</div>
    </aura:if>
</aura:component>

accEdit Component Code


<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId" access="global" >
<aura:attribute name="record" type="Object" 
      description="The record object to be displayed"/>
    <aura:attribute name="simpleRecord" type="Object" 
      description="A simplified view record object to be displayed"/>
    <aura:attribute name="recordError" type="String" 
      description="An error message bound to force:recordData"/>
    
    <force:recordData aura:id="accountRecord"
        layoutType="FULL"
        recordId="{!v.recordId}"
        targetError="{!v.recordError}"
        targetRecord="{!v.record}"
        targetFields ="{!v.simpleRecord}"
        fields="Name"
        mode="EDIT"/>
    
    <!-- Display an editing form -->
        <div class="Record Details">
            <lightning:card iconName="action:edit" title="Edit Account">
                <div class="slds-p-horizontal--small">
                    <lightning:input label="Account Name" value="{!v.accountRecord.Name}"/>
                    <br/>
                    <lightning:button label="Save Account" variant="brand" onclick="{!c.handleSaveRecord}" />
                </div>
            </lightning:card>
        </div>
</aura:component>

accEdit Controlller Js Code


({
    handleSaveRecord: function(component, event, helper) {
        component.find("accountRecord").saveRecord($A.getCallback(function(saveResult) {
            if (saveResult.state === "SUCCESS" || saveResult.state === "DRAFT") {
                console.log("Save completed successfully.");
            } else if (saveResult.state === "INCOMPLETE") {
                console.log("User is offline, device doesn't support drafts.");
            } else if (saveResult.state === "ERROR") {
                console.log('Problem saving record, error: ' + 
                           JSON.stringify(saveResult.error));
            } else {
                console.log('Unknown problem, state: ' + saveResult.state + ', error: ' + JSON.stringify(saveResult.error));
            }
        }));}
})

Monday 20 November 2017

Lightning Components Basics: Connect to Salesforce with Server Side Controllers

Lightning Components Basics: Connect to Salesforce with Server Side Controllers

Campaign List Component Code


<aura:component controller="CampingListController"
implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,force:lightningQuickAction" access="global" >
<aura:handler name="init" action="{!c.doInit}" value="{!this}"/>
    

    <aura:attribute name="items" type="Camping_Item__c[]"/>
    <aura:attribute name="newItem" type="Camping_Item__c" default="{ 'sobjectType': 'Camping_Item__c',
                        'Name': '',
                        'Quantity__c': 0,                                          
                        'Price__c': 0,
                        'Packed__c': false }"/>
    <!--<ol>
        <li>Bug Spray</li>
        <li>Bear Repellant</li>
        <li>Goat Food</li>
    </ol>  -->
    <!-- PAGE HEADER -->
    
    <c:campingHeader />

    <lightning:layout >
        <lightning:layoutItem padding="around-small" size="6">
<div aria-labelledby="newcampaignform">
                <fieldset class="slds-box slds-theme--default slds-container--small">
    <legend id="newcampaignform" class="slds-text-heading--small slds-p-vertical--medium">
                    Add Campaign List
                    </legend> 
                    <form class="slds-form--stacked">
                        <lightning:input aura:id="campaignform" label="Campaign Item Name"
                         name="campaignitemname"
                         value="{!v.newItem.Name}"
                         required="true"/>
                        <lightning:input type="number" aura:id="expenseform" label="Quantity"
                         name="campaignitemprice"
                         min="1"
                         formatter="number"
                         step="0.1"
                         value="{!v.newItem.Quantity__c}"
                         messageWhenRangeUnderflow="Enter quantity that's at least 1."/>
<lightning:input type="number" aura:id="expenseform" label="Price"
                         name="campaignitemprice"
                         min="0.1"
                         formatter="currency"
                         step="0.01"
                         value="{!v.newItem.Price__c}"
                         messageWhenRangeUnderflow="Enter an amount that's at least $0.10."/>
<lightning:input type="checkbox" aura:id="expenseform" label="Packed?" 
                         name="expreimbursed"
                         checked="{!v.newItem.Packed__c}"/>
<lightning:button label="Create Camping" class="slds-m-top--medium"
                                 variant="brand" onclick="{!c.clickCreateItem}"/>
                    </form>
                </fieldset>
            </div>
            
</lightning:layoutItem>
    </lightning:layout>
    <c:campingHeader />

<div class="slds-card slds-p-top--medium">
        <header class="slds-card__header">
            <h3 class="slds-text-heading--small">Items</h3>
        </header>
         
        <section class="slds-card__body">
            <div id="list" class="row">
                <aura:iteration items="{!v.items}" var="items">
                    <c:campingListItem item="{!item}"/>
                </aura:iteration>
            </div>
        </section>
    </div>

</aura:component>


CampaignListController Code


({
     // Load expenses from Salesforce
    doInit: function(component, event, helper) {
    
        // Create the action
        var action = component.get("c.getItems");
    
        // Add callback behavior for when response is received
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                component.set("v.items", response.getReturnValue());
            }
            else {
                console.log("Failed with state: " + state);
            }
        });
    
        // Send action off to be executed
        $A.enqueueAction(action);
    },
    
clickCreateItem: function(component, event, helper) {
        var validCamping = component.find('campingform').reduce(function (validSoFar, inputCmp) {
            // Displays error messages for invalid fields
            inputCmp.showHelpMessageIfInvalid();
            return validSoFar && inputCmp.get('v.validity').valid;
        }, true);
if(validCamping){
var addItm = event.getParam("item");
            helper.createItem(component, addItm);
            var newCampingItem = component.get("v.newItem");
            var campings = component.get("v.items");
            var item = JSON.parse(JSON.stringify(newCampingItem));
            campings.push(item);
            component.set("v.items",campings);
            component.set("v.newItem",{ 'sobjectType': 'Camping_Item__c','Name': '','Quantity__c': 0,
                                           'Price__c': 0,'Packed__c': false });
        }


}
})

Campaign  List Helper Code

({
   
    createItem : function(component, campaign) {
        var action = component.get("c.saveItem");
        action.setParams({
            "item": campaign
        });
        action.setCallback(this, function(response){
            var state = response.getState();
            if (state === "SUCCESS") {
                var expenses = component.get("v.items");
                expenses.push(response.getReturnValue());
                component.set("v.items", expenses);
            }
        });
        $A.enqueueAction(action);
    },
})

Sunday 19 November 2017

Lightning Components Basics: Input Data using Forms

Lightning Components Basics: Input Data using Forms

Campaign Header Component Code:


<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,force:lightningQuickAction" access="global" >
    <lightning:layout class="slds-page-header slds-page-header--object-home">
        <lightning:layoutItem>
            <lightning:icon iconName="action:goal" alternativeText="My Camping List"/>
        </lightning:layoutItem>
        <lightning:layoutItem padding="horizontal-small">
            <div class="page-section page-header">
                <h1 class="slds-text-heading--label">Campaign List</h1>
                <h2 class="slds-text-heading--medium">My Campaign List</h2>
            </div>
        </lightning:layoutItem>
    </lightning:layout>

</aura:component>

Campaign List Component Code:


<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,force:lightningQuickAction" access="global" >
<aura:attribute name="items" type="Camping_Item__c[]"/>
    <aura:attribute name="newItem" type="Camping_Item__c" default="{ 'sobjectType': 'Camping_Item__c',
                        'Name': '',
                        'Quantity__c': 0,                                          
                        'Price__c': 0,
                        'Packed__c': false }"/>
    <!--<ol>
        <li>Bug Spray</li>
        <li>Bear Repellant</li>
        <li>Goat Food</li>
    </ol>  -->
    <!-- PAGE HEADER -->
    
    <c:campingHeader/>

    <lightning:layout>
        <lightning:layoutItem padding="around-small" size="6">
<div aria-labelledby="newcampaignform">
                <fieldset class="slds-box slds-theme--default slds-container--small">
    <legend id="newcampaignform" class="slds-text-heading--small slds-p-vertical--medium">
                    Add Campaign List
                    </legend> 
                    <form class="slds-form--stacked">
                        <lightning:input aura:id="campaignform" label="Campaign Item Name"
                         name="campaignitemname"
                         value="{!v.newItem.Name}"
                         required="true"/>
                        <lightning:input type="number" aura:id="expenseform" label="Quantity"
                         name="campaignitemprice"
                         min="1"
                         formatter="number"
                         step="0.1"
                         value="{!v.newItem.Quantity__c}"
                         messageWhenRangeUnderflow="Enter quantity that's at least 1."/>
<lightning:input type="number" aura:id="expenseform" label="Price"
                         name="campaignitemprice"
                         min="0.1"
                         formatter="currency"
                         step="0.01"
                         value="{!v.newItem.Price__c}"
                         messageWhenRangeUnderflow="Enter an amount that's at least $0.10."/>
<lightning:input type="checkbox" aura:id="expenseform" label="Packed?" 
                         name="expreimbursed"
                         checked="{!v.newItem.Packed__c}"/>
<lightning:button label="Create Camping" class="slds-m-top--medium"
                                 variant="brand" onclick="{!c.clickCreateItem}"/>
                    </form>
                </fieldset>
            </div>
            
</lightning:layoutItem>
    </lightning:layout>
    <c:campingHeader/>

<div class="slds-card slds-p-top--medium">
        <header class="slds-card__header">
            <h3 class="slds-text-heading--small">Items</h3>
        </header>
         
        <section class="slds-card__body">
            <div id="list" class="row">
                <aura:iteration items="{!v.items}" var="items">
                    <c:campingListItem item="{!item}"/>
                </aura:iteration>
            </div>
        </section>
    </div>

</aura:component>

Campaign List Controller Code


({
clickCreateItem: function(component, event, helper) {
        var validCamping = component.find('campingform').reduce(function (validSoFar, inputCmp) {
            // Displays error messages for invalid fields
            inputCmp.showHelpMessageIfInvalid();
            return validSoFar && inputCmp.get('v.validity').valid;
        }, true);
if(validCamping){

            var newCampingItem = component.get("v.newItem");
            var campings = component.get("v.items");
            var item = JSON.parse(JSON.stringify(newCampingItem));
            campings.push(item);
            component.set("v.items",campings);
            component.set("v.newItem",{ 'sobjectType': 'Camping_Item__c','Name': '','Quantity__c': 0,
                                           'Price__c': 0,'Packed__c': false });
        }

}
})

Campaign List Item Code


<aura:component implements="flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId" access="global" >
<aura:attribute name="item" type="Camping_Item__c" required="true"/>
<aura:attribute name="Name" type="String"/>
    <p>Hello! {!v.item.Name}</p>
<aura:attribute name="Price" type="String"/>
    <p>Price:
<lightning:formattedNumber value="{!v.item.Price__c}" style="currency"/>
    </p>
    <aura:attribute name="Quantity" type="String"/>
    <p>Price:
<lightning:formattedNumber value="{!v.item.Quantity__c}" style="Number"/>
    </p>
    <aura:attribute name="Packed" type="String"/>
    <p>
        <lightning:input type="toggle"                           
                         label="Packed?"                          
                         name="Packed"                        
                         checked="{!v.item.Packed__c}" />
     </p>
    <div>
<lightning:button label="Packed!"
            onclick="{!c.packItem}"/>
    </div>
    
</aura:component>

Happy Coding !!!