AngularJS ng-keydown directive only working for <input> context? - angularjs

I am pretty new to AngularJS but found it quite to my liking so far. For my current project I need hotkey functionality and was happy to see that it is supported since the 1.1.2 release.
The ng-keydown directive (http://code.angularjs.org/1.1.3/docs/api/ng.directive:ngKeydown) works as expected for input types but fails me for any other context like div etc. which seems odd given that the documentation says otherwise.
Here is an minimal example (http://jsfiddle.net/TdXWW/12/) of the working respectively the not working:
<input ng-keydown="keypress($event)">
<div ng-keydown="keypress($event)">
NOTE: I know this could be handled with plain jQuery (http://www.mkyong.com/jquery/how-to-check-if-an-enter-key-is-pressed-with-jquery/) but I much prefer to understand how to deal with it in AngularJS.

I was having the same problem and was able to fix it by following this simple tip provided in this comment: https://stackoverflow.com/a/1718035/80264
You need to give the div a tabindex so it can receive focus.
<div id="testdiv" tabindex="0"></div>

Thanks! To wrap this up I got this working by, injecting $document into my directive, then:
MyApp.directive('myDirective', function($document) {
return {
...
$document.keydown(function(e){
console.log(e)
})
}

This was the way I got it working in the end.
Add ng-app to the html element and ng-keyup and ng-keydown to the body element:
<html ng-app="myApp" ng-controller="MainCtrl">
.....
<body ng-keydown="keyPress($event);" ng-keyup="keyRelease($event);">
Then the funcitons in my controller deal with the event calling event.which to get the key code (in my implementation I set a var to the rootScope but you could also broadcast to other controllers)
$scope.keyPress = function(eve) {
if (eve.which === 16) { // shift
// $rootScope.$broadcast('doShift');
$rootScope.shiftOn = true;
};
};

The comment by charlietfl cleared things up and binding the event to $(document) worked as expected! Take away message: The AngularJS documentation is not really exhaustive, i.e. demands background knowledge.

angular.module('app').directive('executeOnEnter', function () {
return {
restrict: 'A',
link: function (scope, el, attrs, $rootScope) {
$('body').on('keypress', function (evt) {
if (evt.keyCode === 13) {
el.trigger('click', function () {
});
}
})
},
controller: function ($rootScope) {
function removeEvent() {
$("body").unbind("keypress");
}
$rootScope.$on('$stateChangeStart', removeEvent);
}
}
})

it worker fine for me, just add tabindex attribute. make sure that ng-keydown contains correct angularjs expression
<div ng-keydown="keypress($event)" tabindex="0">
$scope.keypress = function(ev) {
console.log('keyprez', ev);
}

Related

Angularjs Interpolation using double curly braces not working under ng-if

UPDATE1: developed the plunker sample that will reproduce the problem. See below.
I have a strange problem in my project, where it appears in one place only. Finally, I was able to reproduce the problem using plunker sample:
http://plnkr.co/edit/JJbq54?p=preview
In the above sample, see the section "With ng-if" and "Without ng-if", enter something in the input text, and see how the double curly braces not working under ng-if, but ng-bind works fine. Also, if you remove check-if-required from the template sites-and-improvements.html also the problem is solved.
More details below:
I have the the following HTML5 code block:
<div ng-if="isFullCUSPAP" id="sites_and_imrpovements_comments">
<div class="form-row">
<div class="inputs-group">
<label>WIND TURBINE:</label>
<div class="input-binary">
<label>
<input type="radio" id="wind_turbine"
name="wind_turbine"
ng-model="$parent.wind_turbine"
value="Yes" force-model-update />
Yes
</label>
</div>
<div class="input-binary">
<label>
<input type="radio" id="wind_turbine"
name="wind_turbine"
ng-model="$parent.wind_turbine"
value="No" force-model-update />
No
</label>
</div>
<span ng-bind="wind_turbine"></span>
<span>wind_turbine = {{wind_turbine}}</span>
</div>
</div>
</div>
I know that ng-if will create a new child scope. See above code, scope variable wind_trubine. Only in this HTML5 file, the curly braces {{}} is not working. However, if I use ng-bind it works fine. In other HTML5 files, I have no problem what so ever. This HTML5 is implemented using directive as follows:
app.directive('sitesAndImprovements', function() {
return {
restrict: 'E',
replace:true,
templateUrl: '<path-to-file>/site-and-improvments.html',
link: function (scope, elem, attrs) {
//Business Logic for Sites and Improvements
}
}
})
And, simply, I put it in the parent as follows:
<sites-and-improvements></sites-and-improvements>
The only difference I could see, is that this implementation has two levels of nested ng-if, which would look like the following:
<div ng-if="some_expression">
...
...
<sites-and-improvements></sites-and-improvements>
...
...
</div>
Based on comments, I used controller As notation and defined MainController accordingly. See snapshots below. It seems there is a problem if ng-if is nested with two levels. The scope variable is completely confused. I don't get the same results using ng-bind and double curly braces.
If you examine the above snapshots, even though I used controller As notation, you will see that ng-bind gives different results when compared with interpolation using {{}}.
I even changed the default value of wind_turbine to be set as follows in the link function:
scope.MainController.wind_turbine = 'Yes';
I noticed that on page load, everything looks fine, but when I change the value of the input element wind_trubine using the mouse, all related reference are updated correctly except the one that uses {{}}.
Maybe this is because there are two nested levels of ng-if?
Appreciate your feedback.
Tarek
Remove the replace: true from the sites-and-improvements directive:
app.directive('sitesAndImprovements', function() {
return {
restrict: 'E',
̶r̶e̶p̶l̶a̶c̶e̶:̶t̶r̶u̶e̶,̶
templateUrl: 'site-and-improvments.html',
link: function (scope, elem, attrs) {
//debugger;
}
}
})
It is fighting the check-if-required directive:
app.directive('checkIfRequired', ['$compile', '$timeout', function ($compile, $timeout) {
return {
priority: 2000,
terminal: true,
link: function (scope, el, attrs) {
el.removeAttr('check-if-required');
$timeout(function(){
//debugger;
$(':input', el).each(function(key, child) {
if (child && child.id === 'test_me') {
angular.element(child).attr('ng-required', 'true');
}
if (child && child.id === 'testInput1') {
//debugger;
//angular.element(child).attr('ng-required', 'true');
}
});
$compile(el, null, 2000)(scope);
})
}
};
}])
The DEMO on PLNKR.
replace:true is Deprecated
From the Docs:
replace ([DEPRECATED!], will be removed in next major release - i.e. v2.0)
specify what the template should replace. Defaults to false.
true - the template will replace the directive's element.
false - the template will replace the contents of the directive's element.
-- AngularJS Comprehensive Directive API - replace deprecated
From GitHub:
Caitp-- It's deprecated because there are known, very silly problems with replace: true, a number of which can't really be fixed in a reasonable fashion. If you're careful and avoid these problems, then more power to you, but for the benefit of new users, it's easier to just tell them "this will give you a headache, don't do it".
-- AngularJS Issue #7636
For more information, see Explain replace=true in Angular Directives (Deprecated)
Another solution posted by AngularJS team here:
https://github.com/angular/angular.js/issues/16140#issuecomment-319332063
Basically, they recommend to convert the link() function to use compile() function instead. Here is the update code:
app.directive('checkIfRequired', ['$compile', '$timeout', function ($compile, $timeout) {
return {
priority: 2000,
terminal: true,
compile: function (el, attrs) {
el.removeAttr('check-if-required');
var children = $(':input', el);
children.each(function(key, child) {
if (child && child.id === 'test_me') {
angular.element(child).attr('ng-required', 'true');
}
});
var compiled = $compile(el, null, 2000);
return function( scope ) {
compiled( scope );
};
}
};
}]).directive('sitesAndImprovements', function() {
return {
restrict: 'E',
replace:true,
templateUrl: 'site-and-improvments.html'
}
});
The main problem I have with this solution is that I am using the scope parameter which is passed to the link() function. For example, in the .each() loop above, I need to get the value of the element ID which is based on interpolation using {{<angular expre>}}.
So I tried to use pre-link and post-link within the compile function where the scope is available. I noticed that the section with ng-if is removed when execution is in pre-link and then it is added shortly after that. So I had to use $watch to monitor changes to the children to run the needed process when required. I developed this plunker sample:
http://plnkr.co/edit/lsJvhr?p=preview
Even after all such effort, the issue is not resolved. So the bottom line for similar cases, is that if you need to use the scope then you have to remove replace: true.
Any feedback would be appreciated.
Tarek

angularJS scrolling not recognized in ng-view

Does anyone know why this basic scroll directive doesn't work when I scroll in a template within ng-view? I know this is super vague but I can't find anything on the web. I have tried multiple variations of a scroll directive but none of them recognize scrolling within the template. I appreciate any help I can get. Thanks in advance!
I'm using this basic directive
directive("scroll", function ($window) {
return function(scope, element, attrs) {
angular.element($window).bind("scroll", function() {
if (this.pageYOffset >= 100) {
scope.boolChangeClass = true;
} else {
scope.boolChangeClass = false;
}
scope.$apply();
});
};
});
Here is the HTML
<ng-view><ng-view>
and a basic template for the view
<div scroll>
<blah></blah>
</div>
I should also not that I am using the ngRoute to supply views to ng-view.
I found the issue. Everything in my code was correct. The problem is that I had a piece of css that was hiding my overflow-x. Once I removed that the scroll event started firing.
in my case
<div ng-view></div>
I used to use directive example
<input type="password" class="form-control" scroll="user.password" required>
directive
app.directive("scroll", function () {
return {
};
});

JQuery UI Spinner is not updating ng-model in angular

Angular's ng-model is not updating when using jquery-ui spinner.
Here is the jsfiddle http://jsfiddle.net/gCzg7/1/
<div ng-app>
<div ng-controller="SpinnerCtrl">
<input type="text" id="spinner" ng-model="spinner"/><br/>
Value: {{spinner}}
</div>
</div>
<script>
$('#spinner').spinner({});
</script>
If you update the text box by typing it works fine (you can see the text change). But if you use the up or down arrows the model does not change.
Late answer, but... there's a very simple and clean "Angular way" to make sure that the spinner's spin events handle the update against ngModel without resorting to $apply (and especially without resorting to $parse or an emulation thereof).
All you need to do is define a very small directive with two traits:
The directive is placed as an attribute on the input element you want to turn into a spinner; and
The directive configures the spinner such that the spin event listener calls the ngModel controller's $setViewValue method with the spin event value.
Here's the directive in all its clear, tiny glory:
function jqSpinner() {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, c) {
element.spinner({
spin: function (event, ui) {
c.$setViewValue(ui.value);
}
});
}
};
};
Note that $setViewValue is intended for exactly this situation:
This method should be called when an input directive wants to change
the view value; typically, this is done from within a DOM event
handler.
Here's a link to a working demo.
If the demo link provided above dies for some reason, here's the full example script:
(function () {
'use strict';
angular.module('ExampleApp', [])
.controller('ExampleController', ExampleController)
.directive('jqSpinner', jqSpinner);
function ExampleController() {
var c = this;
c.exampleValue = 123;
};
function jqSpinner() {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, c) {
element.spinner({
spin: function (event, ui) {
c.$setViewValue(ui.value);
}
});
}
};
};
})();
And the minimal example template:
<div ng-app="ExampleApp" ng-controller="ExampleController as c">
<input jq-spinner ng-model="c.exampleValue" />
<p>{{c.exampleValue}}</p>
</div>
Your fiddle is showing something else.
Besides this: Angular can not know about any changes that occur from outside its scope without being aknowledged.
If you change a variable of the angular-scope from OUTSIDE angular, you need to call the apply()-Method to make Angular recognize those changes. Despite that implementing a spinner can be easily achieved with angular itself, in your case you must:
1. Move the spinner inside the SpinnerCtrl
2. Add the following to the SpinnerCtrl:
$('#spinner').spinner({
change: function( event, ui ) {
$scope.apply();
}
}
If you really need or want the jQuery-Plugin, then its probably best to not even have it in the controller itself, but put it inside a directive, since all DOM-Manipulation is ment to happen within directives in angular. But this is something that the AngularJS-Tutorials will also tell you.
Charminbear is right about needing $scope.$apply(). Their were several problems with this approach however. The 'change' event only fires when the spinner's focus is removed. So you have to click the spinner then click somewhere else. The 'spin' event is fired on each click. In addition, the model needs to be updated before $scope.$apply() is called.
Here is a working jsfiddle http://jsfiddle.net/3PVdE/
$timeout(function () {
$('#spinner').spinner({
spin: function (event, ui) {
var mdlAttr = $(this).attr('ng-model').split(".");
if (mdlAttr.length > 1) {
var objAttr = mdlAttr[mdlAttr.length - 1];
var s = $scope[mdlAttr[0]];
for (var i = 0; i < mdlAttr.length - 2; i++) {
s = s[mdlAttr[i]];
}
s[objAttr] = ui.value;
} else {
$scope[mdlAttr[0]] = ui.value;
}
$scope.$apply();
}
}, 0);
});
Here's a similar question and approach https://stackoverflow.com/a/12167566/584761
as #Charminbear said angular is not aware of the change.
However the problem is not angular is not aware of a change to the model rather that it is not aware to the change of the input.
here is a directive that fixes that:
directives.directive('numeric', function() {
return function(scope, element, attrs) {
$(element).spinner({
change: function(event, ui) {
$(element).change();
}
});
};
});
by running $(element).change() you inform angular that the input has changed and then angular updates the model and rebinds.
note change runs on blur of the input this might not be what you want.
I know I'm late to the party, but I do it by updating the model with the ui.value in the spin event. Here's the updated fiddle.
function SpinnerCtrl($scope, $timeout) {
$timeout(function () {
$('#spinner').spinner({
spin: function (event, ui) {
$scope.spinner = ui.value;
$scope.$apply();
}
}, 0);
});
}
If this method is "wrong", any suggestions would be appreciated.
Here is a solution that updates the model like coder’s solution, but it uses $parse instead of parsing the ng-model parameter itself.
app.directive('spinner', function($parse) {
return function(scope, element, attrs) {
$(element).spinner({
spin: function(event, ui) {
setTimeout(function() {
scope.$apply(function() {
scope._spinnerVal = = element.val();
$parse(attrs.ngModel + "=_spinnerVal")(scope);
delete scope._spinnerVal;
});
}, 0);
}
});
};
});

Bind and parse HTML content

I am using AngularJS v1.2.1.
The improved ng-bind-html directive allows me to trust unsafe Html into my view.
Example
HTML:
<div ng-repeat="example in examples" ng-bind-html="example.content()"></div>
JS:
function controller($scope, $sce)
{
function ex()
{
this.click = function ()
{
alert("clicked");
}
this.content() = function ()
{
//if
return $sce.trustAsHtml('<button ng-click="click()">some text</button>');
// no problem, but click is not called
//when
return $sce.parseAsHtml('<button ng-click="click()">some text</button>');
//throw an error
}
}
$scope.examples = [new ex(), new ex()];
}
My question is, how to bind HTML content that may contain Angular expressions or directives ??
If you need dynamic templates per element, as your question suggests, one solution would be to use $compile within a directive to parse the HTML within the context of the local scope. A simple version of this is shown in this Plunk.
An example directive:
app.directive('customContent', function($compile) {
return function(scope, el, attrs) {
el.replaceWith($compile(scope.example.content)(scope));
}
});
The corresponding HTML:
<div ng-repeat="example in examples">
<div custom-content></div>
</div>
Notice that, in the Plunk controller, I've pulled out the click function into the scope for simplicity, since in the template HTML you are calling click() in the context of the scope, not on the example object. There are a couple ways you could use a different click function for each example, if that's what you'd like to do. This egghead.io screencast has a good example of passing an expression into a directive explicitly; in your case, it could be a click function or the whole example object, depending on what you need.

Angular directive is not working when used with two HTML elements?

I am new to AngularJS. I have created one directive using AngularJS which is working fine for me, but when i used this same directive with another HTML element then its not working.
basecampModule.directive("slideElement", function () {
function link($scope, element, attributes) {
var expression = attributes.slideElement;
if (!$scope.$eval(expression)) {
element.hide();
}
$scope.$watch(expression, function (newValue, oldValue) {
if (newValue === oldValue) {
return;
}
if (newValue) {
element.stop(true, true).slideDown("fast");
$('html, body').animate({
scrollTop: $(element).offset().top
}, 1000);
} else {
element.stop(true, true).slideUp("fast");
}
});
}
return ({
link: link,
restrict: "A"
});
});
HTML Part
<div class="row well" id="detailsBugs" slide-element="FilterBugsDetails.ShowPanel">
//FIRST ELEMENT
</div>
<div class="row well" id="detailsTasks" slide-element="FilterTaskDetails.ShowPanel">
//SECOND ELEMENT
</div>
its working with first element but not with second element.
Please let me know what is wrong is that part. ??
A very wilde guess : you should isolate the scope of you directive. Since both instances of this directive are using the same $scope they're probably conflicting :
http://www.egghead.io/video/fYgdU7u2--g
When I first looked at this I couldn't quite work out what was going on.
The watched would fire for once but not a second time for the second directive.
However, turns out the directive is actually watching two different properties (maybe this was obvious to others but I completely missed it). From the question it sounded like the poster wanted the directive to watch the same element.
Anyway it works fine in my plunker here
<button ng-click="FilterBugsDetails.ShowPanel = !FilterBugsDetails.ShowPanel;">Toggle Bugs</button>
<button ng-click="FilterTaskDetails.ShowPanel = !FilterTaskDetails.ShowPanel;">Toggle Tasks</button>
<button ng-click="FilterTaskDetails.ShowPanel = !FilterTaskDetails.ShowPanel; FilterBugsDetails.ShowPanel = !FilterBugsDetails.ShowPanel;">Toggle Tasks And Bugs</button>

Resources