Is there a good angular way to detect window focus? I am using html5 notifications and I would like to only fire if the window is out of focus.
Thanks!
There's a built-in angular directive ngFocus here maybe it helps if you attach it to the body
<window, input, select, textarea, a
ng-focus="">
...
</window, input, select, textarea, a>
Edit: For window focus, there's the $window wrapper and you can do something like:
$window.onfocus = function(){
console.log("focused");
}
Edit #CristiBerceanu is right - you should use the built-in ng-focus directive. However, take this answer as a guideline for any missing event you want to bind.
You must create a directive:
angular
.module('MyModule', [])
.directive('onFocus', function(){
return {
restrict: 'A',
scope: {
'focus': '&onFocus'
},
link: function($scope, $element, $attributes) {
var focus = function(event) {
$scope.focus({'$event': event});
};
$element.on("focus", focus);
$scope.$on('$destroy', function(){
$element.off('focus', onClick);
});
}
}
});
Notice how the event is bound in the directive by jquery and NOT directly in the controller. Additionally, notice that a bound expression is tied using the & prefix (evaluable expression binding) instead of regular prefixes like # (text-binding) or = (scope property reference, bi-directional, binding).
In Cristi Berceanu's answer, he suggests assigning a function to $window.onfocus, which does work. However, there is a problem with that... only one function can be assigned to $window.focus at a time. Thus, by assigning a function to $window.onfocus, you could accidentally overwrite a previous function, and your function will be vulnerable to being overwritten later, too.
Here's a different solution that allows multiple functions to run with the window's focus or blur events:
var onFocus = function () {
// do something
};
var onBlur = function () {
// do something else
};
var win = angular.element($window);
win.on("focus", onFocus);
win.on("blur", onBlur);
This will allow you to assign multiple functions to the focus and blur events for the $window object.
If you added the functions inside a controller and want to remove those functions when the controller is destroyed, you can do something like this:
$scope.$on("$destroy", function handler() {
win.off("focus", onFocus);
win.off("blur", onBlur);
$interval.cancel(interval);
});
Solution inspired by this post: https://www.bennadel.com/blog/2934-handling-window-blur-and-focus-events-in-angularjs.htm
you can write a directive to attach to the body element and inside it you can use $window.onfocus event to notify your angular app using events or a service, the same thing you can do from inside a service, it all depends on your architecture
Related
In AngularJS you can make a button to call an action like this:
<div ng-controller="myController">
<button ng-click="onButtonClicked()">Click me</button>
</div>
So, I'm inserting a custom directive like this:
and in my-canvas.js directive file's link function I replace the tag with a KineticJS canvas. Then, User manipulate the canvas by dragging around Kinetic shapes and, finally, when User does with the shapes what he's required to do, I want the directive to call an action defined on myController. I'm thinking about something like this:
<div ng-controller="myController">
<my-canvas ng-success="onScenarioSuccess" />
</div>
but I can't figure out how the correct way to do it.
How can I make a directive to call it's action/event programmatically?
When you want your directive to expose an API for binding to behaviors you should use an isolate scope and use the & local scope property. This allows you to pass in a function that the directive can invoke. Here is a simple example:
.directive('testDirective', function () {
return {
restrict: 'E',
scope: {
action: '&'
},
template: '<button ng-click="action()">Test</button>'
};
});
And use it like:
<test-directive action="myControllerFunction()"></test-directive>
As per the documentation:
The & binding allows a directive to trigger evaluation of an
expression in the context of the original scope, at a specific time.
Any legal expression is allowed, including an expression which
contains a function call. Because of this, & bindings are ideal for
binding callback functions to directive behaviors.
There'are some more detail in the documentation.
If you want to expose a custom event like ng-success and want to call a function on the event.
You can either do what #Beyers has mentioned using isolated scope.
Or else look at the source code of ng-click, it just wraps the javascript event inside $scope.apply, using the $parse service to evaluate the expression passed to it. Something like this can be added in your link function
var fn = $parse(attr['ngSuccess']);
element.on('someEvent', function (event) {
var callback = function () {
fn(scope, {
$event: event
});
};
scope.$apply(callback);
});
The advantage of this mechanism is that isolated scope is not created.
I'm attempting to fire an animation using a custom directive, "activate" which I use as an attribute here, partials/test.html
<div activate="{{cardTapped}}" >
I define the directive following my app definition in js/app.js
myApp.directive('activate', function ($animate) {
return function(scope, element, attrs) {
scope.$watch(attrs.activate,function(newValue){
console.log('fire');
if(newValue){
$animate.addClass(element, "full");
}
else{
$animate.removeClass(element, "full");
}
},true);
};
});
However, $watch is only firing on page load. When cardTapped changes values, nothing registers. I've tried several variations of parameters here to no avail and I've seen a dozen questions similar to this but so far I havent found a solution
Any thoughts?
The problem is that you wrote it like this: activate="{{cardTapped}}" while it should be activate="cardTapped".
When you want to use a watcher, let it watch a variable, not a string.
JS Fiddle
I created a directive that should add a ng-change directive dynamically to all child input tags:
myApp.directive('autosave', function ($compile) {
return {
compile: function compile(tElement, tAttrs) {
return function postLink(scope, iElement, iAttrs) {
var shouldRun = scope.$eval(iAttrs.autosave);
if (shouldRun) {
iElement.find(':input[ng-model]').each(function () {
$(this).attr("ng-change", iAttrs.ngSubmit);
});
$compile(iElement.contents())(scope);
console.log("Done");
}
}; //end linking fn
}
};
});
The problem that I have is that the ng-change directive isn't running. I can see it that its added to the DOM element BUT not executing when value changes.
The strange thing is that if I try with ng-click, it does work.
Dont know if this is a bug on ng-change or if I did somehting wrong.
Fiddle is with ng-click (click on the input) http://jsfiddle.net/dimirc/fq52V/
Fiddle is with ng-change (should fire on change) http://jsfiddle.net/dimirc/6E3Sk/
BTW, I can make this work if I move all to compile function, but I need to be able to evaluate the attribute of the directive and I dont have access to directive from compile fn.
Thanks
You make your life harder than it is. you do'nt need to do all the angular compile/eval/etc stuff - at the end angular is javascript : see your modified (and now working) example here :
if (shouldRun) {
iElement.find(':input[ng-model]').on( 'change', function () {
this.form.submit();
});
console.log("Done");
}
http://jsfiddle.net/lgersman/WuW8B/1/
a few notes to your approach :
ng-change maps directly to the javascript change event. so your submit handler will never be called if somebody uses cut/copy/paste on the INPUT elements. the better solution would be to use the "input" event (which catches all modification cases).
native events like change/input etc will be bubbled up to the parent dom elements in the browser. so it would have exactly the same to attach the change listener to the form instead of each input.
if you want to autosave EVERY edit that you will have an unbelievable mass of calls to your submit handler. a better approach would be to slow-down/throttle the submit event delegation (see http://orangevolt.blogspot.de/2013/08/debounced-throttled-model-updates-for.html ).
if you want to autosave EVERY edit you skip your change handler stuff completely and suimply watch the scope for changes (which will happen during angular model updates caused by edits) and everything will be fine :
scope.watch( function() {
eElement[0].submit();
});
I have the following AngularJS directive that creates an input element. Input has ng-change attribute that runs doIt() function. In my directive's unit test I want to check if doIt function is called when users changes the input. But the test does not pass. Though it works in the browser when testing manually.
Directive:
...
template: "<input ng-model='myModel' ng-change='doIt()' type='text'>"
Test:
el.find('input').trigger('change') // Dos not trigger ng-change
Live demo (ng-change): http://plnkr.co/edit/0yaUP6IQk7EIneRmphbW?p=preview
Now, the test passes if I manually bind change event instead of using ng-change attribute.
template: "<input ng-model='myModel' type='text'>",
link: function(scope, element, attrs) {
element.bind('change', function(event) {
scope.doIt();
});
}
Live demo (manual binding): http://plnkr.co/edit/dizuRaTFn4Ay1t41jL1K?p=preview
Is there a way to use ng-change and make it testable? Thank you.
From your explanatory comment:
All I want to do in directive's test is to check that doIt is called when user changes the input.
Whether or not the expression indicated by ng-change is correctly evaluated or not is really the responsibility of the ngModel directive, so I'm not sure I'd test it in this way; instead, I'd trust that the ngModel and ngChange directives have been correctly implemented and tested to call the function specified, and just test that calling the function itself affects the directive in the correct manner. An end-to-end or integration test could be used to handle the full-use scenario.
That said, you can get hold of the ngModelController instance that drives the ngModel change callback and set the view value yourself:
it('trigger doIt', function() {
var ngModelController = el.find('input').controller('ngModel');
ngModelController.$setViewValue('test');
expect($scope.youDidIt).toBe(true);
});
As I said, though, I feel like this is reaching too far into ngModel's responsibilities, breaking the black-boxing you get with naturally composable directives.
Example: http://plnkr.co/edit/BaWpxLuMh3HvivPUbrsd?p=preview
[Update]
After looking around at the AngularJS source, I found that the following also works:
it('trigger doIt', function() {
el.find('input').trigger('input');
expect($scope.youDidIt).toBe(true);
});
It looks like the event is different in some browsers; input seems to work for Chrome.
Example: http://plnkr.co/edit/rbZ5OnBtKMzdpmPkmn2B?p=preview
Here is the relevant AngularJS code, which uses the $sniffer service to figure out which event to trigger:
changeInputValueTo = function(value) {
inputElm.val(value);
browserTrigger(inputElm, $sniffer.hasEvent('input') ? 'input' : 'change');
};
Even having this, I'm not sure I'd test a directive in this way.
I googled "angular directive trigger ng-change" and this StackOverflow question was the closest I got to anything useful, so I'll answer "How to trigger ng-change in a directive", since others are bound to land on this page, and I don't know how else to provide this information.
Inside the link function on the directive, this will trigger the ng-change function on your element:
element.controller('ngModel').$viewChangeListeners[0]();
element.trigger("change") and element.trigger("input") did not work for me, neither did anything else I could find online.
As an example, triggering the ng-change on blur:
wpModule.directive('triggerChangeOnBlur', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
element.on('blur', function () {
element.controller('ngModel').$viewChangeListeners[0]();
});
}
};
}]);
I'm sorry that this is not directly answering OP's question. I will be more than happy to take some sound advice on where and how to share this information.
simple and it works
in your unit test env:
spyOn(self, 'updateTransactionPrice');
var el = compile('<form name="form" latest novalidate json-schema="main.schema_discount" json-schema-model="main._data"><input type="text" ng-model="main._data.reverse_discount" ng-class="{ \'form-invalid\': form.reverse_discount.$invalid }" ng-change="main.transactionPrice(form);" name="reverse_discount" class="form-control-basic" placeholder="" ng-disabled="!main.selectedProduct.total_price"></form>')(scope);
el.find('input').triggerHandler('change');
expect(self.updateTransactionPrice).toHaveBeenCalled();
I was looking for this simple line for long hours.
Just to save that in here.
How to select value from html-select, using Karma, and so get ng-change function working?
HTML:
Controller or directive JS:
$scope.itemTypes = [{name: 'Some name 1', value: 'value_1'}, {name: 'Some name 2', value: 'value_2'}]
$scope.itemTypeSelected = function () {
console.log("Yesssa !!!!");
};
Karma test fragment:
angular.element(element.find("#selectedItemType")[0]).val('value_1').change();
console.log("selected model.selectedItemType", element.isolateScope().model.selectedItemType);
Console:
'Yesssa !!!!'
'selected model.selectedItemType', 'value_1'
Have been trying to get this to work, but failed on every attempt. Finally concluded that my ng-model-options with a debounce setting on the onUpdate, was the problem.
If you have a debounce, make sure that you flush with the $timeout service. In angular mock, this timeout service has been extended with a flush operation, which handles all unfulfilled requests/actions.
var tobetriggered = angular.element(element[0].querySelector('.js-triggervalue'));
tobetriggered.val('value');
tobetriggered.trigger('change');
$timeout.flush();
I've created a directive to wrap a jQuery plugin, and I pass a config object for the plugin from the controller to the directive. (works)
In the config object is a callback that I want to call on an event. (works)
In the callback, I want to modify a property on the controller's $scope, which does not work. Angular does not recognize that the property has changed for some reason, which leads me to believe that the $scope in the callback is different than the controller's $scope. My problem is I just don't why.
Can anybody point me in the right direction?
Click here for Fiddle
app.js
var app = angular.module('app', [])
.directive('datepicker', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
// Uncommenting the line below causes
// the "date changed!" text to appear,
// as I expect it would.
// scope.dateChanged = true;
var dateInput = angular.element('.datepicker')
dateInput.datepicker(scope.datepickerOpts);
// The datepicker fires a changeDate event
// when a date is chosen. I want to execute the
// callback defined in a controller.
// ---
// PROBLEM:
// Angular does not recognize that $scope.dateChanged
// is changed in the callback. The view does not update.
dateInput.bind('changeDate', scope.onDateChange);
}
};
});
var myModule = angular.module('myModule', ['app'])
.controller('MyCtrl', ['$scope', function ($scope) {
$scope.dateChanged = false;
$scope.datepickerOpts = {
autoclose: true,
format: 'mm-dd-yyyy'
};
$scope.onDateChange = function () {
alert('onDateChange called!');
// ------------------
// PROBLEM AREA:
// This doesnt cause the "date changed!" text to show.
// ------------------
$scope.dateChanged = true;
setTimeout(function () {
$scope.dateChanged = false;
}, 5000);
};
}]);
html
<div ng-controller="MyCtrl">
<p ng-show="dateChanged">date changed!</p>
<input type="text" value="02-16-2012" class="datepicker" datepicker="">
</div>
There are a number of scope issues at work in your demo. First , within the dateChange callback, even though the function itself is declared inside the controller, the context of this within the callback is the bootstrap element since it is within a bootstrap handler.
Whenever you change angular scope values from within third party code , angular needs to know about it by using $apply. Generally best to keep all third party scopes inside the directive.
A more angular apprroach is to use ng-model on the input. Then use $.watch for changes to the model. This helps keep all the code inside the controller within angular context. Is rare in any angular application not to use ng-model on any form controls
<input type="text" class="datepicker" datepicker="" ng-model="myDate">
Within directive:
dateInput.bind('changeDate',function(){
scope.$apply(function(){
scope[attrs.ngModel] = element.val()
});
});
Then in Controller:
$scope.$watch('myDate',function(oldVal,newVal){
if(oldVal !=newVal){
/* since this code is in angular context will work for the hide/show now*/
$scope.dateChanged=true;
$timeout(function(){
$scope.dateChanged=false;
},5000);
}
});
Demo: http://jsfiddle.net/qxjck/10/
EDIT One more item that should change is remove var dateInput = angular.element('.datepicker') if you want to use this directive on more than one element in page. It is redundant being used in directive where element is one of the arguments in the link callback already, and is instance specific. Replace dateInput with element
The changeDate event bound to the input seems to be set up to fire outside of the Angular framework. To show the paragraph, call $scope.$apply() after setting dateChanged to true. To hide the paragraph after the delay, you can use $apply() again inside the function passed to setTimeout, but you're likely to keep out of further trouble using Angular's $timeout() instead.
Fiddle