• Annonces

    • Olivier Devriese

      Nouveauté du forum, les blogs !   30/04/2016

      Envie de créer votre blog FileMaker ? Ca ne peut pas être plus simple qu'avec cette nouvelle section du site FM Source qui est désormais plus qu'un simple forum mais un vrai centre de ressource. Vous pouvez aussi facilement l'alimenter en le liant avec le flux RSS d'un autre blog que vous possédez déjà.
  • billets
    82
  • commentaires
    3
  • vues
    1 853

Handle Lightning Events from a Visualforce Page

Soliant Consulting

61 vues

Today I'm sharing a quick tutorial on how to add Lightning Components to Visualforce pages and then take an event from your Lightning Component and have your Visualforce page handle it. This assumes you already have basic knowledge of Visualforce pages and the ability to create a basic Lightning Component. Before you start, make sure you've defined stylings for your Lightning Design System.

In order to construct this, we need a few elements:

  1. An app container (Visualforce page)
  2. A Lightning Component with a related controller and helper class
  3. An Apex Controller
  4. A Lightning Event

This example uses Casper Harmer's code for a Lookup Component. Some functionality was removed, and the event was added:

SVG Component

<aura:component >
 <aura:attribute name="class" type="String" description="CSS classname for the SVG element" />
 <aura:attribute name="xlinkHref" type="String" description="SLDS icon path. Ex: /assets/icons/utility-sprite/svg/symbols.svg#download" />
 <aura:attribute name="ariaHidden" type="String" default="true" description="aria-hidden true or false. defaults to true" />
</aura:component>

Lookup Component

<aura:component controller="SampleController" access="global" >
	<ltng:require styles="{!$Resource.SLDS213 + '/assets/styles/salesforce-lightning-design-system.css'}" />

	<!-- Component Init Handler -->
	<aura:handler name="init" value="{!this}" action="{!c.init}"/>

	<!-- Attributes -->
	<aura:attribute name="parentRecordId" type="Id" description="Record Id of the Host record (ie if this was a lookup on opp, the opp recid)" access="global"/>
	<aura:attribute name="lookupAPIName" type="String" description="Name of the lookup field ie Primary_Contact__c" access="global"/>
	<aura:attribute name="sObjectAPIName" type="String" required="true" description="The API name of the SObject to search" access="global"/>
	<aura:attribute name="label" type="String" required="true" description="The label to assign to the lookup, eg: Account" access="global"/>
	<aura:attribute name="pluralLabel" type="String" required="true" description="The plural label to assign to the lookup, eg: Accounts" access="global"/>
	<aura:attribute name="recordId" type="Id" description="The current record Id to display" access="global"/>
	<aura:attribute name="listIconSVGPath" type="String" default="/resource/SLDS213/assets/icons/custom-sprite/svg/symbols.svg#custom11" description="The static resource path to the svg icon to use." access="global"/>
	<aura:attribute name="listIconClass" type="String" default="slds-icon-custom-11" description="The SLDS class to use for the icon." access="global"/>
	<aura:attribute name="searchString" type="String" description="The search string to find." access="global"/>
	<aura:attribute name="required" type="Boolean" description="Set to true if this lookup is required" access="global"/>
	<aura:attribute name="filter" type="String" required="false" description="SOSL filter string ie AccountId = '0014B000003Sz5s'" access="global"/>

	<aura:attribute name="callback" type="String" description="Call this to communcate results to parent" access="global" />

	<!-- PRIVATE ATTRS -->
	<aura:attribute name="matches" type="SampleController.Result[]" description="The resulting matches returned by the Apex controller." />

	<aura:registerEvent name="updateLookup" type="c:LookupEvent" />

	<div class="slds">
		<div aura:id="lookup-div" class="slds-lookup" data-select="single" data-scope="single" data-typeahead="true">
		<!-- This is the Input form markup -->
		<div class="slds-form-element">
			<label class="slds-form-element__label" for="lookup">{!v.label}</label>
			<div class="slds-form-element__control slds-input-has-icon slds-input-has-icon--right">
		    	<c:PR_SVG class="slds-input__icon" xlinkHref="/resource/SLDS213/assets/icons/utility-sprite/svg/symbols.svg#search" />
			    <!-- This markup is for when an item is currently selected -->
			    <div aura:id="lookup-pill" class="slds-pill-container slds-hide">
		            <span class="slds-pill slds-pill--bare">
		                <span class="slds-pill__label">
		                    <c:PR_SVG class="{!'slds-icon ' + v.listIconClass + ' slds-icon--small'}" xlinkHref="{!v.listIconSVGPath}" />{!v.searchString}
		                </span>
		                <button class="slds-button slds-button--icon-bare" onclick="{!c.clear}">
		                    <c:PR_SVG class="slds-button__icon" xlinkHref="/resource/SLDS213/assets/icons/utility-sprite/svg/symbols.svg#close" />
		                    <span class="slds-assistive-text">Remove</span>
		                </button>
		            </span>
		        </div>
		        <!-- This markup is for when searching for a string -->
		        <ui:inputText aura:id="lookup" value="{!v.searchString}" class="slds-input" updateOn="keyup" keyup="{!c.search}" blur="{!c.handleBlur}"/>
		    </div>
		</div>
			<!-- This is the lookup list markup. Initially it's hidden -->
			<div aura:id="lookuplist" class="" role="listbox">
			    <div class="slds-lookup__item">
			        <button class="slds-button">
			            <c:PR_SVG class="slds-icon slds-icon-text-default slds-icon--small" xlinkHref="/resource/SLDS213/assets/icons/utility-sprite/svg/symbols.svg#search" />
			            &quot;{!v.searchString}&quot; in {!v.pluralLabel}
			        </button>
			    </div>
			    <ul aura:id="lookuplist-items" class="slds-lookup__list">
			        <aura:iteration items="{!v.matches}" var="match">
			            <li class="slds-lookup__item">
			                <a id="{!globalId + '_id_' + match.SObjectId}" role="option" onclick="{!c.select }">
			                    <c:PR_SVG class="{!'slds-icon ' + v.listIconClass + ' slds-icon--small'}" xlinkHref="{!v.listIconSVGPath}" />{!match.SObjectLabel}
			                </a>
			            </li>
			        </aura:iteration>
			    </ul>
			</div>
		</div>
	</div>
