8

The Lightning Components Tutorial contains an example where a query is executed when the onkeyup event fires. This results in the query executing for each letter typed into the search bar. I would like to delay the query, and then execute it when the user has stopped typing. I am looking to do a technique like in this StackExchange question with setTimeout. When I define a variable in the controller to hold the timeout, it does not persist. How would I accomplish that within a Lightning Component?

5 Answers 5

7

I have managed to solve this general problem in following way. Keys here are to save the timer and to use the $A.run as peter mentions.

var delay = 150;

        var timer = component.get('v.timer');

       // 0.6 seconds delay after last input



        clearTimeout(timer);

        timer = setTimeout(function(){
            $A.run(function(){
               component.set('v.context', context);
                $A.get('e.c:ObjectSearch').fire();
            });

            clearTimeout(timer);
            component.set('v.timer', null);
        }, delay);

        component.set('v.timer', timer);
3
  • 1
    what type of attributes were you storing the timer and context vars as ? looks exactly like what I have been trying to build! Commented Sep 19, 2016 at 6:27
  • According to Mozilla's page on timers: The returned timeoutID is a numeric, non-zero value which identifies the timer created by the call to setTimeout(); this value can be passed to Window.clearTimeout() to cancel the timeout. ....so "v.timer" is probably a number? "v.context"...not sure what this is, as I'm not sure how it's being used...
    – Stephen
    Commented Nov 23, 2016 at 14:48
  • 4
    PS - for those who stumble across this post while searching, according to the Winter '16 release notes (releasenotes.docs.salesforce.com/en-us/winter16/release-notes/…), $A.run() has been deprecated and $A.getCallback() has replaced it. Aura documentation app also mentions that $A.run is deprecated.
    – Stephen
    Commented Nov 28, 2016 at 17:45
6

Building on Adrian's very helpful previous answer, You can also implement the delay functionality without an event. Lets take an example of an input text.

The component will consist of: Couple of things to note, without the updateOn being specified the searchString attribute was not being updated before call of function for some reason.

<aura:attribute name="searchString" type="String"/> 
<aura:attribute name="timer" type="Integer"/>
<ui:inputText aura:id="searchString" label="Search"
                  value="{!v.searchString}"
                  updateOn="keyup"
                  keyup="{!c.SearchKeyChange}"/>

The controller will contain the SearchKeyChange reference as a function.

({
    SearchKeyChange : function(component, event, helper) {
        var timer = component.get('v.timer');
        clearTimeout(timer);

        var timer = setTimeout(function(){
            var newlst = [];
            helper.getSearchResults(component,event)
            clearTimeout(timer);
            component.set('v.timer', null);
        }, 500);

        component.set('v.timer', timer);
    }
})

This will make a reference to the helper function which will make the actual call to the controller method getItems. Since the action is being called in the helper method (not sure) $A.enqueueAction(action); had to be surrounded by the $A.getCallback() function for the en-queue operation to run successfully. The else operation is used to clear the existing list of items.

The helper function:

({
    getSearchResults : function(component,event){
        var action = component.get("c.getItems");
        action.setParams({
            "searchString": component.get("v.searchString")
        });
        action.setCallback(this, function(response){                
            var state = response.getState();
            if (component.isValid() && state === "SUCCESS") {
                console.log(response.getReturnValue());
                if(response.getReturnValue()!=null)
                    component.set("v.items", response.getReturnValue());
                else{   
                    var items = component.get("v.items");
                    items.splice(0,items.length);
                    component.set("v.items",items);
                }
            }
        });
        $A.getCallback(function() {
            $A.enqueueAction(action);
        })();
    }
})

Hope this helps.

3

Lightning Component Framework does not like you running arbitrary JS without it being in the LCF queue.

The way you get that to happen is to use $A.run(function(){}) and write the code of what you want to have happen (such as your setTimeout call) in the body of the method.

In one instance I'm doing something similar in a component using setInterval.

        $A.run(function(){

            //setting interval Id into the helper so I can get to it from other contexts
            //in my component and remove when needed.
            helper.intervalId = setInterval(function(){

                var action = component.get('c.recordMeasurements');
                action.setParams(
                    {
                      ...
                    });

                $A.enqueueAction(action);

            },10000);
        });

I've not tried with setTimeout, that I recall...but there is no reason it shouldn't work. The important thing is to tell $A (the Aura) instance that you want to run some JS somewhere.

3

Building on Adrian's very helpful previous answer, for those looking for a solution that doesn't use $A.run() and works with the tutorial linked above, you can use the following revised function definition in your controller:

onSearchKeyChange : function(component, event, helper) {

    var myEvent = $A.get("e.c:SearchKeyChange");
    myEvent.setParams({"searchKey": event.target.value});

    var timer = component.get('v.timer');
    clearTimeout(timer);

    var timer = setTimeout(function(){
        myEvent.fire();
        clearTimeout(timer);
        component.set('v.timer', null);
    }, 200);

    component.set('v.timer', timer);
}

On the component, I set the timer attribute as follows:

<aura:attribute name="timer" type="Integer"/>
0

If keyUp doesn't suit your use case, you can also try the other change events that are supported by aura:input: http://documentation.auraframework.org/auradocs#reference?descriptor=ui:input&defType=component.

I'd suggest trying onChange instead of onKeyUp.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .