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?
Related
Plunker link of my usecase.
I am using Eternicode's Bootstrap Datepicker in my app. I have created a datepicker directive and same is used in Angular form.
Datepicker directive :
angular.module('MyApp', [])
.directive('myDatePicker', function($compile) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
$(element[0]).datepicker({
autoclose: true,
format: "dd/mm/yyyy"
});
$(element[0]).datepicker().on("change", function(e) {
scope.$apply(function() {
ngModelCtrl.$setViewValue(element.val());
});
});
}
};
});
When the Angular form is initially loaded, following are its properties:
$pristine : false
$dirty : true
When i watch the form model and print them on console, i understood form model had property that was set by datepicker and this is how it looks :
On same Plunker link , if i comment date section in form following are its properties:
$pristine : true
$dirty : false
This is how form model looks now :
How do i keep my form model free from being corrupted, which is causing problems by setting $pristine to false and $dirty to true, despite of using Date directive ?
PS : This is the abstraction of the bigger use-case that is in our current application.
Thanks in advance.
I have investigated you code and observed that, you should write you following code in compile function of directive.
$(element[0]).datepicker({
autoclose: true,
format: "dd/mm/yyyy"
});
it will resolve your problem.
so your directive looks like as follow :
.directive('myDatePicker', function($compile) {
return {
restrict: 'A',
require: 'ngModel',
compile: function(scope, element, attrs) {
$(element[0]).datepicker({
autoclose: true,
format: "dd/mm/yyyy"
});
},
link: function(scope, element, attrs, ngModelCtrl) {
$(element[0]).datepicker().on("change", function(e) {
scope.$apply(function() {
ngModelCtrl.$setViewValue(element.val());
});
});
}
};
The compile function deals with transforming the template DOM. Since most directives do not do template transformation, it is not used often.
I don't completely understand your use case, but after you set your date selection, why don't you just do a programmatic
$scope.form.$setPristine();
I believe this was introduced in v1.1
I can't seem to find a way to update my ng-model in my directive
My directive looks like this
app.directive('DatepickerChanger', function () {
return {
restrict: 'E',
require: 'ngModel',
scope: {
Model: '=',
startDate: '=',
endDate: '=',
},
templateUrl: function(elem,attrs) {
return '/AngularJS/Directives/msDatepickerTpl.html'
},
link: function (scope, element, attrs, ngModel) {
ngModel = new Date();
}
}
});
And i use it like this
<DatepickerChanger ng-model="date" required></DatepickerChanger>
Where date is a other date object
If i console.log the ng-model, i can see that it's change it's value, but can't see it from the place where i call the directive. anyone got a clue to what im doing worng
You are using it wrong. This ngModel is a controller. Your code should look like this:
link: function (scope, element, attrs, ngModel) {
ngModel.$setViewValue(new Date());
}
Also, the directive should be called datepickerChanger and referenced in html as datepicker-changer
EDIT:
You want to update the ngModel after changing the input, for example. One way to do that is use the bind function to capture the events of the input field (I cant see your template, but I imagine that will have one input):
link: function (scope, element, attrs, ngModel) {
element.find('input').bind('change', function() {
// Do something with this.value
ngModel.$setViewValue(newDate);
});
}
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 "";
});
}
}
});
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.
I'm using JQuery bootstrap datepicker (eternicode.github.io/bootstrap-datepicker/) in an angular application.
I've wrote my own directive to wrap this datepicker and do some date formatting. Datepicker works fine and display an appropriate date format.
Currently, I want to show a formatted date value and put a timestamp formatted value in ngModel.
My code is okay when I'm not trying to use template in my directive :
cosyApp.directive("datepicker", ['moment',
function(moment) {
function link($scope, element, attrs, ctrl) {
// Init JQuery datepicker
element.datepicker();
ctrl.$parsers.push(function(valueFromInput) {
// Format displayed value in timestamp format
return moment(valueFromInput).format('X');
});
}
return {
restrict: 'A',
require: 'ngModel',
link: link
};
}
]);
But when I use template attribute, ngModel and displayed value are the same :
return {
restrict: 'A',
replace: true,
require: 'ngModel',
template: "<div class='input-group date'> {{ngModel}}" +
"<input class='form-control' ng-model='ngModel'>" +
"<span class='input-group-addon'><i class='glyphicon glyphicon-calendar'></i></span>" +
"</div>",
link: link
};
I made a few changes to your plunker. I didn't try to figure out why the calendar is showing up right away, I instead just bound the datepicker to the input field.
Instead of trying to use ngModel on the input ng-mode directly as you were doing, I created an intermediate model object, then adding a function to watch for changes to the input and pass those changes to the ngModelController directly.
Side note, I believe if you plan on updating your model value outside the UI and want it to update the view, you will have to add a $watch to update the intermediate model object.
http://plnkr.co/edit/5213zUvnqyv0ARqc11aU?p=preview
cosyApp.directive("datepickerx",
function($window) {
function link($scope, element, attrs, ctrl) {
$scope.model = ctrl.$viewValue;
// Init JQuery datepicker
element.find('input').datepicker({
autoclose: true,
clearBtn: true,
});
$scope.changed = function() {
ctrl.$setViewValue($scope.model);
}
ctrl.$parsers.push(function(valueFromInput) {
// Format displayed value in timestamp format and store it to ngModel
return $window.moment(valueFromInput).format('X');
});
}
/* ********** This part of code doesn't works ********** */
return {
restrict: 'A',
replace: true,
require: 'ngModel',
scope: {
ngModel: '='
},
template: '<div class="input-group date">' +
'<input class="form-control" ng-model="model" ng-change="changed()"/>' +
'<span class="input-group-addon"><i class="glyphicon glyphicon-calendar"></i></span>' +
'</div>',
link: link
};
}
);