Change the ngmodel value in Angularjsdatepicker directive - angularjs

I have tried to convert a bootstrap datepicker from eternicode to angularjs directive and allow to select week mode. The problem is the value {{var1}} from the view doesn't reflect to the custom value that i set on Directive
weekDatePicker directive:
var app = angular.module('angular.controls', [])
app.directive('weekDatePicker', ['$filter','$parse', function ($filter, $parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModelCtrl) {
console.log('start');
element.datepicker({
//minViewMode: 1,
//format: "MM-yyyy"
}).on('changeDate', function (e) {
scope.$apply(function () {
var date = e.date;// element.datepicker('getDate');
startDate = new Date(date.getFullYear(), date.getMonth(), date.getDate() - date.getDay());
endDate = new Date(date.getFullYear(), date.getMonth(), date.getDate() - date.getDay() + 6);
startDate = $filter('date')(startDate, 'dd-MM-yyyy');
endDate = $filter('date')(endDate, 'dd-MM-yyyy');
dateText = startDate + ' - ' + endDate;
e.date = dateText;
//console.log(e.date);
ngModelCtrl.$setViewValue(dateText); //>> no change from view
console.log(ngModelCtrl);
});
});
}
};
}]);
You can see the whole code at jsFiddle
View: <input week-date-picker ng-model="var1" class="form-control" />
{{var1}} >> why it doesn't get the custom value from directive ?

I have forked you fiddle
var app = angular.module('angular.controls', [])
app.directive('weekDatePicker', ['$filter','$parse', function ($filter, $parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModelCtrl) {
console.log('start');
var ngModelParseFn = $parse(attrs.ngModel);
element.datepicker({
//minViewMode: 1,
//format: "MM-yyyy",
// language: "vi"
})
scope.$watch(function () {
return ngModelCtrl.$modelValue;
}, function (newValue) {
var date = element.data().datepicker.viewDate;
startDate = new Date(date.getFullYear(), date.getMonth(), date.getDate() - date.getDay());
endDate = new Date(date.getFullYear(), date.getMonth(), date.getDate() - date.getDay() + 6);
startDate = $filter('date')(startDate, 'dd-MM-yyyy');
endDate = $filter('date')(endDate, 'dd-MM-yyyy');
dateText = startDate + ' - ' + endDate;
ngModelParseFn.assign(scope, dateText);
});
}
};
}]);
Hope this is what you want.

Related

Remove $watch from this directive

I've wrote this directive to handle my date inputs: https://plnkr.co/edit/7hpc8u5pVc7iaNSwn7Zw?p=preview
app.directive('myDate', ['$filter', myDate]);
function myDate($filter) {
var directive = {
restrict: 'E',
template: template,
require: 'ngModel',
scope: {},
link: link
}
return directive;
function template(element, attrs) {
var template = '<input ng-model="date" ng-keyup="keyup($event.keyCode)" ui-mask="99/99/9999" type="text" ';
if (attrs.class) {
template += 'class="' + attrs.class + '"';
element.removeClass(attrs.class);
}
template += '/>';
return template;
}
function link(scope, element, attrs, ctrl) {
scope.keyup = function(key) {
if (key === 68) { // D key
scope.date = $filter('date')(new Date(), 'ddMMyyyy');
}
};
ctrl.$formatters.push(function(data) { // model to view
data = $filter('date')(data, 'ddMMyyyy');
return data;
});
ctrl.$parsers.push(function(data) { // view to model
var year = data.toString().substr(-4);
var month = data.toString().substr(2, 2);
var day = data.toString().substr(0, 2);
var sep = '-';
data = (year && month && day) ? Date.parse(year + sep + month + sep + day) : '';
return data;
});
scope.$watch('date', function() {
ctrl.$setViewValue(scope.date);
});
ctrl.$render = function() {
scope.date = ctrl.$viewValue;
};
}
}
Unfortunately I used $watch to keep my model updated... I would like to know if there is a better way to trigger $setViewValue(scope.date) without $watch; just to optimize it a bit.
Thank you!
Add this to your template:
ng-change="updateParent()"
And this to you link:
scope.updateParent = function(){
ctrl.$setViewValue(scope.date);
}

Directive updates parent scope value

