evaluate expression in scope - angularjs

I am trying to create a directive for my date pickers here is the relevant code.
usage
<date-input open="openCalendar = true">
<input ng-model="ctrl.model.theDate"
uib-datepicker-popup="dd-MMMM-yyyy"
is-open="openCalendar">
</date-input>
directive
angular
.module('common.directives')
.directive('dateInput', function() {
return {
restrict: 'E',
transclude: true,
scope: {open:'&'},
template: 'date-input-template',
};
});
template
<script type="text/ng-template" id="date-input-template>
<div class="input-group">
<ng-transclude></ng-transclude>
<div class="input-group-btn">
<button class="btn btn-default" type="button" ng-click="open()">
<i class="glyphicon glyphicon-calendar"></i>
</button>
</div>
</div>
</script>
This works once. If I try to open the calendar a 2nd time, the date picker does not display. I'm sure I'm doing something wrong, just not sure what it is.
Any suggestions or guidance is appreciated.

You should call the function with the expression
ng-click="open()">

Your expression for ng-click needs to invoke open:
ng-click="open()"
Edit: You may want to use an object like data.openCalendar = true to avoid prototypal scope inheritance issues, or move the openCalendar = true expression into a function and invoke that function.

Related

Angular UI datepicker popup open without ng-click

According to official Angular UI documentation for datepicker users in popup style I need to create additional button with ng-click event on it to change $scope property which was binded to is-open attribute like so:
<p class="input-group">
<input type="text" class="form-control" uib-datepicker-popup="{{format}}" ng-model="dt" is-open="popup1.opened" min-date="minDate" max-date="maxDate" datepicker-options="dateOptions" date-disabled="disabled(date, mode)" ng-required="true" close-text="Close" alt-input-formats="altInputFormats" />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open1()"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
</p>
In my application it is possible to have more than 10 such datepickers per view so I need to implement property for is-open attribute per each.
Is there any way to open datepicker popup without is-open attribute?
If you have +10 datepickers and repeat the same markup over and over, and need to create $scope functions without any real purpose - then it is almost screaming for a directive to do the trivial tasks! The markup you are repeating can be placed in a template :
<script type="text/ng-template" id="dateAutomater.html">
<input type="text" class="form-control"/>
<span class="input-group-btn">
<button type="button" class="btn btn-default">
<i class="glyphicon glyphicon-calendar"></i>
</button>
</span>
</script>
The directive (the very basics) could look like this :
.directive('dateAutomater', ['$compile', function($compile) {
return {
transclude: true,
templateUrl: 'dateAutomater.html',
restrict: 'AE',
link: function ($scope, element, attrs) {
$scope.dateInfo = $scope.dateInfo || {};
var dateInfo = $scope.dateInfo,
input = element.find('input'),
button = element.find('button'),
name = input.name || 'date'+Object.keys($scope.dateInfo).length,
info = {
open: false,
click: function() {
this.open = true
}
}
dateInfo[name] = info;
input.attr('ng-model', attrs.dateAutomater);
input.attr('uib-datepicker-popup', 'dd-MMMM-yyyy');
input.attr('is-open', 'dateInfo[\"'+name+'\"].open')
button.attr('ng-click', 'dateInfo[\"'+name+'\"].click()');
$compile(element.contents())($scope);
}
}
}])
It simply takes the model as argument, injects the markup from the template and bind the important variable is-open and ng-click function to a self maintained object, $scope.dateInfo. Usage
<p class="input-group" date-automater="dt"></p>
<p class="input-group" date-automater="date"></p>
<p class="input-group" date-automater="yesterDay"></p>
...
demo -> http://plnkr.co/edit/H6hgYdF420R4IKdjCBGM?p=preview
Now expand the directive / template to set other default properties you want on the datepicker, like min-date and so on.

Input value from a specialized controller isn't being sent back to the main controller

