Saturday 21 April 2018

Toast Message from a Visualforce Page

Toast Message from a Visualforce Page

Introduction

Pretty much everything I do now around the user interface is via Lightning Components, typically added to a Lightning page so that I can also include standard components. There’s one aspect that continues to frustrate me - when I need to send the user to a custom new record page programatically, when they press a button in a custom component, for example.

  • I can’t use the force:createRecord standard event because, as the docs say, "This event presents a standard page to create a record. That is, it doesn’t respect overrides on the object’s create action.”.
  • I can’t use a Lightning page as these aren’t URL-addressable and there’s no way to navigate to them.
  • I can’t use force:navigateToComponent as that’s still in beta and, while I’m pretty sure it will be supported in the future, there’s no guarantee it will ever go GA.
  • I can use a Lightning app (.app bundle) as these are URL addressable, but when I do they take me outside the Lightning Experience.

So I use Lightning Out for Visualforce and force:navigateToURL to send the user there. 

Events

As my Lightning components are inside the Visualforce iframe, I have no access to the standard Lightning events, such as force:navigateToURL and, to the point of this post, force:showToast. If I try to instantiate one of these events in a Lightning component:

var evt=$A.get("e.force:navigateToURL");

I’ll get null returned.

For many events this isn’t the end of the world though, as I can add a handler in my Visualforce page when I dynamically create the component:

$A.eventService.
        addHandler({
                    event: 'force:showToast',
                    handler: function(event) { 
                                 // take some action
                    }
});

Then when I instantiate the force:showToast event I’ll get one, and when I fire the event my handler function will be invoked I’m still in Visualforce though, in an iframe and not executing in the one.app container, so it doesn’t help that much so far.

One really important aspect I always forget when adding the custom event handler in Lightning Out for Visualforce - my Lightning app that is the bridge between Visualfoce and the underlying Lightning components needs to declare a dependency for the event, otherwise nothing works. Another few hours of my life I won’t get back and even more annoying as it’s about the fifth time I’ve fallen for it:

<aura:dependency resource="markup://force:showToast" type="EVENT"/>

Posting a Message

Visualforce pages can communicate with Lightning components, via the window.postMessage() JavaScript function. It’s slightly clunky in that I need to identify the target origin that I want to send the message to, but I can pull the message from the toast event and send it on wrapped in a JavaScript object:

var lexOrigin = "https://kabtutorial-developer-edition.lightning.force.com";
var message={type: "EventFromVF",
             message: event.getParams().message};
parent.postMessage(message, lexOrigin);

So now all I need is a Lightning component that can receive this and raise it’s own toast event to actually display the message to the user. I called mine ToastReceiver and  a reduced version of the code is shown below :

window.addEventListener("message", function(event) {
    if ( (event.data.type) && (event.data.type=='EventFromVF') )
        var toastEvent = $A.get('e.force:showToast');
	toastEvent.setParams({
    	        type: 'info',
        	    message: event.data.message
	        });
    	toastEvent.fire();
    }
}, false);

My first thought was to wrap my Visualforce page in a Lightning component that iframes it in. This worked perfectly, but left me in exactly the same situation as before - I didn’t have a supported (GA) mechanism to programmatically navigate to that component. It seems that I’m stuck between a rock and a hard place - I need to navigate to a Visualforce page, but somehow that Visualforce page needs to message a Lightning component on the same browser page.

Enter the Lightning Utility Bar

The Lightning Utility Bar allows me to include the same set of Lightning components across every page of my app, so I added one to my Sales app and added my ToastReceiver to it, remembering to check the box that loads the component in the background when the app opens, otherwise my toast messages only appear after I’ve opened the utility bar element.

The diagram below shows the flow:

Screen Shot 2018 04 21 at 16 59 03

I enter a message in my Lightning component, which fires a toast event (1), this is picked up by the event handle in the Visualforce JavaScript, which posts a message (2) that is received by the Lightning component in the utility bar. This fires it’s own toast event (3) that, as it is executing in the one.app container, displays a toast message to the user.

Note that while my toast message originated from a Lighting component, this can also be used to display toast messages from regular Visualforce pages inside the Lightning experience, just post a message. All standard functionality and all generally available. Not a bad result for a Saturday afternoon’s work, even if I say so myself.

Demo

In the short video below my browser is initially on a Lightning page and clicking the button programmatically sends me to a Visualforce page via the force:navigateToURL event, containing a Lightning component with a message input and a button. Clicking the button starts off the flow described above.

Related Posts

 

Saturday 14 April 2018

Managing a List of New Records with Lightning Components

Managing a List of New Records with Lightning Components

Nearly 7 years ago I wrote a blog post on Managing a list of New Records in Visualforce, and I thought it would be interesting to recreate this using Lightning Components and compare  the two.