I use bootstrap-datepicker for its ability to select a range of days. I try to put it in Angular directive and expect to update the parent scope value when the date changed, and the date format must be in the whole week (20-04-2015 - 26-04-2015)
var app = angular.module('angular.controls.weekDatePicker', [])
app.directive('weekDatePicker', ['$filter', '$parse', function ($filter, $parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModelCtrl) {
var ngModelParseFn = $parse(attrs.ngModel);
element.datepicker({
minViewMode: parseInt(attrs.minviewmode),
format: attrs.format,
language: "vi"
}).on('changeDate', function (e) {
scope.$apply(function () {
console.log(scope.$parent);
if (attrs.week == 'true') {
}
else {
// code
}
});
});
element.datepicker('update', new Date()); //reset to now
if (attrs.week == 'true') {
scope.$watch(function () {
return ngModelCtrl.$modelValue;
}, function (newValue) {
var date = element.data().datepicker.viewDate;
var startDate = new Date(date.getFullYear(), date.getMonth(), date.getDate() - date.getDay());
var endDate = new Date(date.getFullYear(), date.getMonth(), date.getDate() - date.getDay() + 6);
startDate = $filter('date')(startDate, 'dd-MM-yyyy');
endDate = $filter('date')(endDate, 'dd-MM-yyyy');
console.log(scope.$parent.fixtureDate); //fail to get parent scope value ?
var dateText = startDate + ' - ' + endDate;
ngModelParseFn.assign(scope, dateText);
});
}
scope.$on("$destroy",
function handleDestroyEvent() {
element.datepicker('remove');
}
);
}
};
}]);
View:
<input week-date-picker ng-model="fixtureDate" minviewmode="0" format="MM-yyyy" class="form-control" week="true" style="width:200px" />
Plunker source here
I've done a version with a callback (Plunker).
JS
element.datepicker({
minViewMode: parseInt(attrs.minviewmode),
format: attrs.format,
language: "vi"
}).on('changeDate', function(e) {
var date = e.date;
var firstDay = new Date(date.getFullYear(), date.getMonth(), 1); // 0 = january
var lastDay = new Date(date.getFullYear(), date.getMonth() + 1, 0);
var startDate = firstDay.getDate() + '-' + (firstDay.getMonth() + 1) + '-' + firstDay.getFullYear();
var endDate = lastDay.getDate() + '-' + (lastDay.getMonth() + 1) + '-' + lastDay.getFullYear();
scope.changeDate({startDate: startDate, endDate: endDate});
});
Note this in the directive;
scope: {changeDate: "&"},
index.html
$scope.fixtureDate = {
startDate: startDate,
endDate: endDate
};
$scope.updateFixtures = function(startDate, endDate) {
$scope.fixtureDate.startDate = startDate;
$scope.fixtureDate.endDate = endDate;
};
You should change the html to
<input week-date-picker ng-model="fixtureDate" minviewmode="0" format="MM-yyyy" class="form-control" week="true" style="width:200px" />
You have no variable called dt in your code. The variable that you want to change is called fixtureDate.

Angular input validation with optional max min always invalid

I am building a directive that adds some logic to the input['date'] element. This is what I have right now:
app.directive('calendarInput', function() {
'use strict';
return {
template : '<input type="date"' +
'ng-model="calendarInput"' +
'min="{{min}}"' +
'max="{{max}}" />'
replace: true,
scope: {
startdate : '#',
enddate : '#',
calendarInput: '='
},
restrict: 'A',
link: function (scope, element, attrs) {
scope.$watch('startdate', function (val) {
if (val) {
var date = new Date(val);
scope.min = date.toIsoString().split('T')[0];
}
});
scope.$watch('enddate', function (val) {
if (val) {
var date = new Date(val);
scope.max = date.toIsoString().split('T')[0];
}
});
}
};
});
The idea is to reuse this directive. Sometimes there will be a startdate only, sometimes a enddate only. Like so:
<div calendar-input="item.birthday" enddate="'1990-01-01'"></div>
Unfortunately this always results in an invalid form with the class ng-invalid-min. It's because I don't supply the startdate parameter.
How can I make min or max values optional?
Edit:
I am using Angular 1.3.9
Figured out a way to compile max and min attributes on demand.
app.directive('calendarInput', function($compile) {
'use strict';
return {
template : '<input type="date" class="datepicker" ng-model="omdCalendarInput" />',
replace: true,
scope: {
startdate : '#',
enddate : '#',
calendarInput: '='
},
restrict: 'CA',
link: function (scope, element, attrs) {
var dateInput = element.find('input') // <----
scope.$watch('startdate', function (val) {
if (val) {
var date = new Date(val);
scope.min = date.toIsoString().split('T')[0];
if (!dateInput.attr('min')) { // <----
dateInput.attr('min', '{{min}}'); // <----
$compile(dateInput)(scope); // <----
}
}
});
scope.$watch('enddate', function (val) {
if (val) {
var date = new Date(val);
scope.max = date.toIsoString().split('T')[0];
if (!dateInput.attr('max')) { // <----
dateInput.attr('max', '{{max}}'); // <----
$compile(dateInput)(scope); // <----
}
}
});
}
};
});
Relevant Link
EDIT: Improved the code above. Also I'm marking this as the correct answer, since nobody else has a solution.
This is great, just what I needed. Thank you.
Nit-pick - might want to change:
scope.min = date.toIsoString().split('T')[0];
to
scope.min = $format('date')(date, 'yyyy-MM-dd');
to make it more angularish.

ngModelController: update view value after parser executed

