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
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);
-
1what type of attributes were you storing the timer and context vars as ? looks exactly like what I have been trying to build! 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...– StephenNov 23, 2016 at 14:48
-
4PS - 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.– StephenNov 28, 2016 at 17:45
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.
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.
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"/>
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.