How to put a delay on AngularJS instant search? - search

I have a performance issue that I can't seem to address. I have an instant search but it's somewhat laggy, since it starts searching on each keyup().
JS:
var App = angular.module('App', []);
App.controller('DisplayController', function($scope, $http) {
$http.get('data.json').then(function(result){
$scope.entries = result.data;
});
});
HTML:
<input id="searchText" type="search" placeholder="live search..." ng-model="searchText" />
<div class="entry" ng-repeat="entry in entries | filter:searchText">
<span>{{entry.content}}</span>
</div>
The JSON data isn't even that large, 300KB only, I think what I need to accomplish is to put a delay of ~1 sec on the search to wait for the user to finish typing, instead of performing the action on each keystroke. AngularJS does this internally, and after reading docs and other topics on here I couldn't find a specific answer.
I would appreciate any pointers on how I can delay the instant search.

UPDATE
Now it's easier than ever (Angular 1.3), just add a debounce option on the model.
<input type="text" ng-model="searchStr" ng-model-options="{debounce: 1000}">
Updated plunker:
http://plnkr.co/edit/4V13gK
Documentation on ngModelOptions:
https://docs.angularjs.org/api/ng/directive/ngModelOptions
Old method:
Here's another method with no dependencies beyond angular itself.
You need set a timeout and compare your current string with the past version, if both are the same then it performs the search.
$scope.$watch('searchStr', function (tmpStr)
{
if (!tmpStr || tmpStr.length == 0)
return 0;
$timeout(function() {
// if searchStr is still the same..
// go ahead and retrieve the data
if (tmpStr === $scope.searchStr)
{
$http.get('//echo.jsontest.com/res/'+ tmpStr).success(function(data) {
// update the textarea
$scope.responseData = data.res;
});
}
}, 1000);
});
and this goes into your view:
<input type="text" data-ng-model="searchStr">
<textarea> {{responseData}} </textarea>
The mandatory plunker:
http://plnkr.co/dAPmwf

(See answer below for a Angular 1.3 solution.)
The issue here is that the search will execute every time the model changes, which is every keyup action on an input.
There would be cleaner ways to do this, but probably the easiest way would be to switch the binding so that you have a $scope property defined inside your Controller on which your filter operates. That way you can control how frequently that $scope variable is updated. Something like this:
JS:
var App = angular.module('App', []);
App.controller('DisplayController', function($scope, $http, $timeout) {
$http.get('data.json').then(function(result){
$scope.entries = result.data;
});
// This is what you will bind the filter to
$scope.filterText = '';
// Instantiate these variables outside the watch
var tempFilterText = '',
filterTextTimeout;
$scope.$watch('searchText', function (val) {
if (filterTextTimeout) $timeout.cancel(filterTextTimeout);
tempFilterText = val;
filterTextTimeout = $timeout(function() {
$scope.filterText = tempFilterText;
}, 250); // delay 250 ms
})
});
HTML:
<input id="searchText" type="search" placeholder="live search..." ng-model="searchText" />
<div class="entry" ng-repeat="entry in entries | filter:filterText">
<span>{{entry.content}}</span>
</div>

In Angular 1.3 I would do this:
HTML:
<input ng-model="msg" ng-model-options="{debounce: 1000}">
Controller:
$scope.$watch('variableName', function(nVal, oVal) {
if (nVal !== oVal) {
myDebouncedFunction();
}
});
Basically you're telling angular to run myDebouncedFunction(), when the the msg scope variable changes. The attribute ng-model-options="{debounce: 1000}" makes sure that msg can only update once a second.

<input type="text"
ng-model ="criteria.searchtext""
ng-model-options="{debounce: {'default': 1000, 'blur': 0}}"
class="form-control"
placeholder="Search" >
Now we can set ng-model-options debounce with time and when blur, model need to be changed immediately otherwise on save it will have older value if delay is not completed.

For those who uses keyup/keydown in the HTML markup.
This doesn't uses watch.
JS
app.controller('SearchCtrl', function ($scope, $http, $timeout) {
var promise = '';
$scope.search = function() {
if(promise){
$timeout.cancel(promise);
}
promise = $timeout(function() {
//ajax call goes here..
},2000);
};
});
HTML
<input type="search" autocomplete="off" ng-model="keywords" ng-keyup="search()" placeholder="Search...">

