11

We have a Lightning Component showing a span tag with class slds-badge. We add another class to the tag depending on a field value of a Account. We show this component on the record page of said Account, so within the standard app, not a custom one.

Our component correctly renders the different colours based on the class returned from the controller when we open the Account page. Our challenge lies in the fact that we want to update the component when the field value changes. However, we are having trouble catching the event that fires (if there is an event in the first place) when the field change is saved.

We tried catching the aura:waiting or aura:doneWaiting events, which didn't really work as these events fire a lot (hundreds of times) during the page life cycle. The thing we tried next was to listen to the event fired when a toast is shown, which typically happens when a field change is succesful or a related object (like a task) is created. All in all this works, but we're not totally satisfied with the situation, because what happens if Salesforce decides to stop showing toasts after a record is saved?

Do you know of any more robust ways to listen to events on field change?

Component code

<aura:component implements="flexipage:availableForAllPageTypes,force:hasRecordId" access="global" controller="stagebar_Account_Lightning">
    <aura:attribute name="iscont" type="Boolean" default="true"/>
    <aura:attribute name="recordId" type="Id"/>

    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    <aura:handler event="force:showToast" action="{!c.recordSaved}"/>

    <div class="slds">
        <ltng:require styles="/resource/SLDS103/assets/styles/salesforce-lightning-design-system-ltng.css"/>
            <span aura:id="isContacted" class="slds-badge">{!$Label.c.Is_contacted}</span>
    </div>
</aura:component>

JS Controller Code

({
    doInit : function(component, event, helper) {
        helper.getContacted(component);
    },

    recordSaved: function(component, event, helper) {
        helper.getContacted(component);
    }
})

The controller basically calls the init method again if it detects a toast.

Helper code

({
    getContacted : function(cmp){
        var action = cmp.get("c.accountIsContacted");
        action.setCallback(this, function(a){
            if(a.getState() === "SUCCESS"){
                var cmpTarget = cmp.find("isContacted");
                this.setBadgeColor(cmpTarget, a.getReturnValue());
            } 
        });
        $A.enqueueAction(action);
    },

//Color the badges, helper method.
setBadgeColor: function(target, retValue) {
    console.log("inside setBadgeColor");
    if(retValue == 'green'){
        $A.util.addClass(target, "slds-badge-green");
        $A.util.removeClass(target, "slds-badge-yellow");
        $A.util.removeClass(target, "slds-badge-grey");
        $A.util.removeClass(target, "slds-badge-red");
    }else if(retValue == 'yellow') {
        $A.util.removeClass(target, "slds-badge-green");
        $A.util.addClass(target, "slds-badge-yellow");
        $A.util.removeClass(target, "slds-badge-grey");
        $A.util.removeClass(target, "slds-badge-red");
    } else if(retValue == 'grey'){
        $A.util.removeClass(target, "slds-badge-green");
        $A.util.removeClass(target, "slds-badge-yellow");
        $A.util.addClass(target, "slds-badge-grey");
        $A.util.removeClass(target, "slds-badge-red");

    } else if(retValue == 'red'){
        $A.util.removeClass(target, "slds-badge-green");
        $A.util.removeClass(target, "slds-badge-yellow");
        $A.util.removeClass(target, "slds-badge-grey");
        $A.util.addClass(target, "slds-badge-red");
    }
}
])

Apex Controller (only relevant code)

@AuraEnabled
    public static String accountIsContacted() {
        if(currentAccount.LastActivityDate > Date.today().addYears(-1) && currentAccount.LastActivityDate != null){
            return badgeColor.green.name();
        } else {
            return badgeColor.yellow.name();
        }
    }
  • Every time I've looked at your question, I've always felt as though information needed to help you was missing. Now that you're down to 2 days left on your bounty I'm going to ask that you post your controller and any other related code like event handlers. Without it, it doesn't appear that you're going to get an answer that you'll find helpful. – crmprogdev May 30 '16 at 13:41
  • You're right, @crmprogdev. I've added our code for the helper and the Apex controller. – Folkert May 31 '16 at 14:13
  • If I understand correctly, you're not using any kind of server side controller to get your callback following the action method to save. You're relying entirely on Aura to do that for you. Is that right? – crmprogdev May 31 '16 at 14:27
  • That's right, I'm only using service side controller to return a color in a String. – Folkert May 31 '16 at 14:38
  • why not wire up a handler for the force:refreshView event, fetch and reinitialize the needed value again? just an thought – Praveen Jun 30 '16 at 18:57
