debounce is not working with keyup event in Angular - angularjs

I have a text box to enter some text to search records. I am using data-ng-model-options="{ debounce: 1000 }" with keyup event, data-ng-model-options" working fine, but I want to fire keyup event after the debounce time duration.
Currently keyup event fires instantly before debounce duration. May be I doing something wrong.
Here is my HTML
<input type="text" id="focusOnMe" placeholder="Search..."/ data-ng-keyup="loadSearchResult($event)" data-ng-model="searchText" ng-model-options="{ debounce: 1000 }">
And this is my Keyup event action
$scope.loadSearchResult = function(event) {
$rootScope.hideSearchResult = true;
$rootScope.showLoading = true;
var searchText = $scope.searchText.trim();
if (searchText.length > 0) {
$http({
method: 'POST',
url: '/secure/search',
data: {
searchText: searchText,
peopleFlag: checkboxValueForPeopleSearch,
colonyFlag: checkboxValueForColonySearch
}
}).success(function(data) {
console.log(data);
if (data !== undefined || data !== null) {
$timeout(function() {
$rootScope.hideSearchResult = false;
$rootScope.showLoading = false;
$scope.allSearchResult = {
"bookmarks": data.bookmarks,
"people": data.people,
"colonies": data.colonies
};
}, 300);
} else {
$rootScope.showLoading = false;
commonNotification($rootScope, false, true, true, 'something went wrong!');
$timeout(function() {
$rootScope.newStatus = false;
}, 2000);
}
}).error(function(err) {
});
} else {
$rootScope.hideSearchResult = true;
$rootScope.showLoading = false;
}
};
Sorry the code is dependent to more files, so that I don't have plunker example
Any suggestion will be helpful for me.
Thank You

Debounce doesn't affect the keyup event. It only delays the assigning of the model ($scope) variable. So your keyup event fires immediately and loadSearchResult runs before you want it to.
To solve it, you can add a watch tied to $scope.searchText.
$scope.$watch('searchText', function (newValue) {
loadSearchResult(newValue);
});
Super-simple and cleanly-coded because we're using the Single Source of Truth ($scope).
Note: I omitted the event object because you're not using it.

Once you specify debounce in ng-model-options, it will change the way model gets updated. But it won't change how key event works. For your case, use _.debounce would help:
var delay = 500;
$scope.loadSearchResult = _.debounce($scope.loadSearchResult, delay);

Related

Angular JS upload file occurs twice in IE

I have a directive that handles uploading file and shows it in a list. For this I have a custom button for opening up the explorer. And after user selects a file from the explorer system shows the file name twice in the list. After debugging I realized it's calling the "onClick" method twice, once when the user clicks it (duh) and some mysterious event invokes it again. I think it's the scope.$apply part but can't be sure. Here's my code snippet:
<div data-ng-click="addFile($event)">
<span class="icon-small icon-add"></span>
</div>
Angular JS:
scope.addFile = function (event) {
if (event.originalEvent == null || !(event.originalEvent instanceof MouseEvent)) {
return;
}
if (!hiddenInputElementNode) {
//inject the hidden HtmlInputFile element and bind to the click event
hiddenInputElementNode = angular.element(
"<input accept='application/pdf,audio/*' type='file' class='hidden' multiple />");
hiddenInputElementNode.insertAfter(event.target);
}
//bind to the inputElementNode change event
hiddenInputElementNode.bind('change', function () {
angular.forEach(hiddenInputElementNode[0].files, function (dataFile) {
scope.$apply(
scope.selectedFiles.push({
name: dataFile.name,
data: dataFile
}));
});
this.value = null;
hiddenInputElementNode.unbind('change');
});
$timeout(function () {
if (!!hiddenInputElementNode) {
hiddenInputElementNode.click();
}
}, 0, false);
};
Even weirder this.value = null doesn't nullify the value!
Try by changing your javascript code for this one:
scope.addFile = function (event) {
if (event.target.tagName.toUpperCase() === "DIV") {
if (!hiddenInputElementNode) {
//inject the hidden HtmlInputFile element and bind to the click event
hiddenInputElementNode = angular.element(
"<input accept='application/pdf,audio/*' type='file' class='hidden' multiple />");
hiddenInputElementNode.insertAfter(event.target);
}
//bind to the inputElementNode change event
hiddenInputElementNode.bind('change', function () {
angular.forEach(hiddenInputElementNode[0].files, function (dataFile) {
scope.$apply(
scope.selectedFiles.push({
name: dataFile.name,
data: dataFile
}));
});
this.value = null;
hiddenInputElementNode.unbind('change');
});
$timeout(function () {
if (!!hiddenInputElementNode) {
hiddenInputElementNode.click();
}
}, 0, false);
}
};
I believe that you ng-click event is been fired twice because of the span inside the div (i had a similar problem with IE too).