Debounced / throttled model updates for angularjs : http://jsfiddle.net/lgersman/vPsGb/3/
In your case there is nothing more to do than using the directive in the jsfiddle code like this:
<input
id="searchText"
type="search"
placeholder="live search..."
ng-model="searchText"
ng-ampere-debounce
/>
Its basically a small piece of code consisting of a single angular directive named "ng-ampere-debounce" utilizing http://benalman.com/projects/jquery-throttle-debounce-plugin/ which can be attached to any dom element. The directive reorders the attached event handlers so that it can control when to throttle events.
You can use it for throttling/debouncing
* model angular updates
* angular event handler ng-[event]
* jquery event handlers
Have a look : http://jsfiddle.net/lgersman/vPsGb/3/
The directive will be part of the Orangevolt Ampere framework (https://github.com/lgersman/jquery.orangevolt-ampere).

Just for users redirected here:
As introduced in Angular 1.3 you can use ng-model-options attribute:
<input
id="searchText"
type="search"
placeholder="live search..."
ng-model="searchText"
ng-model-options="{ debounce: 250 }"
/>

I believe that the best way to solve this problem is by using Ben Alman's plugin jQuery throttle / debounce. In my opinion there is no need to delay the events of every single field in your form.
Just wrap your $scope.$watch handling function in $.debounce like this:
$scope.$watch("searchText", $.debounce(1000, function() {
console.log($scope.searchText);
}), true);

Another solution is to add a delay functionality to model update. The simple directive seems to do a trick:
app.directive('delayedModel', function() {
return {
scope: {
model: '=delayedModel'
},
link: function(scope, element, attrs) {
element.val(scope.model);
scope.$watch('model', function(newVal, oldVal) {
if (newVal !== oldVal) {
element.val(scope.model);
}
});
var timeout;
element.on('keyup paste search', function() {
clearTimeout(timeout);
timeout = setTimeout(function() {
scope.model = element[0].value;
element.val(scope.model);
scope.$apply();
}, attrs.delay || 500);
});
}
};
});
Usage:
<input delayed-model="searchText" data-delay="500" id="searchText" type="search" placeholder="live search..." />
So you just use delayed-model in place of ng-model and define desired data-delay.
Demo: http://plnkr.co/edit/OmB4C3jtUD2Wjq5kzTSU?p=preview

I solved this problem with a directive that basicly what it does is to bind the real ng-model on a special attribute which I watch in the directive, then using a debounce service I update my directive attribute, so the user watch on the variable that he bind to debounce-model instead of ng-model.
.directive('debounceDelay', function ($compile, $debounce) {
return {
replace: false,
scope: {
debounceModel: '='
},
link: function (scope, element, attr) {
var delay= attr.debounceDelay;
var applyFunc = function () {
scope.debounceModel = scope.model;
}
scope.model = scope.debounceModel;
scope.$watch('model', function(){
$debounce(applyFunc, delay);
});
attr.$set('ngModel', 'model');
element.removeAttr('debounce-delay'); // so the next $compile won't run it again!
$compile(element)(scope);
}
};
});
Usage:
<input type="text" debounce-delay="1000" debounce-model="search"></input>
And in the controller :
$scope.search = "";
$scope.$watch('search', function (newVal, oldVal) {
if(newVal === oldVal){
return;
}else{ //do something meaningful }
Demo in jsfiddle: http://jsfiddle.net/6K7Kd/37/
the $debounce service can be found here: http://jsfiddle.net/Warspawn/6K7Kd/
Inspired by eventuallyBind directive http://jsfiddle.net/fctZH/12/

Angular 1.3 will have ng-model-options debounce, but until then, you have to use a timer like Josue Ibarra said. However, in his code he launches a timer on every key press. Also, he is using setTimeout, when in Angular one has to use $timeout or use $apply at the end of setTimeout.

Why does everyone wants to use watch? You could also use a function:
var tempArticleSearchTerm;
$scope.lookupArticle = function (val) {
tempArticleSearchTerm = val;
$timeout(function () {
if (val == tempArticleSearchTerm) {
//function you want to execute after 250ms, if the value as changed
}
}, 250);
};

I think the easiest way here is to preload the json or load it once on$dirty and then the filter search will take care of the rest. This'll save you the extra http calls and its much faster with preloaded data. Memory will hurt, but its worth it.

Related

RESOLVED : axios repeat in a loop a request but I don't want this | axios [duplicate]

This question already has answers here:
React onClick function fires on render
(13 answers)
Closed 1 year ago.
I code a page in react, it interacts with a small API code with express by me and I created a button when I click on it it deletes a message but when I don't click the function is still executed... my code:
delete(id){
for (let i = 0; i<1; i++) {
axios.delete(`http://localhost:8080/messages/${id}`)
.then(res => {
console.log(res);
console.log(res.data);
})
}
}
render() {
return (
<div>
<ul>
{<button type="submit" onClick={this.delete(messageId)}>suprimmer</button></li>}
</ul>
</div>
)
}
}
Does this work:
<button type="submit" onClick={() => this.delete(messageId)}>suprimmer</button>
When you do it as you did in your version, you are actually calling the function.
onClick receives value as its function to trigger/call/run when user "click" on it. But in your code, you trigger/call/run function this.delete() during compile phase (compiler read Button onClick={this.delete(id)}>Delete Button</button> and called this.delete(id) immediately and use the result of it as onClick's value(delete function return nothing it means {return undefined}, so the final result is ` ).
So the result is:
Everytime the component loaded, it will call delete instantly. And onClick value now is undefined and it makes onClick won't work as expected.
For more details I'll give an example below:
const DeleteButton =({id})=> {
const delete = (id)=> {
return api.deleteItem(id)
}
return <Button onClick={delete(id)}>Delete Button</button>
}
So, when I use the component above:
<Container>
<Content value={item}/>
<DeleteButton id={item.id}/>
</Container>
it will automatically delete the item you've loaded, because while render DeleteButton it called delete(id) already.
So, how to fix it? - there are many solutions for it, but ultimately it have to give the type of value of onClick is a function:
#1 I bet noone use this, but I think it is more useful to describe my idea.
const DeleteButton =({id})=> {
const delete = (id)=> {
return function() {
api.deleteItem(id)
}
}
//delete(id) called and it returns an anonymous function that receive `id` and called it when onClick trigger
return <Button onClick={delete(id)}>Delete Button</button>
}
#2
const DeleteButton =({id})=> {
const delete = (id)=> {
return api.deleteItem(id)
}
return <Button onClick={(id)=>delete(item.id)}>Delete Button</button>
//or use bind : bind is return a function that wrapped parameter into
return <Button onClick={delete.bind(id)}>Delete Button</button>
}

Changing src attribute for an audio element doesn't work

Changing src attribute for an audio element doesn't work:
var Audio = React.createClass({
render : function() {
return (
<audio src={this.props.data.songUrl}/>
);
}
});
var Music = React.createClass({
render : function() {
return (
<article className="music">
<article className="musicContent">
<MusicButton data={Data} />
<List />
<Footer />
</article>
</article>
);
}
});
var MusicButton = React.createClass({
getInitialState : function() {
return {
isPlay : true,
count : 0
}
},
musicPlay : function () {
var audio = React.findDOMNode(this.refs.audio);
if(this.state.isPlay) {
audio.play();
this.setState({isPlay: false});
} else {
audio.pause();
this.setState({isPlay: true});
}
},
getBackWardMusic : function() {
this.setState({count: ++this.state.count});
var audio = React.findDOMNode(this.refs.audio);
audio.play();
},
getForwardMusic : function() {
this.setState({count: --this.state.count});
var audio = React.findDOMNode(this.refs.audio);
audio.play();
},
render : function() {
var classString = 'iconMusic icon-pause';
if(this.state.isPlay) {
classString = 'iconMusic icon-pause';
} else {
classString += ' rotate';
}
return (
<header className="musicHeader">
<Audio ref="audio" data={this.props.data[this.state.count]} />
<span onClick={this.getBackWardMusic} className="iconMusic icon-backward"></span>
<span onClick={this.musicPlay} className={classString}></span>
<span onClick={this.getForwardMusic} className="iconMusic icon-forward"></span>
</header>
);
}
});
after changing the source of audio you need to .load() first, before play() plays the new source.
you may like to use .oncanplaythroug = .play()
I don't have a specific answer for your question but I've found that media elements have their own lifecycle that I'm not sure is handled correctly in the React wrappers. The React and media element lifecycles have subtle interactions that are difficult to get right.
E.g., in Chrome, media elements don't release their resources unless you set src='' and if you do this in a React class, followed by a src='something-else' then I suspect the src='' can get optimised away.
To manage a video element, for example, I wrapped it in a React component and attached my own event listeners to the video DOM element to help manage its state and also managed cases like src='' and others by directly manipulating the DOM element in componentWillReceiveProps and componentWillUpdate based on what was changing.
Sorry I've not given a complete answer. It would take a lot of time to completely describe everything but I hope this helps a bit.

Avoid to show the keyboard when a selectOneMenu is selected on mobile devices

I need to avoid to show the keyboard when a selectOneMenu is selected on mobile devices
Someone suggests to use h:selectOneMenu in this question:
How do I prevent the keyboard from popping up on a p:selectOneMenu using Primefaces?
But I need to use the p:selectOneMenu component
You can override the SelectOneMenu focusFilter function.
What we have to do is adding one more condition, if it's not a mobile device do the focus otherwise don't do it.
Here's the overridden function, just execute it in the document.ready.
//check if it's a mobile device
mobileDevice = (/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(navigator.userAgent.toLowerCase()));
PrimeFaces.widget.SelectOneMenu.prototype.focusFilter = function(timeout) {
if(!mobileDevice) {
if(timeout) {
var $this = this;
setTimeout(function() {
$this.focusFilter();
}, timeout);
}
else {
this.filterInput.focus();
}
}
}
Then we check if it's a mobileDevice again, if so we remove the foucsInput this time
if(mobileDevice) {
for (var propertyName in PrimeFaces.widgets) {
if (PrimeFaces.widgets[propertyName] instanceof PrimeFaces.widget.SelectOneMenu) {
PrimeFaces.widgets[propertyName].focusInput.remove();
}
}
}
Note: This has been fixed in PrimeFaces 5.2.
A small working example can be found on github, And an online Demo.
Try these on the id of your selectOneMenu (using jQuery)
$(".mySelect").focus(function() {
$(this).blur();
});
OR
$('body').on("focus", '.mySelect', function(){
$(this).blur();
});
OR other example using the blur attribute (using just javascript):
If the HTML for these fields looked like this:
<input type="text" name="username" id="username">
<input type="password" name="password" id="password">
Then the JavaScript would be:
document.getElementById('username').blur();
document.getElementById('password').blur();
Blur: is an event is sent to an element when it loses focus
These worked for me before.
I hope this helps!
:)

backbonejs node file upload

None of the answers I have found anywhere have worked. I am trying to extend the example in "Developing Backbone.js Applications" to upload files. Although the form has enctype="multipart/form-data," request.files is always undefined.
The form HTML is:
<form id="addBook" action="..." enctype="multipart/form-data">
<div>
<label for="coverImage">CoverImage: </label><input id="coverImage" name="coverImage" type="file" />
<label for="title">Title: </label><input id="title" type="text" />
<label for="author">Author: </label><input id="author" type="text" />
<label for="releaseDate">Release date: </label><input id="releaseDate" type="text" />
<label for="keywords">Keywords: </label><input id="keywords" type="text" />
<button id="add">Add</button>
</div>
</form>
The backbone that saves the new record is
addBook: function( e ) {
e.preventDefault();
var formData = {};
var reader = new FileReader();
$( '#addBook div' ).children( 'input' ).each( function( i, el ) {
if( $( el ).val() != '' )
{
if( el.id === 'keywords' ) {
formData[ el.id ] = [];
_.each( $( el ).val().split( ' ' ), function( keyword ) {
formData[ el.id ].push({ 'keyword': keyword });
});
} else if( el.id === 'releaseDate' ) {
formData[ el.id ] = $( '#releaseDate' ).datepicker( 'getDate' ).getTime();
} else {
formData[ el.id ] = $( el ).val();
}
}
});
console.log(formData);
this.collection.create( formData );
}
The Node being called.
//Insert a new book
app.post( '/api/books', function( request, response ) {
console.log(request.body);
console.log(request.files);
});
The value of coverimage send to node is correct, I just never get anything in request.files. I have a cool drag and drop I would like to use instead, but until I get this working I am stuck.
I tried the JQuery-file-upload, that got me nowhere.
If I had hair, I would be pulling it out right now.
I wouldn't be submitting the file as part of the model.save/collection.create(model).
What I've used is Plupload for a file upload manager, submitting a file to an upload handler. This upload handler either returns the path to the uploaded file, or fileId if a reference is stored in a database table.
From there I populate a property in my backbone model, then persist the model. You can have your model listenTo plupload, for an upload completed event or similar.
I'm also following the sample of the book "Developing Backbone.js Applications", I extended the functionality to upload images to a folder in the server and save the path in my model to show the correct images. It is working fine. I tried to use Plupload and other jquery plugins but I didn't like them. My sample is using ajax to upload images to the server and then using them. I read many posts referencing the use of iframes to have ajax functionality. The best approach for this I found is using the jquery.form.js to avoid postbacks and load the images in a nice way.
The running sample working fine with nodeJS:
https://github.com/albertomontellano/bookLibrarySampleNodeJS
I based my solution in the post of Mark Dawson:
http://markdawson.tumblr.com/post/18359176420/asynchronous-file-uploading-using-express-and-node-js
However, I had to correct a method of this post to make it work correctly:
app.post('/api/photos', function(req, res) {
var responseServerPath = 'images/' + req.files.userPhoto.name;
var serverPath = 'site/images/' + req.files.userPhoto.name;
console.log(req.files.userPhoto.path);
require('fs').rename(
req.files.userPhoto.path,
serverPath,
function(error) {
if(error) {
console.log(error);
res.send({
error: 'Ah crap! Something bad happened'
});
return;
}
res.send({
path: responseServerPath
});
}
);
});
I hope it helps.
Turned out I had to end up hiring someone to do it, because I can't find any examples online of anybody uploading a file through backbone, even without updating any database interaction. Everybody has the same basic advice about what tools to use to upload the files, but I can't for the life of me find ONE example of someone implementing it.
I am planning on making the code available to everybody, so they can see how it works.

When to call YUI destroy?

When should destroy be called? Does it ever get called automatically by YUI lifecycle? Does the page unload cause the YUI lifecycle to call destroy on all objects created during the page processing? I have been working under the assumption that I need to make all my own calls to destroy but that gets hairy when ajax calls replace sections of code that I had progressively enhanced. For example:
<div id="replaceMe">
<table>
<tr>
<td>1</td>
</tr>
<tr>
<td>2</td>
</tr>
</table>
<script>
YUI().use('my-lib', function(Y) {
Y.mypackage.enhanceTable("replaceMe");
});
<script>
</div>
The my-lib module basically adds a click handler and mouseover for each row:
YUI.add('my-lib', function(Y) {
function EnhancedTable(config) {
EnhancedTable.superclass.constructor.apply(this, arguments);
}
EnhancedTable.NAME = "enhanced-table";
EnhancedTable.ATTRS = {
containerId : {},
onClickHandler : {},
onMouseoverHandler : {},
onMouseoutHandler : {}
};
Y.extend(EnhancedTable, Y.Base, {
_click : function(e) {
//... submit action
},
destructor : function() {
var onClickHandler = this.get("onClickHandler"),
onMouseoverHandler = this.get("onMouseoverHandler"),
onMouseoutHandler = this.get("onMouseoutHandler");
onClickHandler && onClickHandler.detach();
onMouseoverHandler && onMouseoverHandler.detach();
onMouseoutHandler && onMouseoutHandler.detach();
},
initializer : function(config) {
var container = Y.one("[id=" + this.get("containerId") + "]");
this.set("container", container);
this.set("onMouseoverHandler", container.delegate("mouseover",
this._mouseover, "tr", this ));
this.set("onMouseoutHandler", container.delegate("mouseout",
this._mouseout, "tr", this ));
this.set("onClickHandler", container.delegate("click",
this._click, "tr", this ));
},
_mouseout : function(e) {
e.currentTarget.removeClass("indicated");
},
_mouseover : function(e) {
e.currentTarget.addClass("indicated");
}
});
Y.namespace("mypackage");
Y.mypackage.enhanceTable = function(containerId) {
var enhancedTable new EnhancedTable({containerId:containerId});
};
}, '0.0.1', {
requires : [ 'base', 'node' ]
});
The click handler would submit a request back to my application that would change the page. Do I need to remember all the enhancedTable objects and have an onunload handler call the destroy method of each? Or does the YUI framework take care of this?
The last part of this quesiton is, I also have code outside of this that replaces the whole table by replacing the content of the <div id="replaceMe">. In doing so, the script would get re-run and augment the new <table> with a new EnhancedTable. Do I need to remember the old table, and destroy it before the new table clobbers it?
Instead of setting handlers as attributes I'd store them all in an array like this:
this._handlers = [
container.delegate("mouseover", this._mouseover, "tr", this ),
container.delegate("mouseout", this._mouseout, "tr", this ),
container.delegate("click", this._click, "tr", this )
];
Then add a destructor method that does the following
destructor : function() {
new Y.EventTarget(this._handlers).detach();
}
It accomplishes the same thing but with way less work on your part!
Ideally instead of running this against each table you'd attach all your delegates to #replaceMe so that it wouldn't need to be recreated each time you changed the content, no matter where that happened from.
YUI won't automatically call .destroy() for you on unload, it will clean up DOM subs though. The above is extra credit that's really only necessary if you are going to be destroying the object yourself.

Resources