The basic concept is that I have a collection of new account records and I want to be able to enter details, add or remove rows and save once I’m happy. In Lightning I’ve created a couple of components to manage this - one that looks after the collection of records (NewAccounts) and one that captures the input for a single account (NewAccount).  In the following screenshot each row equates to the NewAccount component:

Screen Shot 2018 04 14 at 16 45 14

One interesting aspect is that the list of records is managed outside of the rows, but each row has a button to allow it to be deleted. While I could try to juke around with the styling to line things up, this is an excellent use case for a Component Facet. A Component Facet is an attribute passed to a contained component that is itself a collection of components. As it is defined in the outer component, it can reference aspects of the outer component. In my case it defines the controller function called when the user clicks the delete button and includes the index of of the element in the collection of records in the button name, so that I can easily locate and remove the element:

<c:NewAccount account="{!account}" index="{!index}">
  <aura:set attribute="buttons">
    <div class="slds-form-element">
      <label class="slds-form-element__label">Actions</label>
      <div class="slds-form-element__control">
        <lightning:button label="Delete" onclick="{!c.deleteRow}"
                          name="{!'btn' + index}" />
      </div>
    </div>
  </aura:set>
</c:NewAccount>

The first major difference is that in Visualforce I have to create my collection of Account records server side, in the constructor of the page controller, while in Lightning my NewAccounts component creates these in it’s init handler:

init : function(component, event, helper) {
    var accounts=[];
    for (var idx=0; idx<5; idx++) {
        accounts.push({sobjectType:'Account'});
    }
    component.set('v.accounts', accounts);
}

The only field that I’m defining when I create each account record is the sobjectType - I don’t think that I actually need this, as on the server side I use a strongly typed array of Account records, but I find it’s a great habit to get into. In terms of the user experience there’s probably not a lot to choose here though - the Visualforce page will take a short while to be created and returned, and Lightning pages are hardly .. lightning fast.

However, all that changes when the user adds or deletes rows. In Visualforce I have to send the list of records back to the server and then carry out the appropriate action. In my lightning component, this is handled in the JavaScript controller. for example when deleting a row:

deleteRow : function(component, event, helper) {
    var name=event.getSource().get("v.name");
    var index=name.substring(3);
    var accounts=component.get('v.accounts');
    accounts.splice(index, 1);
    component.set('v.accounts', accounts);
}

I get the index from the name which has the format ‘btn<index>’ so I just use the array substring prototype function to strip off the ‘btn’ and then use the Array.splice prototype function to remove the element at that position.

In Visualforce I’d probably show some kind of spinner to let the user know that something has happened, whereas in Lightning this happens so quickly there’s no chance for me to get in between and show something. If I really want to draw the users attention, I’d use CSS to highlight the element that was created or do some kind of slow motion hiding of the element before removing it. 

When the user decides to save is the place where I have to do more work in Lightning. In Visualforce I would simply bind the button to a server side action, insert the updated accounts property, and set a page message, maybe after some checking of how many were populated etc. In Lightning I have to figure out the populated records, instantiate a controller action, add my records as s property, hand over to apex and then process the results. While it sounds like a fair bit to do, it actually isn’t that bad, especially if I create a utility function to process the response that all of my components can utilise, which I do every time for production code.

saveRows : function(component, event, helper) {
    var accounts=component.get('v.accounts');
    var toSave=[];
    for (var idx=0; idx<accounts.length; idx++) {
        if ( (null!=accounts[idx].Name) && (''!=accounts[idx].Name) ) {
            toSave.push(accounts[idx]);
        }
    }
    var accAction = component.get("c.SaveAccounts");
    var params={"accountsStr":JSON.stringify(toSave)};
    accAction.setParams(params);
    accAction.setCallback(this, function(response) {
        var state = response.getState();
        if (state === "SUCCESS") {
            var toastEvent=$A.get("e.force:showToast");
            if (toastEvent) {
                toastEvent.setParams({
                        "type":'success',
                        "title":'Success',
                        "message":'Accounts saved'
                });
                        toastEvent.fire();
            } 
        }
        else if (state === "ERROR") {
            var errors = response.getError();
            if (errors) {
                if (errors[0] && errors[0].message) {
                    reject(Error("Error message: " + errors[0].message));
                }
            }
            else {
                reject(Error("Unknown error"));
            }
        }
    });
    $A.enqueueAction(accAction); 
}

Note that I’m sending my list of records back as a JSON string, a habit I got into when the Lightning Components framework had problems with array parameters. I still use it occasionally so that my controller methods can handle multiple types of parameters. I’m always in two minds as to whether this is a good thing - it makes the code more flexible, but more difficult to understand what is going on without appropriately typed parameters. 

There’s not a lot more to the code, but if you want the full picture it’s available on Github.

Related Posts