How to pass $event from custom directive to function? - angularjs

This is some directive that i use for detecting enter key
.directive('enterSubmit', function() {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
elem.bind('keydown', function(event) {
var code = event.keyCode || event.which;
if (code === 13) {
if (!event.shiftKey) {
event.preventDefault();
scope.$apply(attrs.enterSubmit);
}
}
});
}
}
})
I have a function that i wanna get $event as a parameter to it.
$scope.test = function(evt){
var el = angular.element(evt.target);
console.log(el[0]);
}
But when i use $event as i could in built-in directives
<textarea enter-submit="test($event)"></textarea>
It says evt is undefined so how to do it?

Change your if to this:
if (!event.shiftKey) {
event.preventDefault();
var enterSubmitFunction = $parse(attrs['enterSubmit']);
enterSubmitFunction(scope, { $event: event });
scope.$apply();
}
You'll need to inject $parse now, and i'm not sure if you need to use scope.$parent in the enterSubmitFunction or just scope. Try both.

Related

Why I cannot require ngSubmit in angularjs

I have a angularjs directive which I require ngSubmit in it:
.directive('testDirective', function(){
return {
scope: {},
require: '?^ngSubmit',
....
In my html I have the ng-submit as the parent of directive:
<form name="testForm" ng-submit="printHello()">
<test-directive></test-directive>
<button type="submit">submit button</button>
</form>
I am wondering why I cannot access it and in the link function, the ngSubmitCtrl is always undefined:
link: function(scope, element, attr, ngSubmitCtrl){
// ngSubmitCtrl is always undefind
}
This is plunker with complete code:
https://next.plnkr.co/edit/8duvT6xHcixvbrDz?open=lib%2Fscript.js&deferRun=1
This is because ngSubmit does not instantiate a controller. It is created in bulk with many other ngEventDirectives and only defines a compile property. Take a look at the source code:
https://github.com/angular/angular.js/blob/master/src/ng/directive/ngEventDirs.js
forEach(
'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
function(eventName) {
var directiveName = directiveNormalize('ng-' + eventName);
ngEventDirectives[directiveName] = ['$parse', '$rootScope', '$exceptionHandler', function($parse, $rootScope, $exceptionHandler) {
return createEventDirective($parse, $rootScope, $exceptionHandler, directiveName, eventName, forceAsyncEvents[eventName]);
}];
}
);
function createEventDirective($parse, $rootScope, $exceptionHandler, directiveName, eventName, forceAsync) {
return {
restrict: 'A',
compile: function($element, attr) {
// NOTE:
// We expose the powerful `$event` object on the scope that provides access to the Window,
// etc. This is OK, because expressions are not sandboxed any more (and the expression
// sandbox was never meant to be a security feature anyway).
var fn = $parse(attr[directiveName]);
return function ngEventHandler(scope, element) {
element.on(eventName, function(event) {
var callback = function() {
fn(scope, {$event: event});
};
if (!$rootScope.$$phase) {
scope.$apply(callback);
} else if (forceAsync) {
scope.$evalAsync(callback);
} else {
try {
callback();
} catch (error) {
$exceptionHandler(error);
}
}
});
};
}
};
}

element.on("click", ...) don't trigger $watch

Why does not the click event fire $watch in the code below? If I include $apply, $watch is triggered.
angulartest.factory("Fac", function () {
var v = "foo";
return {
getV: function () {
return v;
},
setV: function (v_) {
v = v_;
}
};
});
angulartest.directive("wdirective", function (Fac) {
return {
restrict: "E",
template: "<p>Hello</p>",
link: function (scope, element, attrs) {
element.on("click", function (event) {
Fac.setV("bar");
//scope.$apply(); <-- Need this to fire $watch
});
}
};
});
angulartest.controller("wController", function($scope, Fac) {
$scope.$watch(Fac.getV, function(newValue, oldValue) {
console.log("v = " + newValue);
});
});
Because Angulars $digest cycle is not triggered by pure JS click events.

Angular.js binding model to directive

I'm trying to put together an Angular directive that will be a replacement for adding
ng-disabled="!canSave(schoolSetup)"
On a form button element where canSave is a function being defined in a controller something like the following where the parameter is the name of the form.
$scope.canSave = function(form) {
return form.$dirty && form.$valid;
};
Ideally I'd love the directive on the submit button to look like this.
can-save="schoolSetup"
Where the string is the name of the form.
So... how would you do this? This is as far as I could get...
angular.module('MyApp')
.directive('canSave', function () {
return function (scope, element, attrs) {
var form = scope.$eval(attrs.canSave);
function canSave()
{
return form.$dirty && form.$valid;;
}
attrs.$set('disabled', !canSave());
}
});
But this obviously doesn't bind properly to the form model and only works on initialisation. Is there anyway to bind the ng-disabled directive from within this directive or is that the wrong approach too?
angular.module('MyApp')
.directive('canSave', function () {
return function (scope, element, attrs) {
var form = scope.$eval(attrs.canSave);
scope.$watch(function() {
return form.$dirty && form.$valid;
}, function(value) {
value = !!value;
attrs.$set('disabled', !value);
});
}
});
Plunker: http://plnkr.co/edit/0SyK8M
You can pass the function call to the directive like this
function Ctrl($scope) {
$scope.canSave = function () {
return form.$dirty && form.$valid;
};
}
app.directive('canSave', function () {
return {
scope: {
canSave: '&'
},
link: function (scope, element, attrs) {
attrs.$set('disabled', !scope.canSave());
}
}
});
This is the template
<div ng-app="myApp" ng-controller="Ctrl">
<div can-save="canSave()">test</div>
</div>
You can see the function is called from the directive. Demo

Non HTML5 Placeholder Directive that works with ng-model?

I have come across a problem my Angular knowledge is a little too limited to figure out. I want a non html5 placeholder attribute. Here is some code I found before on stack overflow that does this handsomely:
// Placeholder for non HTML5 browsers
app.directive("ngPlaceholder", function($log, $timeout) {
var txt;
return {
restrict: "A",
scope: { txt: "#ngPlaceholder" },
link: function(scope, elem, attrs) {
elem.on("focus", function() {
if(elem.val() === scope.txt) {
elem.val("");
}
scope.$apply()
})
elem.on("blur", function() {
if(elem.val() === "") {
elem.val(scope.txt);
}
scope.$apply()
})
// Initialise placeholder
$timeout(function() {
elem.val(scope.txt)
scope.$apply();
})
}
}
})
However... use it in conjunction with ng-model:
input(
type="text"
ng-model="card.number"
ng-placeholder="0000-0000-0000-0000")
And it obliterates the two way data binding!
Heres a plunker:
http://plnkr.co/edit/1AvVOxb5O6P5pU3wIuKv?p=preview
What am I missing?
Update Many people have voiced there solutions to this rather annoying problem here
Use $parent to refer the to model in the parent scope since the directive ngPlaceholder creates an isolated scope. (This is not specific to IE 9 though. )
<input type="text" ng-placeholder="0000-0000-0000-0000" ng-model="$parent.card.number2"/>
This one fixes the problem that in delay of $timeout, the $scope may be changed in meantime. It also makes it cross-browser.
// Placeholder for all browsers
app.directive("ngPlaceholder", function($log, $timeout) {
return {
restrict: "A",
link: function(scope, elem, attrs) {
var txt = attrs.ngPlaceholder,
model = attrs.ngModel,
placeholderSupport = 'placeholder' in document.createElement("input");
//Use HTML5 placeholder attribute.
if (placeholderSupport) {
attrs.$set("placeholder", txt);
return;
}
elem.on("focus", function(event) {
if (elem.val() === txt) {
elem.val("");
}
});
elem.on("blur", function(event) {
if (elem.val() === "") {
elem.val(txt);
}
});
scope.$watch(model, function (newValue, oldValue, scope) {
if (newValue === undefined || newValue === "") {
elem.val(txt);
//scope.$apply(); not needed, since scope fired this event.
}
}, true);
}
}
});
You seen the ngModel issue, so the isolate scope on ngPlaceholder should be removed. I realize that sza's workaround works, but the key thing I'd emphasize is that the ngPlaceholder doesn't need its own scope.
For example here I tweaked the directive and removed the references to scope by storing creating the txt variable as its own local variable.
http://plnkr.co/edit/43z1TZHFwmgLJ9wyystD?p=preview
// Placeholder for non HTML5 browsers
app.directive("ngPlaceholder", function($log, $timeout) {
var txt;
return {
restrict: "A",
link: function(scope, elem, attrs) {
var txt = attrs.ngPlaceholder;
elem.bind("focus", function() {
if(elem.val() === txt) {
elem.val("");
}
scope.$apply()
})
elem.bind("blur", function() {
if(elem.val() === "") {
elem.val(txt);
}
scope.$apply()
})
// Initialise placeholder
$timeout(function() {
elem.val(txt)
scope.$apply();
})
}
}
})
placeholder support in IE with normal color
app.directive('placeholder',['$timeout','$window', function($timeout,$window){
var i = document.createElement('input');
if ('placeholder' in i) {
return {}
}
return {
link: function(scope, elm, attrs){
var userAgent = $window.navigator.userAgent;
if(userAgent.indexOf("MSIE 9.0") <0){
return;
}
if (attrs.type === 'password') {
return;
}
$timeout(function(){
elm.val(attrs.placeholder).css({"color":'#ccc'});
elm.bind('focus', function(){
if (elm.val() == attrs.placeholder) {
elm.val('').css({"color":'#555'});
}
}).bind('blur', function(){
if (elm.val() == '') {
elm.val(attrs.placeholder).css({"color":'#ccc'});
}
});
});
}
}
}]);
live code here:http://plnkr.co/edit/ev6kQ3Ks31FhqAfCMDkc

Two way data binding with directive and isolate scope

I'm trying to understand directives, and I'm having problems with two way data binding.
My directive will be used to submit a form when "enter" is pressed in a textarea.
I found a solution in another SO thread (see the code below in the scope definition of the directive), but I don't like it because it means that if I change the model name, I need to change the directive as well..
--> Here is the problem in codepen.io
Here is the html part:
<div ng-app="testApp" ng-controller="MyController">
<textarea ng-model="foo" enter-submit="submit()"></textarea><br/>
Binding: {{foo}}
</div>
Here is the javascript part:
var app = angular.module('testApp', []);
function MyController($scope) {
$scope.foo = "bar"
$scope.submit = function() {
console.log("Submitting form");
}
}
app.directive('enterSubmit', function () {
return {
restrict: 'A',
scope: {
submitFn: '&enterSubmit',
foo: '=ngModel' // <------------------- dont't like this solution
},
link: function (scope, elem, attrs) {
elem.bind('keydown', function(event) {
var code = event.keyCode || event.which;
if (code === 13) {
if (!event.shiftKey) {
event.preventDefault();
scope.submitFn();
}
}
});
}
}
});
Thanks for your help !
When multiple directives are used on an element, normally you don't want any of them to use an isolate scope, since that forces all of them to use the isolate scope. In particular, isolate scopes should not be used with ng-model – see Can I use ng-model with isolated scope?.
I suggest creating no new scope (the default, i.e., scope: false):
app.directive('enterSubmit', function () {
return {
restrict: 'A',
//scope: {
// submitFn: '&enterSubmit',
// foo: '=ngModel' // <------------------- dont't like this solution
//},
link: function (scope, elem, attrs) {
elem.bind('keydown', function(event) {
var code = event.keyCode || event.which;
if (code === 13) {
if (!event.shiftKey) {
event.preventDefault();
scope.$apply(attrs.enterSubmit);
}
}
});
}
}
});

Resources