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?

6

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);
| improve this answer | |
  • 1
    what type of attributes were you storing the timer and context vars as ? looks exactly like what I have been trying to build! – CloudHugger Sep 19 '16 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 Nov 23 '16 at 14:48
  • 3
    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 Nov 28 '16 at 17:45
4

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.

| improve this answer | |
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.

| improve this answer | |
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"/>
| improve this answer | |
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.

| improve this answer | |

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.