</aura:component>

Lookup JS Controller

/**
 * (c) Tony Scott. This code is provided as is and without warranty of any kind.
 * Adapted for use in a VF page, removed need for two components, removed events - Caspar Harmer
 *
 * This work by Tony Scott is licensed under a Creative Commons Attribution 3.0 Unported License.
 * http://creativecommons.org/licenses/by/3.0/deed.en_US
 */
({
    /**
     * Search an SObject for a match
     */
    search : function(cmp, event, helper) {
        helper.doSearch(cmp);
    },

    /**
     * Select an SObject from a list
     */
    select: function(cmp, event, helper) {

        helper.handleSelection(cmp, event);
    },

    /**
     * Clear the currently selected SObject
     */
    clear: function(cmp, event, helper) {

        helper.clearSelection(cmp);
    },

    /**
     * If the input is requred, check if there is a value on blur
     * and mark the input as error if no value
     */
     handleBlur: function (cmp, event, helper) {

         helper.handleBlur(cmp);
     },

    init : function(cmp, event, helper){
      try{
        //first load the current value of the lookup field
        helper.init(cmp);
        helper.loadFirstValue(cmp);

        }catch(ex){
          console.log(ex);
        }
      }
})

Lookup JS Helper

/**
 * (c) Tony Scott. This code is provided as is and without warranty of any kind.
 * Adapted for use in a VF page, removed need for two components, removed events - Caspar Harmer
 *
 * This work by Tony Scott is licensed under a Creative Commons Attribution 3.0 Unported License.
 * http://creativecommons.org/licenses/by/3.0/deed.en_US
 */
({
    //lookup already initialized
    initStatus : {},
    init : function (cmp){
      var required = cmp.get('v.required');
      if (required){
        var cmpTarget = cmp.find('lookup-form-element');
        $A.util.addClass(cmpTarget, 'slds-is-required');
      }
    },

    /**
     * Perform the SObject search via an Apex Controller
     */
    doSearch : function(cmp) {
        // Get the search string, input element and the selection container
        var searchString = cmp.get('v.searchString');
        var inputElement = cmp.find('lookup');
        var lookupList = cmp.find('lookuplist');

        // Clear any errors and destroy the old lookup items container
        inputElement.set('v.errors', null);

        // We need at least 2 characters for an effective search
        console.log('searchString = ' + searchString);
        if (typeof searchString === 'undefined' || searchString.length &lt; 2)
        {
            // Hide the lookuplist
            //$A.util.addClass(lookupList, 'slds-hide');
            return;
        }

        // Show the lookuplist
        console.log('lookupList = ' + lookupList);
        $A.util.removeClass(lookupList, 'slds-hide');

        // Get the API Name
        var sObjectAPIName = cmp.get('v.sObjectAPIName');
        // Get the filter value, if any
        var filter = cmp.get('v.filter');

        // Create an Apex action
        var action = cmp.get('c.lookup');

        // Mark the action as abortable, this is to prevent multiple events from the keyup executing
        action.setAbortable();

        // Set the parameters
        action.setParams({ "searchString" : searchString, "sObjectAPIName" : sObjectAPIName, "filter" : filter});

        // Define the callback
        action.setCallback(this, function(response) {
            var state = response.getState();
            console.log("State: " + state);

            // Callback succeeded
            if (cmp.isValid() &amp;&amp; state === "SUCCESS")
            {
                // Get the search matches
                var matches = response.getReturnValue();
                console.log("matches: " + matches);

                // If we have no matches, return nothing
                if (matches.length == 0)
                {
                    //cmp.set('v.matches', null);
                    return;
                }

                // Store the results
                cmp.set('v.matches', matches);
            }
            else if (state === "ERROR") // Handle any error by reporting it
            {
                var errors = response.getError();

                if (errors)
                {
                    if (errors[0] &amp;&amp; errors[0].message)
                    {
                        this.displayToast('Error', errors[0].message);
                    }
                }
                else
                {
                    this.displayToast('Error', 'Unknown error.');
                }
            }
        });

        // Enqueue the action
        $A.enqueueAction(action);
    },

    /**
     * Handle the Selection of an Item
     */
    handleSelection : function(cmp, event) {
        // Resolve the Object Id from the events Element Id (this will be the &lt;a&gt; tag)
        var objectId = this.resolveId(event.currentTarget.id);
		// Set the Id bound to the View
		cmp.set('v.recordId', objectId);

        // The Object label is the inner text)
        var objectLabel = event.currentTarget.innerText;

		// Update the Searchstring with the Label
		cmp.set("v.searchString", objectLabel);

        // Log the Object Id and Label to the console
        console.log('objectId=' + objectId);
        console.log('objectLabel=' + objectLabel);

        //This is important.  Notice how i get the event.
        var updateEvent = $A.get("e.c:LookupEvent");
		updateEvent.setParams({"lookupVal": objectId, "lookupLabel": objectLabel});
		updateEvent.fire();
    },

    /**
     * Clear the Selection
     */
    clearSelection : function(cmp) {
        // Clear the Searchstring
        cmp.set("v.searchString", '');
				cmp.set('v.recordId', null);

        var func = cmp.get('v.callback');
        console.log(func);
        if (func){
          func({id:'',name:''});
        }

        // Hide the Lookup pill
        var lookupPill = cmp.find("lookup-pill");
        //$A.util.addClass(lookupPill, 'slds-hide');

        // Show the Input Element
        var inputElement = cmp.find('lookup');
        $A.util.removeClass(inputElement, 'slds-hide');

        // Lookup Div has no selection
        var inputElement = cmp.find('lookup-div');
        $A.util.removeClass(inputElement, 'slds-has-selection');

        // If required, add error css
        var required = cmp.get('v.required');
        if (required){
          var cmpTarget = cmp.find('lookup-form-element');
          $A.util.removeClass(cmpTarget, 'slds-has-error');
        }
    },

    handleBlur: function(cmp) {
       var required = cmp.get('v.required');
       if (required){
         var cmpTarget = cmp.find('lookup-form-element');
         $A.util.addClass(cmpTarget, 'slds-has-error');
       }
   },

    /**
     * Resolve the Object Id from the Element Id by splitting the id at the _
     */
    resolveId : function(elmId)
    {
        var i = elmId.lastIndexOf('_');
        return elmId.substr(i+1);
    },

    /**
     * Display a message
     */
    displayToast : function (title, message)
    {
        var toast = $A.get("e.force:showToast");

        // For lightning1 show the toast
        if (toast)
        {
            //fire the toast event in Salesforce1
            toast.setParams({
                "title": title,
                "message": message
            });

            toast.fire();
        }
        else // otherwise throw an alert
        {
            alert(title + ': ' + message);
        }
    },

    loadFirstValue : function(cmp){

        var action = cmp.get('c.getCurrentValue');
        var self = this;
        action.setParams({
            'type' : cmp.get('v.sObjectAPIName'),
            'value' : cmp.get('v.recordId'),
        });

        action.setCallback(this, function(a) {
            if(a.error &amp;&amp; a.error.length){
                return $A.error('Unexpected error: '+a.error[0].message);
            }
            var result = a.getReturnValue();
            cmp.set("v.searchString", result);

            if (null!=result){
              // Show the Lookup pill
              var lookupPill = cmp.find("lookup-pill");
              $A.util.removeClass(lookupPill, 'slds-hide');

              // Lookup Div has selection
              var inputElement = cmp.find('lookup-div');
              $A.util.addClass(inputElement, 'slds-has-selection');
            }
        });
        $A.enqueueAction(action);
    }
})