I'm building an application that's making use of Angular UI Bootstrap's datepicker in several spots. I've included some other enhancements to this, such as validation styling, and wanted to propagate it everywhere so I put it in a directive.
app.controller('DateController', function () {
var vm = this;
vm.status = {
opened: false
};
vm.open = open;
function open($event) {
vm.status.opened = true;
}
});
app.directive('customDatePicker', function () {
return {
restrict: 'E',
scope: {
name: '=',
bindingProperty: '=',
minDate: '=',
maxDate: '=',
maxMode: '=',
format: '=',
isRequired: '=',
form: '='
},
templateUrl: "/Scripts/SharedAngular/Templates/datePicker.html"
};
});
With the template URL code looking like:
<div ng-controller="DateController as datePicker">
<p class="input-group">
<input type="text" id="{{ name }}_datePicker" name="{{ name }}_datePicker" class="form-control" max-mode="maxMode" datepicker-popup="{{format}}" ng-model="bindingProperty" min-date="minDate" max-date="maxDate" ng-required="isRequired" close-text="Close" is-open="datePicker.status.opened" />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="datePicker.open($event)"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
<span ng-show="form.{{ name }}_datePicker.$invalid && form.{{ name }}_datePicker.$dirty" class="glyphicon glyphicon-exclamation-sign form-control-feedback datepicker-error-icon"></span>
</p>
</div>
I'm including this directive in a couple places. It is placed on a page within the scope of a different controller like this:
<div ng-controller="myController as ctrl">
<custom-date-picker name="'somePrefix'" form="myForm" binding-property="ctrl.date" max-mode="'month'" max-date="ctrl.currentDate" format="'yyyy/MM/dd'" is-required="true"></custom-date-picker>
</div>
So, I want to take a property from myController and two-way bind it to the directive's date picker. However, what ends up actually happening is that the current value for ctrl.date property is passed to the datepicker, but any changes to that value that happen in the directive are not sent back. So, it seems that myController and DateController only pass the value one-way, and only when the directive is loaded onto the page. To be clear, the directive doesn't seem to be the source of my issue, since putting the HTML code from the template directly onto my page results in the same behavior. So, it appears that the issue is specifically due to the use of two different controllers.
Previously, I was using ng-model both in the template code and the directive on my primary page. Neither option seems to work. I'm open to any ideas on how to get the input from my directive to be sent back to the myController.
I figured out how to ditch the DateController entirely. I put my expressions directly into the template elements via ng-click and ng-init. The input creates a property called opened and uses ng-init initializes it to false. It then uses that property for the is-open attribute of the datepicker. Then the button just uses ng-click="opened=true". Definitely simplifies things and removes the nesting of controllers.
Directive:
app.directive('customDatePicker', function () {
return {
restrict: 'E',
scope: {
name: '=',
bindingProperty: '=ngModel',
minDate: '=',
maxDate: '=',
maxMode: '=',
format: '=',
isRequired: '=',
form: '='
},
require: 'ngModel',
templateUrl: "/Scripts/SharedAngular/Templates/datePicker.html"
};
});
Template:
<p class="input-group">
<input type="text" id="{{ name }}_datePicker" name="{{ name }}_datePicker" class="form-control" max-mode="maxMode" datepicker-popup="{{format}}" ng-model="bindingProperty" min-date="minDate" max-date="maxDate" ng-required="isRequired" close-text="Close" is-open="opened" ng-init="opened=false" />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="opened=true"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
<span ng-show="form.{{ name }}_datePicker.$invalid && form.{{ name }}_datePicker.$dirty" class="glyphicon glyphicon-exclamation-sign form-control-feedback datepicker-error-icon"></span>
</p>
Using the directive:
<div ng-controller="MyController as ctrl">
<custom-date-picker name="'somePrefix'" form="myForm" ng-model="ctrl.Date" max-mode="'month'" max-date="ctrl.currentDate" format="'yyyy/MM/dd'" is-required="true"></custom-date-picker>
</div>

Can't create a popover with custom template

First I tried using angular-ui
<span popover-template="removePopover.html" popover-title="Remove?" class="glyphicon glyphicon-remove cursor-select"></span>
here the template is not included and no errors are provided in console. As I undestood from previous questions this capability is still in development (using v0.13.0).
Then I tried using bootstrap's popover
<span delete-popover row-index={{$index}} data-placement="left" class="glyphicon glyphicon-remove cursor-select"></span>
This is included to popover
<div id="removePopover" style="display: none">
<button id="remove" type="button" ng-click="removeElement()" class="btn btn-danger">Remove</button>
<button type="button" ng-click="cancelElement()" class="btn btn-warning">Cancel</button>
</div>
This is the managing directive
app.directive('deletePopover', function(){
return{
link: function(scope, element, attrs) {
$(element).popover({
html : true,
container : element,
content: function() {
return $('#removePopover').html();
}
});
scope.removeElement = function(){
console.log("remove"); //does not get here
}
scope.cancelElement = function(){
console.log("cancel"); //does not get here
}
}
};
});
In case of bootstrap's popover the scope is messed up. cancelElement() call does not arrive in directive neither the parent controller.
If anyone could help me get atleast on of these working it would be great.

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 get the form data when the form is in a directive in Angular?

