Angular invoke controller fn from directive isolated template - angularjs

I want on-click event from directive invoke some function from my controller. But for some reason it doesn't work. I want my datepicker to expand when I event is fired. Could you please help me to investigate what is wrong my in my current build?
app.js
app.directive('myDatepicker', function() {
return {
restrict: 'E',
scope :{
model:'=model',
minDate:'=minDate',
isOpened:'=isOpened',
openFunction: '&'
},
templateUrl: 'templates/datepicker/datepicker.html',
link: function(scope, elm, attrs) {
}
};
});
app.controller('FlightDatePickerController', function ($scope) {
$scope.openFunction = function($event, isDepart) {
$event.preventDefault();
$event.stopPropagation();
$scope.departureOpened = true;
};
};
datepicker.html
<fieldset>
<pre>{{model}}</pre>
<div class='input-group'>
<input type="text" class="form-control" datepicker-popup ng-model="{{model}}" min-date="{{minDate}}" is-open="{{isOpened}}" datepicker-options="dateOptions" ng-required="true" close-text="Close" />
<span ng-click='openFunction({event:event}, {isDepart:isDepart})' class="btn btn-default input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
</fieldset>
index.html
<div ng-controller="FlightDatePickerController">
<div class="col-md-2">
<my-datepicker model="departureDate" minDate="minDateDeparture" isOpened="departureOpened" open-function="openFunction($event, isDepart)"></my-datepicker>
</div>
</div>

You can add a controller attribute to your directive, in order to bind some function to your template.
In your case, you can do :
Directive
app.directive('myDatepicker', function() {
return {
restrict: 'E',
scope :{
model:'=model',
minDate:'=minDate',
isOpened:'=isOpened'
},
templateUrl: 'templates/datepicker/datepicker.html',
controller: 'FlightDatePickerController'
};
});
Datepicker.html
<div ng-controller="FlightDatePickerController">
<div class="col-md-2">
<my-datepicker model="departureDate" minDate="minDateDeparture" isOpened="departureOpened"></my-datepicker>
</div>
</div>

Your overall implementation is correct, but you made couple of mistakes.
ng-click should be like adding parameter in JSON like structure.
ng-click='openFunction({event:$event, isDepart:isDepart})'
& then your directive element should have
open-function="openFunction($event, isDepart)"

Related

Creating a wrapper directive for Bootstrap-UI's uib-datepicker-popup

I'm trying to create a wrapper AngularJS directive for Bootstrap-UI's uib-datepicker-popup so I don't have to recreate a bunch of boilerplate each time I need to select a date. I've been working off an example I found here which was written for an earlier version of Angular, and am running into some oddities getting this working.
I've gotten the directive to a point where it displays a popup, however the two-way data binding seems to be broken; the date value in the field's model doesn't propagate into the directive, and when you click on the popup and select a date, it doesn't propagate back out. Does anyone have any ideas on what's going on here?
I've created a Plunker demonstrating the issue here.
Directive code:
app.directive('myDatepicker', function() {
return {
restrict: 'E',
scope: {
model: "=",
myid: "#"
},
templateUrl: 'datepicker.html',
require: 'ngModel',
link: function(scope, element) {
scope.popupOpen = false;
scope.openPopup = function($event) {
$event.preventDefault();
$event.stopPropagation();
scope.popupOpen = true;
};
scope.open = function($event) {
$event.preventDefault();
$event.stopPropagation();
scope.opened = true;
};
}
};
});
Template code:
<div class="row">
<div class="col-md-6">
<p class="input-group">
<input type="text" class="form-control" id="{{myid}}" uib-datepicker-popup ng-model="model" is-open="opened" ng-required="true" />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open($event)"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
</p>
</div>
</div>
You're using ng-model in the directive code, but you're looking for model in the directive template.
<my-datepicker ng-model="selected" myid="someid"></my-datepicker>
And here you are looking for an attribute called model:
app.directive('myDatepicker', function() {
return {
restrict: 'E',
scope: {
//this line should be ngModel
model: "=",
myid: "#"
},
Here's a working plunker: https://plnkr.co/edit/s5CT4xGqXtUxgCH8vw8q?p=preview
In general, I try to avoid using names like "model" and "ng-model" on custom directives, as built-in angular attributes should be distinguished from custom attributes.

Validating custom directive inside a form

I've a form which contains a custom directive that I want to validate. I could do that if the scope is not an isolate scope, but how to proceed if we have an isolate scope case? Here is an example where the form contains 2 fields: first and last name. First name is directly on the form but the last name is within a directive. When i hit 'Save' only first name gets validated. Can somebody point out what am I missing? Code goes somewhat like this:
Main form:
<form class=" form form-group" name="personForm" ng-submit="saveInfo(personForm.$valid, '#/success')" novalidate>
<label for="fname"><strong>First Name:</strong></label>
<input type="text"
name="fname"
class="form-control"
ng-model="person.firstname"
ng-maxlength=10
ng-minlength=3
ng-required="true">
<div class="error-message" ng-messages="personForm.fname.$error" data-ng-if="interacted(personForm.fname)">
<div ng-message="required">This is a required field</div>
<div ng-message="maxlength">max length should be 10</div>
<div ng-message="minlength">min length should be 3</div>
</div>
<last-name ng-model="person"></last-name>
<br/>
<button class="btn btn-primary" type="submit">Save</button>
and the directive (last name):
<label for="lname"><strong>Last Name:</strong></label>
<input type="text"
name="lname"
class="form-control"
ng-model="person.lastname"
ng-maxlength=20
ng-minlength=5
ng-required="required">
<div ng-messages="personForm.lname.$error" data-ng-if="interacted(personForm.lname)">
<div ng-message="required">This is a required field</div>
<div ng-message="maxlength">max length of last name should be 20</div>
<div ng-message="minlength">min length of last name should be 5</div>
</div>
... and finally here is my javascript:
var app = angular.module('MyApp',['ngRoute','ngMessages']);
app.config(function ($routeProvider) {
$routeProvider
.when('/success', {
templateUrl: 'success.html'
})
.when('/', {
templateUrl: 'first-name.html',
controller: 'MyController'
})
})
app.directive('lastName', function() {
return {
restrict: 'E',
scope: {
person: '=ngModel',
},
require:'ngModel',
templateUrl: 'last-name.html'
}
});
app.controller('MyController', function($scope, $location) {
$scope.person = {};
$scope.submitted = false;
$scope.interacted = function(field) {
return $scope.submitted || field.$dirty;
};
$scope.saveInfo = function(isValid, url) {
$scope.submitted = true;
if (isValid) {
$location.path(url);
} else {
alert('Missing values in mandatory fields');
}
}
});
I kinda solved my problem.This is a "poor man's solution" per se so i implore the experts reading this post to suggest me a better way.
My issue was "how will the directive know, if it's parent form has been submitted" (without sharing the scope). My answer was to send another attribute into the directive that indicated the form submission (scope: { formSubmitted: '='}).
app.directive('lastName', function() {
return {
restrict: 'E',
scope: {
formSubmitted: '='
},
require:'ngModel',
templateUrl: './last-name.html',
link: function(scope, element, attrs, controllers) {
scope.$watch('lastname', function() {
controllers.$setViewValue(scope.lastname);
});
}
};
});
So the directive looks a bit silly :( but works.
<last-name ng-model="person.lastname" form-submitted="submitted"></last-name>
... and the "submitted" was something that was used anyways to detect if submit button was pushed.
So with that and some help of other posts on stack overflow (ng-form: to enclose the elements in the directive), I was able to get it working. Here is the plunker for the same.

Angular directive issue

For few days I was trying to debug why my directive doesn't work. No event has been fired when I press a button. Finally I found which line breaks everything!
Inside template html I have line datepicker-popup ng-model="{{model}}" min-date="{{minDate}}" is-open="{{isOpened}}" if I remove it everything works well. But it is essential part in my custom directive and I want to keep it. I assume problem is that I am using directive inside custom directive?
Could you please help me to identify problem and find a correct solution?
Thanks for any help!
Directive:
(function(){
function directive(){
return {
scope :{
model:'=model',
minDate:'=minDate',
isOpened:'=isOpened'
},
restrict: 'E',
templateUrl: 'templates/datepicker/datepicker.html',
controller: 'Ctrl'
};
};
app.directive('myDirective', directive);
})();
controller:
(function(){
function Controller($scope) {
$scope.open = function() {
alert('HELLO');
};
app.controller('Ctrl', Controller);
})();
template html:
<fieldset>
<div class='input-group'>
<input type="text" class="form-control" datepicker-popup ng-model="{{model}}" min-date="{{minDate}}" is-open="{{isOpened}}" datepicker-options="dateOptions" ng-required="true" close-text="Close" />
<span ng-click="open()" class="btn btn-default input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
</fieldset>
As mentioned in the other answer, you don't need the curly braces in your template because it's a two way binding.
The problem with your click function is probably the isolated scope of your custom directive.
If you'd like to have the open method in your main controller you could pass it to the isolated scope.
Please find below a demo of your directive or here at jsfiddle.
angular.module('demoApp', ['ui.bootstrap'])
.controller('mainController', Controller)
.directive('customDir', Directive);
function Directive() {
return {
scope: {
model: '=',
minDate: '=',
isOpened: '='
},
transclude: true,
restrict: 'E',
templateUrl: 'templates/datepicker/datepicker.html',
//controller: 'Ctrl'
controller: function($scope) {
$scope.open = function() {
console.log('open popup now!!');
$scope.isOpened = true;
};
}
};
}
function Controller($scope) {
$scope.open = function () {
alert('HELLO'); // not called becasue of isolated scope of custom directive
};
$scope.dateModel = {
date: new Date(),
min: new Date()
};
$scope.isOpened = false;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.3/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.2/ui-bootstrap.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.2/ui-bootstrap-tpls.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
<div ng-app="demoApp" ng-controller="mainController">
<script type="text/ng-template" id="templates/datepicker/datepicker.html">
<fieldset>
<div class='input-group'>
<input type="text" class="form-control" datepicker-popup="" ng-model="model" min-date="minDate" is-open="isOpened" datepicker-options="dateOptions" ng-required="true" close-text="Close" />
<span ng-click="open()" class="btn btn-default input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
</fieldset>
</script>
<custom-dir model="dateModel.date" min-date="dateModel.min" is-opened="isOpened"></custom-dir>
</div>
When you define your directive's scope properties with a = symbol, you don't need to use {{}} in your views.
Remove the {{}} from your view:
<input type="text" class="form-control" datepicker-popup ng-model="model" min-date="minDate" is-open="isOpened" datepicker-options="dateOptions" ng-required="true" close-text="Close" />
Here's more info regarding directive's scope properties using =, & and #.

Angular JS pass attributes to directive template

I have custom directive for datepicker. I want to reuse it in several different places. But in order to reuse current directive I have to dynamically pass and change different attributes into my-datepicker directive.
If you look inside datepicker.html I am using following attributes: ng-model="departureDate" min-date="minDateDeparture" is-open="departureOpened".
Question: How do I set this attributes on the my-datepicker element level and pass all the way down to my directive html template? I want to achieve something like that:
<my-datepicker ng-model="departureDate1" min-date="minDateDeparture1" is-open="departureOpened1"></my-datepicker>
<my-datepicker ng-model="departureDate2" min-date="minDateDeparture2" is-open="departureOpened2"></my-datepicker>
Thanks for any help!
datepicker-contoller.js
app.directive('myDatepicker', function() {
return {
restrict: 'E',
templateUrl: 'templates/datepicker/datepicker.html'
};
});
datepicker.html
<fieldset>
<div class='input-group'>
<input type="text" class="form-control" datepicker-popup ng-model="departureDate" min-date="minDateDeparture" is-open="departureOpened" datepicker-options="dateOptions" ng-required="true" close-text="Close" />
<span ng-click="open1($event)" class="btn btn-default input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
</fieldset>
Datepicker usage
<div ng-controller="MyController">
<div class="col-md-2">
<my-datepicker></my-datepicker>
</div>
<div class="col-md-2">
<my-datepicker></my-datepicker>
</div>
</div>
Update: See this jsFiddle: http://jsfiddle.net/j31ky7c2/
You can pass the data as well as functions as attribute in your directive.
<my-datepicker min-date="minDateDeparture2" is-open="departureOpened2" some-function="testFunction()"></my-datepicker>
You can receive this data in your directive's scope.
directive('myDatepicker', [function() {
return {
restrict: 'E',
scope: {
minDate: '#',
isOpen: '#',
someFunction: '&'
},
link: function(scope, elm, attrs) {
}
}
}]);
Then you can simply use minDate and isOpen and someFunction in your directive template like:
<div ng-bind="{{::minDate}}"></div>
<div ng-bind="{{::isOpen}}"></div>
<Button ng-click="someFunction()">Click me</Button>

How to access a dom element in a Angular directive

I am trying to attach a keyup event to a directive in my Angular project. Here is the directive:
angular.module('clinicalApp').directive('chatContainer', function() {
return {
scope: {
encounter: '=',
count: '='
}
templateUrl: 'views/chat.container.html',
link: function(scope, elem, attrs) {
scope.count = 500;
}
};
});
And here is the html from the template:
<div class="span4 chat-container">
<div class="chat-body">
<form accept-charset="UTF-8" action="#" method="POST">
<div class="text-area-container">
<textarea id="chatBox" class="chat-box" rows="2"></textarea>
</div>
<div class="button-container btn-group btn-group-chat">
<input id="comment" class="btn btn-primary btn-small btn-comment disabled" value="Comment" ng-click="addMessage()"/>
</div>
</form>
</div>
</div>
</div>
I want to access the chatbox in my link function and attach the keyup event to it. I know I can get it with jQuery, but that cannot be the Angular way. What is the proper way to grab that element from the dom?
You can easily do it with Angular' element' find() method:
var chatbox = elem.find("textarea"); // Finding
chatbox.bind("keyup",function(){ // Binding
console.log("KEYUP!")
})
Live example: http://jsfiddle.net/cherniv/S7XdK/
You can use element.find(yourSelector) as previously mentioned, but it is better to use ngKeyUp, similar to how you would use ngClick:
https://docs.angularjs.org/api/ng/directive/ngKeyup

Resources