AngularJS: Format DateTimePicker on load - angularjs

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 "";
});
}
}
});

Related

Angular filter not working

I am coding a filter that will format phone numbers in a contact form I've built however for some reason the value in the input is never being updated and I'm not sure what I'm doing wrong.
Here's my HTML:
<div class='form-group'>
<input name='phone' ng-model='home.contact.phone' placeholder='(Area code) Phone' required ng-bind='home.contact.phone | phone' />
</div>
and here's my filter:
(function () {
'use strict'
angular
.module('Allay.phoneFilter', [])
.filter('phone', function () {
return function (phone) {
if(!phone) return '';
var res = phone + '::' // Since this isn't working, I'm doing something super simple, adding a double colon to the end of the phone number.
return res;
}
});
})();
I'm not sure if you need this, but here's the controller:
(function () {
'use strict'
angular
.module('Allay', [
'Allay.phoneFilter'
])
.controller('HomeController', function () {
var home = this;
});
})();
If I add an alert(res) before 'return res' in the filter I see the value I expect '123::', however the value in the input it's self is still just 123.
You need create directive to change your ngModel, like this:
.directive('phoneFormat', function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ctrl) {
var setvalue = function() {
elem.val(ctrl.$modelValue + "::");
};
ctrl.$parsers.push(function(v) {
return v.replace(/::/, '');
})
ctrl.$render = function() {
setvalue();
}
elem.bind('change', function() {
setvalue();
})
}
};
});
Use in html:
<input name='phone' ng-model='contact.phone' placeholder='(Area code) Phone' required phone-format />
JS Fiddle: http://jsfiddle.net/57czd36L/1/
Your usage of ngBind on the input is not quite correct. From the documentation,
The ngBind attribute tells Angular to replace the text content of the specified HTML element with the value of a given expression, and to update the text content when the value of that expression changes
You do not need to replace the text content of the <input> element, that wouldn't make sense. You can instead extend the formatter pipeline of the NgModelController using a directive like
app.directive('phoneFormat', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
ngModel.$formatters.push(function (value) {
if (value)
return value + '::';
});
}
}
});
Then, in your HTML,
<input ng-model='home.contact.phone' phone-format />
In case you wanted to keep the filter you wrote (for other usages), you can actually re-use it in the directive like
app.directive('phoneFormat', [ '$filter', function ($filter) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
ngModel.$formatters.push($filter('phone'));
}
}
}]);
$filter('phone') simply returns the filter function registered under 'phone'. Here is a Plunker.
Note, this solution will only format data when you change the $modelValue of the NgModelController, for example like
$scope.$apply('home.contact.phone = "123-456-7890"');
If you are looking for something to update/format the value of the input as the user is typing, this is a more complicated task. I recommend using something like angular-ui/ui-mask.
Although a filter module is a good approach, I use an 'A' directive to do the dirty work because changing the element value will affect its ng-model.
However, I would only suggest this kind of solution if your actual data manipulation could sum in 3-4 lines of code; otherwise, a more thorough approach is needed.
This is an example that will delete anything which isn't an integer:
(function () {
'use strict'
angular.module('Allay').directive('phoneValidator', function () {
return {
restrict: 'A',
link: function(scope, element, attrs) {
angular.element(element).on('keyup', function() {
element.val(element.val().replace(/[^0-9\.]/, ''));
});
}
}
});
})();
And than in your HTML template :
<input name="phone" ng-model="home.contact.phone" placeholder="(Area code) Phone" phoneValidator required/>`
You should remove your "ng-bind" cause you are filtering it and what is presented is what in the ng-model. use value instead.
<input name='phone' ng-model='home.contact.phone | phone' value="{{contact.phone | phone}}" />
see working example: JsFiddle

Compile custom directives

I'm trying to add dynamically a custom validation directive inside other custom directive. It works fine for system angular directive like "required", but not work for custom validate directive.
I have directive 'controlInput' with input, on which i dynamically add directive 'testValidation' (in real application in dependance from data of control-input).
<control-input control-data='var1'></control-input>
Directives:
app.directive('controlInput', function ($compile) {
return {
restrict: 'E',
replace: true,
template: '<div><input type="text" ng-model="var1"></div>',
link: function (scope, elem, attrs) {
var input = elem.find('input');
input.attr('required', true);
input.attr('test-validation', true);
$compile(elem.contents())(scope);
}
};
});
app.directive('testValidation', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ctrl) {
ctrl.$parsers.unshift(function (value) {
if (value) {
var valid = value.match(/^test$/);
ctrl.$setValidity('invalidTest', valid);
}
return valid ? value : undefined;
});
}
};
});
Full example http://plnkr.co/edit/FylMfTugHrotEMSQyTfT?p=preview
In this example I also add simple input to be sure 'testValidation' directive is working.
Thanks for any answers!
EDIT:
I suggest you fix your original program by changing the template in the controlInput directive to:
template: '<div><input type="text" testdir required ng-model="var1"></div>'
I don't see why not do it as mentioned above, but another way would be to replace the input with a new compiled one:
input.replaceWith($compile(elem.html())(scope));
NOTE:
Change
var valid = value.match(/^test$/);
To
var valid = /^test$/.test(value);
From MDN:
String.prototype.match()
Return value
array An Array containing the matched results or null if there were no
matches.
RegExp.prototype.test() returns what you need, a boolean value.

Access ng-model inside jQuery control

I have created an AngularJS directive using the bootstrap-datepicker which is based on jQuery and I want some stuff based on the ng-model which I'm sending to the directive. The problem is that on the datepicker init click events I don't have access to anything angular related. There is no scope, no ngModel nothing. All of these are undefined. Now, I might be missing something obvious, but I don't know what this is. Here is the code for the directive:
.directive('jqCalendar', function () {
return {
restrict: 'EA',
require: 'ngModel',
template: '<div></div>',
replace: true,
scope:{
ngModel: '='
},
link: function (scope, element, attrs, ngModelCtrl) {
var ngModel = scope.ngModel;
element.datepicker({
multidate: true,
beforeShowDay: function (date) {
var today = new Date();
var angularModel = ngModel; //ngModel is always undefined in here, why?!
if (date < today) return false;
},
format: 'dd MM yyyy',
weekStart: 1
})
.on('changeDate', function (e) {
//make server call for selected/deselected date
alert('asd');
});
}
}
})
Outside the element.datepicker({...}) I can see the value of my model, but once inside the said control, I cannot access anything. Any ideas?

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.

How do I get my directive to only fire on onchange?

I've defined a directive like so:
angular.module('MyModule', [])
.directive('datePicker', function($filter) {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ctrl) {
ctrl.$formatters.unshift(function(modelValue) {
console.log('formatting',modelValue,scope,elem,attrs,ctrl);
return $filter('date')(modelValue, 'MM/dd/yyyy');
});
ctrl.$parsers.unshift(function(viewValue) {
console.log('parsing',viewValue);
var date = new Date(viewValue);
return isNaN(date) ? '' : date;
});
}
}
});
The parser seems to fire every time I type a key in my textbox though -- what exactly is the default event, is it keyup, or input? And how do I change it to only fire onchange? It really isn't necessary to fire anymore often than that.
Furthermore, I'm actually manipulating the content of this input using jQuery UI's datepicker. When clicking on the calendar it doesn't seem to trigger the appropriate event that causes the model to be updated/parser to be triggered. I think I can force an event to be fired but I need to know which one.
Trying to use scope.$apply() but that doesn't seem to help any:
.directive('datepicker', function($filter) {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ctrl) {
$(elem).datepicker({
onSelect: function(dateText, inst) {
console.log(dateText, inst);
scope.$apply();
}
});
ctrl.$formatters.unshift(function(modelValue) {
console.log('formatting',modelValue);
return $filter('date')(modelValue, attrs.datePicker || 'MM/dd/yyyy');
});
ctrl.$parsers.unshift(function(viewValue) {
console.log('parsing',viewValue);
return new Date(viewValue);
});
}
}
})
I don't think the solution given here works for me because (a) I want to use the datepicker attribute value for choosing a date format or other options, but more importantly, (b) it seems to be passing back a string to the model when I want an actual date object... so some form of parsing has to be done and applied to the ng-model.
Here I created a mo-change-proxy directive, It works with ng-model and it updates proxy variable only on change.
In this demo I have even included improved directive for date-input. Have a look.
Demo: http://plnkr.co/edit/DBs4jX9alyCZXt3LaLnF?p=preview
angModule.directive('moChangeProxy', function ($parse) {
return {
require:'^ngModel',
restrict:'A',
link:function (scope, elm, attrs, ctrl) {
var proxyExp = attrs.moChangeProxy;
var modelExp = attrs.ngModel;
scope.$watch(proxyExp, function (nVal) {
if (nVal != ctrl.$modelValue)
$parse(modelExp).assign(scope, nVal);
});
elm.bind('blur', function () {
var proxyVal = scope.$eval(proxyExp);
if(ctrl.$modelValue != proxyVal) {
scope.$apply(function(){
$parse(proxyExp).assign(scope, ctrl.$modelValue);
});
}
});
}
};
});

Resources