I have the following directive:
myDirectives.directive('simpleDate', function ($filter, $locale, $timeout) {
return {
require: 'ngModel',
restrict: 'A',
link: function (scope, elem, attrs, ctrl) {
var dateFormat = 'shortDate';
ctrl.$formatters.unshift(function (modelValue) {
if (!modelValue) {
return '';
} else {
return $filter('date')(modelValue, dateFormat);
}
});
ctrl.$parsers.unshift(function (viewValue) {
if (!isNaN(viewValue)) {
var now = new Date(),
year = now.getFullYear(),
month = now.getMonth() + 1,
day = Number(viewValue),
lastDay = (new Date(year, month, 0)).getDate();
if (day >= 1 && day <= lastDay) {
return '' + year + (month < 10 ? '-0' : '-') +
month + (day < 10 ? '-0' : '-') + day;
}
}
return '';
});
}
};
});
Parser calculates model value, and needs to reflect back view value; but how? I know there is ctrl.$render() method, but I guess it must run after $parser executed. The following didn't work:
$timeout(function () { ctrl.$render(); });
How can I render view value then?
Is this what you expect? :
myDirectives.directive('simpleDate', function ($filter, $locale, $timeout) {
return {
require: 'ngModel',
restrict: 'A',
link: function (scope, elem, attrs, ctrl) {
var dateFormat = 'shortDate';
ctrl.$formatters.unshift(function (modelValue) {
if (!modelValue) {
return '';
} else {
return $filter('date')(modelValue, dateFormat);
}
});
ctrl.$parsers.unshift(function (viewValue) {
if (!isNaN(viewValue)) {
var now = new Date(),
year = now.getFullYear(),
month = now.getMonth() + 1,
day = Number(viewValue),
lastDay = (new Date(year, month, 0)).getDate();
if (day >= 1 && day <= lastDay) {
var currentValue = '' + year + (month < 10 ? '-0' : '-') + month + (day < 10 ? '-0' : '-') + day;
ctrl.$setViewValue(currentValue);
ctrl.$render();
return currentValue;
}
}
return '';
});
}
};
});

ui bootstrap datepicker with unix timestamps

I am trying to use ui-bootstrap datepicker binded to unix timestamp datas.
to do so, i would like to use this directive which transforms the unix timestamp to a javascript date from ng-model.
here is the code (plunker)
<div ng-model="date" date-format>
<datepicker min="minDate" show-weeks="false"></datepicker>
</div>
and the directive
.directive('dateFormat', function() {
return {
require: 'ngModel',
link: function(scope, element, attr, ngModelCtrl) {
ngModelCtrl.$formatters.unshift(function(timestamp) {
if (timestamp) return new Date( timestamp * 1000 );
else return "";
});
ngModelCtrl.$parsers.push(function(date) {
if (date instanceof Date) return Math.floor( date.getTime() / 1000 );
else return "";
});
}
};
})
it's possible to select a date. The unix timestamp is correct but then, the calendar switches to 1970…
is there a solution to make this work and use ui bootstrap's datepicker with unix timestamp datas ?
It's kinda late, but I also had this problem - I don't want to keep additional value in my model just for datepicker/timepicker. Followed this great tutorial on ngModelController by Christopher Nadeau
This works in AngularJS 1.2.16:
.directive('timestampFormat', function() {
// Directive that converts timestamp back and forth from
// seconds to Date object
return {
scope: true, // isolated scope
require: 'ngModel',
link: function(scope, element, attr, ngModelCtrl) {
ngModelCtrl.$formatters.push(function (modelValue) {
// returns $viewValue
return {
timestamp: (modelValue ? new Date(modelValue*1000) : "")
};
});
scope.$watch('timestamp', function () {
ngModelCtrl.$setViewValue({ timestamp: scope.timestamp });
});
ngModelCtrl.$parsers.push(function (viewValue) {
// returns $modelValue
if (viewValue.timestamp instanceof Date) return Math.floor( viewValue.timestamp.getTime() / 1000 );
else return "";
});
ngModelCtrl.$render = function () {
// renders timestamp to the view.
if (!ngModelCtrl.$viewValue) ngModelCtrl.$viewValue = { timestamp: ""};
scope.timestamp = ngModelCtrl.$viewValue.timestamp;
};
}
};
});
Now in view you can access it as a timestamp variable.
<div ng-model="yourModelValue" timestamp-format>
{{timestamp}}
<timepicker ng-model="timestamp" ...>
your don't need multiplied and divided
link: function(scope, element, attr, ngModelCtrl) {
ngModelCtrl.$formatters.unshift(function(timestamp) {
if (timestamp) {
var date = new Date( timestamp );
console.log('timestamp to date: ', date);
return date;
} else return "";
});
ngModelCtrl.$parsers.push(function(date) {
if (date instanceof Date) {
timestamp = date.getTime();
console.log('date to timestamp: ', timestamp);
return timestamp;
} else return "";
});
}

Resources