Apex Controller

public with sharing class SampleController
{
	@AuraEnabled
	public static String getCurrentValue(String type, String value){
		if(String.isBlank(type))
		{
			System.debug('type is null');
			return null;
		}

		ID lookupId = null;
		try
		{
			lookupId = (ID)value;
		}catch(Exception e){
			System.debug('Exception = ' + e.getMessage());
			return null;
		}

		if(String.isBlank(lookupId))
		{
			System.debug('lookup is null');
			return null;
		}

		SObjectType objType = Schema.getGlobalDescribe().get(type);
		if(objType == null){
			System.debug('objType is null');
			return null;
		}

		String nameField = getSobjectNameField(objType);
		String query = 'Select Id, ' + nameField + ' From ' + type + ' Where Id = \'' + lookupId + '\'';
		System.debug('### Query: '+query);
		List&lt;SObject&gt; oList = Database.query(query);
		if(oList.size()==0)
		{
			System.debug('objlist empty');
			return null;
		}
		return (String) oList[0].get(nameField);
	}

	/*
	   * Returns the "Name" field for a given SObject (e.g. Case has CaseNumber, Account has Name)
	*/
	private static String getSobjectNameField(SobjectType sobjType)
	{
		//describes lookup obj and gets its name field
		String nameField = 'Name';
		Schema.DescribeSObjectResult dfrLkp = sobjType.getDescribe();
		for(schema.SObjectField sotype : dfrLkp.fields.getMap().values()){
		  Schema.DescribeFieldResult fieldDescObj = sotype.getDescribe();
		  if(fieldDescObj.isNameField() ){
		    nameField = fieldDescObj.getName();
		      break;
		  }
		}
		return nameField;
	}
	/**
	 * Aura enabled method to search a specified SObject for a specific string
	 */
	@AuraEnabled
	public static Result[] lookup(String searchString, String sObjectAPIName)
	{
		// Sanitze the input
		String sanitizedSearchString = String.escapeSingleQuotes(searchString);
		String sanitizedSObjectAPIName = String.escapeSingleQuotes(sObjectAPIName);

		List&lt;Result&gt; results = new List&lt;Result&gt;();

		// Build our SOSL query
		String searchQuery = 'FIND \'' + sanitizedSearchString + '*\' IN ALL FIELDS RETURNING ' + sanitizedSObjectAPIName + '(id,name) Limit 50';

		// Execute the Query
		List&lt;List&lt;SObject&gt;&gt; searchList = search.query(searchQuery);
		System.debug('searchList = ' + searchList);
		System.debug('searchQuery = ' + searchQuery);

		// Create a list of matches to return
		for (SObject so : searchList[0])
		{
		    results.add(new Result((String)so.get('Name'), so.Id));
		}

		System.debug('results = ' + results);
		return results;
	}

	/**
	 * Inner class to wrap up an SObject Label and its Id
	 */
	public class Result
	{
		@AuraEnabled public String SObjectLabel {get; set;}
		@AuraEnabled public Id SObjectId {get; set;}

		public Result(String sObjectLabel, Id sObjectId)
		{
		    this.SObjectLabel = sObjectLabel;
		    this.SObjectId = sObjectId;
		}
	}
}

