AngularJS datepicker custom directive formatter not displaying date - angularjs

I have two custom datepicker directives. One is called startDatePicker directive and the second is endDatePicker. I'm passing model from one to another which is fine, however when I pass date as string it does not display the date. I know it shouldn't because datepicker expects date object and it cannot convert it to date object automatically. Because my backend server returns dates as strings I need to convert to date objects. For that I use model.$formatter in my directive to convert string to date object and then send it to the view so the date can be selected and displayed. The date is getting selected, but it's not getting displayed..
To simulate the issue I have defined two variables in my controller. These two will be used for datepicker models as well. The first variable gets selected and displayed in the datepicker, but second only gets selected, but not displayed. What I'm doing wrong?
$scope.startDate = moment.utc("2016/12/25 00:00:00")._d;
$scope.endDate = "2016/12/25 00:00:00";
I have setup plunker to show the issue http://plnkr.co/edit/trEkwuO06VMJ1HdCkl5w?p=preview
Complete directive below
angular.module('ui.bootstrap.demo').directive('endDatepicker', function() {
return {
restrict: 'A',
scope: {
ngModel: "=",
minStartDate: "=",
},
require: 'ngModel',
replace: true,
templateUrl: 'datepicker-template.html',
link: function(scope, element, attrs, ngModel) {
ngModel.$formatters.push(function getJsonDate(date) {
if (!date) return null;
console.log("Unformatted date " + date)
var newDate = moment.utc(date)._d;
console.log("Formatter fired " + newDate)
return newDate
});
scope.popupOpen = false;
scope.openPopup = function($event) {
$event.preventDefault();
$event.stopPropagation();
scope.popupOpen = true;
};
console.log(scope.endDate);
scope.$watch('minStartDate', function(newValue,oldValue){
if (newValue) {
console.log(newValue)
scope.dateOptions.minDate = newValue;
}
})
scope.dateOptions = {
startingDay: 1,
minDate: moment.utc()._d
}
scope.open = function($event) {
$event.preventDefault();
$event.stopPropagation();
scope.opened = true;
};
}
};
});

This can be a confusing issue.
Right there will be two ngModel controllers associated with the endDatepicker directive.
One for ng-model here:
<div end-datepicker ng-model="endDate" min-start-date="startDate"></div>
And one for ng-model in the template:
<input type="text" class="form-control" ... ng-model="ngModel" ...
Both controllers will work with the same model, but you need to add the formatter to the correct one.
When you require the controller in your directive you will get the ngModel controller for the first one, but you need the controller for the ngModel on the input where the formatted value is to be shown.
Instead of requiring the controller the following should work:
link: function(scope, element, attrs, ngModel) {
var inputModelController = element.find('input').controller('ngModel');
inputModelController.$formatters.push(function getJsonDate(date) {
// Code
});

Related

Change model binding in Angular JS dynamically using a directive?

Is it possible to change the binding expression on an ng-model directive dynamically?
This is what I'am trying to accomplish:
I have an input field that initially should propose a value (based on a value that the user entered some time ago -> "repetitions.lastTime"). This value should be bound initially. Then, if the user clicks on the input field, the proposed value should be copied to another property ("repetitions.current") on the scope. From now on, the input field should be bound to "repetitions.current".
Edit Plunker: http://plnkr.co/edit/9EbtnEYoJccr02KYUzBN?p=preview
HTML
<mo-repetitions mo-last-time="repetitions.lastTime" mo-current="repetitions.current"></mo-repetitions>
<p>current: {{repetitions.current}}</p>
<p>last time: {{repetitions.lastTime}}</p>
JavaScript
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.repetitions = {
lastTime : 5,
current : 0
};
});
app.directive('moRepetitions', [function () {
return {
restrict: 'E',
scope: {
moLastTime : "=",
moCurrent : "="
},
link: function (scope, element, attrs, ngModel) {
element.css("color", "gray");
scope.activated = false;
scope.activate = function () {
if (scope.activated)
return;
else
scope.activated = true;
element.css("color", "black");
scope.moCurrent = scope.moLastTime;
//This is not working, because it apparently comes too late:
attrs['ngModel'] = 'moCurrent';
};
},
template: '<input ng-model="moLastTime" ng-click="activate()" type="number" />',
replace: true
};
}]);
Can someone point me on the right track?
Created a plnkr
element.attr('ng-model', 'moCurrent');
$compile(element)(scope);