I have this this template:
<div class="modal" id="popupModal" tabindex="-1" role="dialog" aria-labelledby="createBuildingLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title" id="createBuildingLabel">{{ title }}</h4>
</div>
<form data-ng-submit="submit()">
<div class="modal-body" data-ng-transclude>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-ng-click="visible = false">Annuleren</button>
<button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-save"></span>Maken</button>
</div>
</form>
</div>
</div>
</div>
and here's the directive:
app.directive("modalPopup", [function () {
return {
restrict: 'E',
templateUrl: 'Utils/ModalPopup',
scope: {
title: '#',
onSubmit: '&',
visible: '='
},
transclude: true,
link: function (scope, element, attributes) {
var container = $("#popupModal");
scope.submit = function (newGroup) {
scope.onSubmit(newGroup);
}
scope.hide = function () {
container.modal('hide');
}
scope.show = function () {
container.modal('show');
}
scope.$watch('visible', function (newVal, oldVal) {
if (newVal === true) {
scope.show();
}
else {
scope.hide();
}
})
}
}
}]);
As you can see I have declared my form tag inside the directive and I also use transclude to determine how my form is going to look like. For now I have this:
<modal-popup title="Nieuwe groep aanmaken" data-on-submit="createGroup()" visible="showAddGroupForm">
<div class="row">
<div class="col-md-3">Kopieren van:</div>
<div class="col-md-8">
<select class="form-control" data-ng-model="newGroup.Year">
<option value="">Nieuw jaar</option>
<option data-ng-repeat="year in years" value="{{year.Id}}">{{year.Name}}</option>
</select>
</div>
</div>
<div class="row">
<div class="col-md-3">Naam</div>
<div class="col-md-8">
<input type="text" class="form-control" data-ng-model="newGroup.Name" />
</div>
</div>
</modal-popup>
When the submit button is pressed, I want the data to be available in my controller.
I ques the data isn't available because of the isolated scope, however I'm not sure. What do I need to do to get the data back from the directive into my controller?
PS: I know about angular-ui and angularstrap, but I'm doing this to learn about Angular.
EDIT:
Here's a Fiddle
I think the cause is a misunderstanding about how scopes work (especially with transclusion).
Let's start with this code (from the fiddle):
<div ng-controller="MyCtrl">
<my-popup on-submit="formSubmitted(model)">
<input type="text" ng-model="model.Name"/>
</my-popup>
</div>
Since <my-popup> transcludes its content, the scope above is that of MyCtrl, even in the content of the directive. By content I mean the <input>, NOT the directive template, i.e. the <div><form ... code.
Therefore it is implied that model (as used in ng-model="model.Name") is a property of the scope of MyCtrl, as is formSubmitted(). Since both are members of the same scope, you do not need to pass the model as argument; you could just do:
(in the template:)
<my-popup on-submit="formSubmitted()"><!-- no `model` argument -->
(the controller:)
function MyCtrl($scope) {
// I like declaring $scope members explicitly,
// though it can work without it (charlietfl comments)
$scope.model = {};
$scope.submittedValue = null;
$scope.formSubmitted = function() {
// another bug was here; `model` is always a member of the `$scope`
// object, not a local var
$scope.submittedValue = $scope.model.Name;
}
}
Another bug is in the directive code:
link: function(scope, element, attributes){
scope.submit = function(){
scope.onSubmit({model: model});
}
}
The variable model (not the name model:) is undefined! It is a property of the parent scope, so you would have a chance if the scope was not isolated. With the isolated scope of the directive, it may work with an awful workaround that I am not even considering to write :)
Luckily, you do not need the directive to know about things happening in the external scope. The directive has one function, to display the form and the submit button and invoke a callback when the submit button is clicked. So the following is not only enough for this example, but also conceptually correct (the directive does not care what is happenning outside it):
link: function(scope, element, attributes){
scope.submit = function(){
scope.onSubmit();
}
}
See the fiddle: http://jsfiddle.net/PRnYg/
By the way: You are using Angular v1.0.1. This is WAAAAY too old, seriously consider upgrading!!! If you do upgrade, add the closing </div> to the template: <div ng-transclude></div>.

Resources