Most of this code is from Caspar's post, but there are a few differences. The first major difference is in the helper's "handleSelection" method, where it instead activates the event for the selection and associates values to the event's properties:

var updateEvent = $A.get("e.c:LookupEvent");
updateEvent.setParams({"lookupVal": objectId, "lookupLabel": objectLabel});
updateEvent.fire();

This is important because instead of passing the callback to the Visualforce page instead we will have it waiting for an event to fire. The Lookup component also contained a reference to an event:

<aura:registerEvent name="updateLookup" type="c:LookupEvent" />

The event code is very simple. Note: the type needs to be APPLICATION instead of COMPONENT:

<aura:event type="APPLICATION" description="Event template">
	<aura:attribute name="lookupVal" type="String" description="Response from calls" access="global" />
	<aura:attribute name="lookupLabel" type="String" description="Response from calls" access="global" />
</aura:event>

At this point, you will have an active component that will run an event when a selection is made. The missing link so far is the Visualforce page:


<apex:page applyBodyTag="false" standardController="Contact" docType="html-5.0" showHeader="true" sidebar="false" standardStylesheets="false">
	<html xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
		<head>
		  <apex:includeScript value="/lightning/lightning.out.js" />
		  <apex:stylesheet value="{!URLFOR($Resource.SLDS213, 'assets/styles/salesforce-lightning-design-system-vf.css')}"/>
		</head>
		<body >
		  <div class="slds">
		    <div class="slds-form-element slds-m-top--xx-small">
		      <div class="slds-m-right--x-small" id="account_lookup"></div>
		    </div>
		  </div>
		</body>
		<script>
			var visualForceFunction = function(event)
			{
				var myEventData1 = event.getParam("lookupVal");
				var label = event.getParam("lookupLabel");
				console.log('response data = ' + myEventData1 + ' : ' + label);
			};

			// and...
			$Lightning.use("c:expensesAppVF", function()
			{
				$Lightning.createComponent
				(
					"c:Lookup",
					{
						
						recordId: "{!contact.AccountId}",
						label: "Account",
						pluralLabel: "Accounts",
						sObjectAPIName: "Account"
					},
					"account_lookup",
					function()
					{
					    $A.eventService.addHandler({ "event": "c:LookupEvent", "handler" : visualForceFunction});
					}
				);
			});
		</script>
	</html>
</apex:page>

Instead of receiving a callback, this version adds a handler to wait for the event to fire when an Account is selected. This lookup could work for any object that you want as long as you change the API name of the objects.  You can replace the console.log in the event handler and instead have it do some real functionality, such as actually saving the values returned from the Lightning Component on the Salesforce record.

The post Handle Lightning Events from a Visualforce Page appeared first on Soliant Consulting.


Afficher la totalité du billet


  Signaler le billet


0 Commentaire


Aucun commentaire à afficher.

Créer un compte ou se connecter pour commenter

Vous devez être membre afin de pouvoir déposer un commentaire

Créer un compte

Créez un compte sur notre communauté. C’est facile !


Créer un nouveau compte

Se connecter

Vous avez déjà un compte ? Connectez-vous ici.


Connectez-vous maintenant