Karma Unit: Testing keypress with escape button

I have such code inside directive :
$document.bind('keydown', function ($event) {
if ($event && $scope.visible && $event.which === escapeKey) {
$scope.toggle();
$scope.$apply();
}
});
I want to test if user click escape toggle will run. At moment I have such test:
it('should toggle window visibility to false when keypress escape', function () {
var doc,
$event;
$httpBackend.when(method, url)
.respond(template);
$event = {
event: 'keydown'
};
directive = createDirective();
$httpBackend.flush();
$isolateScope = directive.isolateScope();
$isolateScope.toggle();
$document.triggerHandler('keydown');
});
But how can I pass that certain key was pressed thought triggerHandler. Don't want to use any jQuery . Is there another way of testing this?
element.triggerHandler({type: 'keydown', which: escapeKey});

not updated template view -> it needs mous scroll, or clear input

I'm using angular.js with stomp-websockets,sock.js under by this tutorial http://g00glen00b.be/spring-websockets-angular/. I'm getting messages from websocket, but template view isn't refreshed after message from websocket is arrived. When I want to update template view I need to clear input box, or scroll with mouse.
$scope.initiator = false;
$scope.socket = {
client: null,
stomp: null
};
$scope.initSockets = function() {
$scope.socket.client = new SockJS('/myUrl');
$scope.socket.stomp = Stomp.over($scope.socket.client);
$scope.notifyMessage = function(message) {
$scope.initiator = true;
$scope.messages.push(message)
};
$scope.socket.stomp.connect({}, function() {
$scope.socket.stomp.subscribe("/topic/messages", $scope.notifyMessage);
});
$scope.socket.client.onclose = $scope.reconnect;
};
$scope.initSockets();
template view:
<ul><li ng-repeat="item in messages track by $index">{{item}}</li></ul>
You probably need to use scope.$apply to have it take effect. afaics sock.js is not made for angular, so you need to let angular know something has changed on the scope. You do this using scope.$apply.
$scope.notifyMessage = function(message) {
$scope.$apply(function(){
$scope.initiator = true;
$scope.messages.push(message)
});
};

How can I cut down on the number of events that are fired when a key is pressed in ckEditor?