3

I recommend you look at this page in the Lightning Components Documentation: Calling a Server-Side Action.

Using parts of the example code provided, you could set up a server-side "echo" that gets returned to your client side controller when the action method is complete as described at the bottom of the 2nd code section on that page following where the action method is enqueued:

In the client-side controller, we use the value provider of c to invoke a server-side controller action. We also use the c syntx in markup to invoke a client-side controller action. The cmp.get("c.serverEcho") call indicates that we are calling the serverEcho method in the server-side controller. The method name in the server-side controller must match everything after the c. in the client-side call.

There are several notes on the page of importance. This note may one that applies to what's happening in your code:

If your action is not executing, make sure that you're not executing code outside the framework's normal rerendering lifecycle. For example, if you use window.setTimeout() in an event handler to execute some logic after a time delay, wrap your code in $A.getCallback(). You don't need to use $A.getCallback() if your code is executed as part of the framework's call stack; for example, your code is handling an event or in the callback for a server-side controller action.

One of things to note that could also be useful to you is the following:

Always add an isValid() check if you reference a component in asynchronous code, such as a callback or a timeout. If you navigate elsewhere in the UI while asynchronous code is executing, the framework unrenders and destroys the component that made the asynchronous request. You can still have a reference to that component, but it is no longer valid. Add an isValid() call to check that the component is still valid before processing the results of the asynchronous request.

Additionally, there's this:

setCallback() has a third parameter that registers the action state that invokes the callback. If you don't specify the third argument for setCallback(), it defaults to registering the SUCCESS and ERROR states. To set a callback for another state, such as ABORTED, you can call setCallback() multiple times with the action state set explicitly in the third argument. For example:

Should none of the above solve your issue, It would seem your only recourse will be to insert the record yourself in a synchronous operation if you want to improve performance and obtain reliable confirmation within your component that the action method has completed.

  • Thanks for the link, but that's already what I'm doing. cmp.get("c.accountIsContacted"); is essentially the same as cmp.get("c.serverEcho") referred to in the link. You have a good point concerning the isValid() check, I should add that. Unfortunately that doesn't help my use case of refreshing the component if something else happens in another component on the same page. Inserting the record myself would be an option, but I want to leverage the standard components wherever possible. – Folkert Jun 1 '16 at 7:30
  • If it were me, I'd be doing multiple Callbacks where I assumed Success unless or until an error was returned from a Callback with either an Error state or Aborted, at which point I'd do a retry of the Save. If a 2nd attempt failed again, then I'd use an error handler of some kind to notify the user the record can't be update at this time and to try again later. – crmprogdev Jun 1 '16 at 12:00
1
+50

Instead of just referencing the field by it's binding, have you tried:

<ui:inputText value="{!c.Is_contacted}" label="my output" change="{!c.recordSaved}"/>

It should work even if the field isn't visible.

Unless you mean when the record is saved through another process, then you may need to look into using the Streaming API.

  • The thing is that it's not necessarily a field change that would cause a change in the component. It could be a combination of factors, including field changes or for instance the creation of a new task related to the object. – Folkert May 30 '16 at 7:15
  • Then I believe you would need be to use the streaming API and push notifications – brezotom May 31 '16 at 14:00
  • I'm afraid streaming API is a bit overkill to implement, but a good suggestion nonetheless. I'll award you the bounty for that if no other answer comes in the next few hours. – Folkert Jun 1 '16 at 7:32
0

I think this is what you are looking for:

component:

   <aura:attribute name="body" type="String" description="The body" />
   <aura:handler name="change" value="{!v.body}" action="{!c.onBodyChange}"/>

Controller:

onBodyChange : function onBodyChange(cmp, evt, hlp) {
//do some stuff
}
  • I'm afraid this is not going to work, as the things that are changing are not in my own component (thus not in v.body) but in standard components such as Record Detail fields. – Folkert May 30 '18 at 14:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.