AngularJS: Format DateTimePicker on load

I'm really struggling to get a datetimepicker working in a custom application within Umbraco 7. I've eventually got as far as creating a directive and saving the value back to the model but after hours of trying various examples I still have not been able to find a way to format the date on the initial load. The date format is '1901-02-07T01:05:00Z' until you interact with the datepicker. Here's what I have so far in my directive:
angular.module("umbraco.directives")
.directive('datetimepicker', function ($parse) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var ngModel = $parse(attrs.ngModel);
element.datetimepicker({
format: 'yyyy-mm-dd hh:ii:ss',
autoclose: true
})
.on('changeDate', function () {
scope.$apply(function (scope) {
ngModel.assign(scope, element.val());
});
})
}
}
});
The HTML used is:
<input type="text" ng-model="ShowFrom" datetimepicker />
Does anybody have any ideas on how I would achieve this? I've also tried using parsers and formatters with no luck.
Thanks
As I suspected the answer was to use parsers and formatters. I was originally getting an "unknown format provider" error when trying to use them, it turns out that adding require: 'ngModel' to the directive solved the problem. The $parser function converts the string back to a date for updating the model while the $formatter function takes the model value and formats it appropriately as a string.
Below is the final version of the directive. If anyone else would like to use the code then here is the datepicker this was written for.
angular.module("umbraco.directives")
.directive('datetimepicker', function ($parse, $filter) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ctrl) {
var ngModel = $parse(attrs.ngModel);
element.datetimepicker({
format: 'yyyy-mm-dd hh:ii:00',
autoclose: true
})
.on('changeDate', function () {
scope.$apply(function (scope) {
ngModel.assign(scope, element.val());
});
});
ctrl.$parsers.push(function (value) {
return new Date(Date.parse(value));
});
ctrl.$formatters.push(function (value) {
if (value.indexOf('0001-01-01') < 0)
return value.replace('T', ' ').replace('Z', '');
else
return "";
});
}
}
});

Why does the ngModelCtrl.$valid not update?