I am using ckEditor and have a directive containing:
ck.on('key', function () {
$scope.$apply(function () {
ngModel.$setViewValue(ck.getData());
console.log("Updating the model - key - " + attr['name']);
});
});
This picks up every change to mode and key click inside the editor and updates the model.
However I notice that these changes are slowing down data entry. Is there a way that I could still catch every keypress but not have so many updates to the model. I was thinking of some kind of a timeout but I am not sure how I could implement that.
Here is an alternative method that doesn't use intervals. Each time a key is pressed, start a 1s timeout and after that perform the update. If a key is pressed more often than 1s, making it too fast, just reset the timer. You can use the same timer and same update function for other events too, making this nice an scalable.
function update() {
$scope.$apply(function () {
ngModel.$setViewValue(ck.getData());
console.log("Updating the model - key - " + attr['name']);
});
}
var timeout = 1000, timer;
ck.on('key', function () {
clearTimeout(timer);
timer = setTimeout(function() {
update();
}, timeout);
});
Alternatively, if you want to update every n seconds when a key is pressed, you could do something like this.
var timeout = 1000, timer, updating = false;
function update() {
$scope.$apply(function () {
ngModel.$setViewValue(ck.getData());
console.log("Updating the model - key - " + attr['name']);
updating = false;
});
}
ck.on('key', function () {
if (updating === true) return;
updating = true;
timer = setTimeout(function() {
update();
}, timeout);
});
However, if you need to check if content has changed and not if a key is pressed, you could use ck.checkDirty() and ck.resetDirty() to do the checking combined with an interval. There is also a change event, but I haven't tested it. There are many different content changes that do not trigger the key event, such as bolding some text, adding image or HR via icon, changing table properties, and dragging an image for example.
One way of doing this would be to wrap a set interval in a set timeout on keydown, which would allow you to keep updating and applying, but only if they've typed recently.
Something like this
var x = setInterval(function() {
$scope.$apply(function () {
ngModel.$setViewValue(ck.getData());
});
}, 200);
ck.on('key', function () {
if (!x) {
x = setInterval(function() {
$scope.$apply(function () {
ngModel.$setViewValue(ck.getData());
});
}, 200);
};
setTimeout(function() {
window.clearInterval(x), 2000
});
});
However, I haven't tested this code, but if you want to put up a fiddle with the situation you're describing, I can do some testing.
Edit: Just realised previous method would create intervals for each callback scope.

How to stop $broadcast events in AngularJS?

Is there a built in way to stop $broadcast events from going down the scope chain?
The event object passed by a $broadcast event does not have a stopPropagation method (as the docs on $rootScope mention.) However this merged pull request suggest that $broadcast events can have stopPropagation called on them.
Snippets from angularJS 1.1.2 source code:
$emit: function(name, args) {
// ....
event = {
name: name,
targetScope: scope,
stopPropagation: function() {
stopPropagation = true;
},
preventDefault: function() {
event.defaultPrevented = true;
},
defaultPrevented: false
},
// ....
}
$broadcast: function(name, args) {
// ...
event = {
name: name,
targetScope: target,
preventDefault: function() {
event.defaultPrevented = true;
},
defaultPrevented: false
},
// ...
}
As you can see event object in $broadcast not have "stopPropagation".
Instead of stopPropagation you can use preventDefault in order to mark event as "not need to handle this event". This not stop event propagation but this will tell the children scopes: "not need to handle this event"
Example: http://jsfiddle.net/C8EqT/1/
Since broadcast does not have the stopPropagation method,you need to use the defaultPrevented property and this will make sense in recursive directives.
Have a look at this plunker here:Plunkr
$scope.$on('test', function(event) {
if (!event.defaultPrevented) {
event.defaultPrevented = true;
console.log('Handle event here for the root node only.');
}
});
I implemented an event thief for this purpose:
.factory("stealEvent", [function () {
/**
* If event is already "default prevented", noop.
* If event isn't "default prevented", executes callback.
* If callback returns a truthy value or undefined,
* stops event propagation if possible, and flags event as "default prevented".
*/
return function (callback) {
return function (event) {
if (!event.defaultPrevented) {
var stopEvent = callback.apply(null, arguments);
if (typeof stopEvent === "undefined" || stopEvent) {
event.stopPropagation && event.stopPropagation();
event.preventDefault();
}
}
};
};
}]);
To use:
$scope.$on("AnyEvent", stealEvent(function (event, anyOtherParameter) {
if ($scope.keepEvent) {
// do some stuff with anyOtherParameter
return true; // steal event
} else {
return false; // let event available for other listeners
}
}));
$scope.$on("AnyOtherEvent", stealEvent(function (event, anyOtherParameter) {
// do some stuff with anyOtherParameter, event stolen by default
}));

Resources