I'm trying to create a directive that contains an inputfield with a ng-model and knows if the inputcontrol is valid. (I want to change a class on a label within the directive based on this state.) I want to use the ngModelController.$valid to check this, but it always returns true.
formcontroller.$valid or formcontroller.inputfieldname.$valid do work as exprected, but since im trying to build a reusable component using a formcontroller is not very handy because then i have to determine what field of the form corresponds with the current directive.
I dont understand why one works and one doesnt, because in de angular source it seems to be the same code that should manage these states: The ngModelController.$setValidity function.
I created a test directive that contains a numeric field with required and a min value. As you can see in the fiddle below, the model controller is only triggered during page load and after that never changes.
jsfiddle with example directive
Directive code:
angular.module('ui.directives', []).directive('textboxValid',
function() {
return {
restrict: 'E',
require: ['ngModel', '^form'],
scope: {
ngModel: '='
},
template: '<input type="number" required name="somefield" min="3" ng-model="ngModel" /> '+
'<br>Form controller $valid: {{formfieldvalid}} <br> ' +
'Model controller $valid: {{modelvalid}} <br>'+
'Form controller $valid: {{formvalid}} <br>',
link: function (scope, element, attrs, controllers) {
var ngModelCtrl = controllers[0];
var formCtrl = controllers[1];
function modelvalid(){
return ngModelCtrl.$valid;
}
function formvalid(){
return formCtrl.$valid;
}
scope.$watch(formvalid, function(newVal,oldVal)
{
scope.modelvalid = ngModelCtrl.$valid;
scope.formvalid = formCtrl.$valid;
scope.formfieldvalid = formCtrl.somefield.$valid;
});
scope.$watch(modelvalid, function(newVal,oldVal)
{
scope.modelvalid = ngModelCtrl.$valid;
scope.formvalid = formCtrl.$valid;
scope.formfieldvalid = formCtrl.somefield.$valid;
//This one only gets triggered on pageload
alert('modelvalid ' + newVal );
});
}
};
}
);
Can someone help me understand this behaviour?
I think because you're watching a function and the $watch is only execute when this function is called !!
Watch the model instead like that :
scope.$watch('ngModel', function(newVal,oldVal)
{
scope.modelvalid = ngModelCtrl.$valid;
scope.formvalid = formCtrl.$valid;
scope.formfieldvalid = formCtrl.somefield.$valid;
//This one triggered each time the model changes
alert('modelvalid ' + ngModelCtrl.$valid );
});
I figured it out..
The textboxValid directive has a ng-model directive, and so does the input that gets created by the directive template. However, these are two different directives, both with their own seperate controller.
So, i changed my solution to use an attribute directive like below. This works as expected.
.directive('attributetest',
function() {
return {
restrict: 'A',
require: 'ngModel',
scope: {
ngModel: '='
},
link: function (scope, element, attrs, ngModelCtrl) {
function modelvalid(){
return ngModelCtrl.$valid;
}
scope.$watch(modelvalid, function(newVal,oldVal){
console.log('scope.modelvalid = ' + ngModelCtrl.$valid );
});
}
};
});

How do I properly set the value of my timepicker directive in AngularJS?

I'm trying to create a timepicker directive in AngularJS that uses the jquery timepicker plugin. (I am unable to get any of the existing angular TimePickers to work in IE8).
So far, I was able to get the directive to work as far as updating the scope when a time is selected. However, what I need to accomplish now is getting the input to display the time, rather than the text of the model's value when the page first loads. See below:
this is what shows:
this is what I want:
Here is my directive:
'use strict';
playgroundApp.directive('timePicker', function () {
return {
restrict: 'A',
require: "?ngModel",
link: function(scope, element, attrs, controller) {
element.timepicker();
//controller.$setViewValue(element.timepicker('setTime', ngModel.$modelValue));
//ngModel.$render = function() {
// var date = ngModel.$modelValue ? new Date(ngModel.$modelValue) : null;
//};
//if (date) {
// controller.$setViewValue(element.timepicker('setTime', date));
//}
element.on('change', function() {
scope.$apply(function() {
controller.$setViewValue(element.timepicker('getTime', new Date()));
});
});
},
};
})
The commented code is what I've attempted, but it doesn't work. I get an error that reads, ngModel is undefined. So, to clarify, when the page first loads, if there is a model for that input field, I want the input to show only the time, as it does after a value is selected.
Thanks.
EDIT:
Ok, after making some trial and error changes, my link function looks like this:
link: function (scope, element, attrs, controller) {
if (!controller) {
return;
}
element.timepicker();
var val = controller.$modelValue;
var date = controller.$modelValue ? new Date(controller.$modelValue) : null;
controller.$setViewValue(element.timepicker('setTime', controller.$modelValue));
//ngModel.$render = function () {
// var date = ngModel.$modelValue ? new Date(ngModel.$modelValue) : null;
//};
if (date) {
controller.$setViewValue(element.timepicker('setTime', date));
}
element.on('change', function() {
scope.$apply(function() {
controller.$setViewValue(element.timepicker('getTime', new Date()));
});
});
},
This doesn't give me any errors, but the $modelValue is always NaN. Here is my controller code:
$scope.startTime = new Date();
$scope.endTime = new Date();
and the relevant html:
<input id="startTime" ng-model="startTime" time-picker/>
<input id="endTime" ng-model="endTime" time-picker />
Is there something else I need to do?
I spent several days trying with the same plugin without getting results and eventually I found another:
http://trentrichardson.com/examples/timepicker/
It works perfectly using the following directive:
app.directive('timepicker', function() {
return {
restrict: 'A',
require : 'ngModel',
link : function (scope, element, attrs, ngModelCtrl) {
$(function(){
element.timepicker({
onSelect:function (time) {
ngModelCtrl.$setViewValue(time);
scope.$apply();
}
});
});
}
}
});
I hope you find useful.

Why is the value not updated to the view from within a directive?

I have this html:
<form name="form" novalidate data-ng-submit="TradeDaysFormSubmit()">
<div data-ng-repeat="tradeDay in tradeDays">
{{ tradeDay }}
<br />
<div inline-calender class="trade-calender-box" date="tradeDay" data-ng-model="tradeDay"></div>
</div>
</form>
With this controller:
$scope.tradeDays = [];
$scope.tradeDays.push("31-01-2013");
$scope.tradeDays.push("28-02-2013");
$scope.tradeDays.push("10-03-2013");
$scope.tradeDays.push("11-04-2013");
$scope.tradeDays.push("12-05-2013");
$scope.tradeDays.push("13-06-2013");
$scope.tradeDays.push("11-07-2013");
$scope.tradeDays.push("23-08-2013");
$scope.tradeDays.push("14-09-2013");
$scope.tradeDays.push("30-10-2013");
$scope.tradeDays.push("22-11-2013");
$scope.tradeDays.push("23-12-2013");
$scope.tradeDays.push("31-01-2014");
And the directive:
app.directive("inlineCalender", function () {
return {
restrict: 'A',
require: 'ngModel',
scope: {
date:'='
},
link: function (scope, element, attributes, modelController) {
scope.$watch('date', function (newValue) {
if (!_.isString(newValue)) return;
dateparts = scope.date.split('-');
var minDate = new Date(dateparts[2], parseInt(dateparts[1]) - 1, 1);
var maxDate = new Date(dateparts[2], parseInt(dateparts[1]), 0);
element.datepicker({ defaultDate: scope.date, hideIfNoPrevNext: true, minDate: minDate, maxDate: maxDate });
element.bind("change", function (event) {
var currentDate = element.datepicker("getDate");
scope.date = currentDate;
modelController.$setViewValue(currentDate.toString());
modelController.$render();
scope.$apply();
});
});
}
}
});
The view shows all my calenders, so far so good. But, when I change a date, the new value is not passed to the model. As you can see, in the HTML I have {{ tradeDay }}, but this value doesn't change when I select a date on my calender.
Can someone tell me what I am doing wrong here? I've tried the model controller, and isolate scope with two-way binding, but neither did work.
Fiddle When you click a date in the datepicker, the text above is not updated.
There is just a small mistake in your code you should be using dot in a model just change your code to below and it will work
http://jsfiddle.net/Qvu5u/3/
Just use dot model like
$scope.tradeDays.push({date:'31-01-2013'});
https://github.com/angular/angular.js/wiki/Understanding-Scopes
The best practice to deal with the isolated scope is always have a '.' in your ng-models.
You can remove the $watch to prevent $apply to trigger multiple times. (You can see that in your code if you log something before $apply)
The date change can be easily captured by the callback function of the calender, you can change the code to:
link: function (scope, element, attributes, modelController) {
dateparts = scope.date.split('-');
var minDate = new Date(dateparts[2], parseInt(dateparts[1]) - 1, 1);
var maxDate = new Date(dateparts[2], parseInt(dateparts[1]), 0);
element.datepicker({
defaultDate: scope.date,
hideIfNoPrevNext: true,
minDate: minDate,
maxDate: maxDate,
onSelect: function (date) {
modelController.$setViewValue(date);
scope.date = date;
scope.$apply();
}
});
}
Demo on jsFiddle

Resources