I am trying to wrap the bootstrap datepicker with an angular directive
in addition to the common datepicker this datepicker contains a different dateformat for display and different date format for the ng-model.
and getting an error of :
Error: [$rootScope:inprog] $digest already in progress
my code is pasted below:
app.directive('ngDatePicker', function () {
return {//this datePicker will show the date in the wanted format and save it (ng-model) with the default format of yy-mm-dd
template: "<span><input id='displayDate' class={{class}} type='text'/><input id='hiddenDate' type='text' style='display:none'/></span>",
require: 'ngModel',
replace: true,
scope: {
ngModel: '=',
class: '#'
},
link: function (scope, element, attrs, ngModelCtrl) {
// set date pickers - this datePicker
var displayInput = element.find('#displayDate');
var hiddenInput = element.find('#hiddenDate');
var options = {};
options.minDate = new Date();
options.format = "yyyy-MM-dd";
options.autoclose = 'true';
displayInput.datepicker(options);
displayInput.datepicker().on('changeDate', function () {
//update the ng-model
var changedDate = displayInput.datepicker("getDate");
var newDate = converDateToDefault(changedDate);
if (scope.ngModel != newDate) {
scope.$apply(function () {
hiddenInput.val = newDate;
scope.ngModel = newDate;
});
}
});
scope.$watch("ngModel", function (newVal) {
if (typeof newVal !== undefined && newVal != null && newVal != "") {
displayInput.datepicker('destroy');
var option = {};
option.format = "yyyy-MM-dd";
displayInput.datepicker(option);
var convertedVal = converDateToDefault(displayInput.datepicker("getDate"));
if (convertedVal != "Invalid date" && newVal != convertedVal) {
displayInput.datepicker("setDate", convertDate(newVal));
hiddenInput.val = newVal;
}
}
}, true);
}
};
});
Thanks
Why reinvent the wheel? Use ui-bootstrap directive datepicker - http://angular-ui.github.io/bootstrap/#/datepicker
btw your original problem is because of
scope.$apply(function () {
hiddenInput.val = newDate;
scope.ngModel = newDate;
});
you can solve it by wrapping the scope.$apply
if (scope.$root.$$phase != '$apply' && scope.$root.$$phase != '$digest') {
scope.$apply(function () {
hiddenInput.val = newDate;
scope.ngModel = newDate;
});
}
Related
Im using Angular 1.5.6.
I have a directive for checking for a double click:
angular.module('redMatter.analyse')
.directive('iosDblclick',
function () {
var DblClickInterval = 300; //milliseconds
var firstClickTime;
var waitingSecondClick = false;
return {
restrict: 'A',
link: function (scope, element, attrs) {
element.bind('click', function (e) {
if (!waitingSecondClick) {
firstClickTime = (new Date()).getTime();
waitingSecondClick = true;
setTimeout(function () {
waitingSecondClick = false;
}, DblClickInterval);
}
else {
waitingSecondClick = false;
var time = (new Date()).getTime();
if (time - firstClickTime < DblClickInterval) {
scope.$apply(attrs.iosDblclick);
}
}
});
}
};
});
I use this here:
<div ios-dblclick="onDoubleClick($event, graph)" ></div>
graph is an object inside an ng-repeat directive. In onDoubleClick, I need access to $event:
$scope.onDoubleClick = function($event, graph){
console.log('in onDoubleClick and arguments are ', arguments);
var element = $event.srcElement;
However I'm unsure of how to pass the event from the directive to onDoubleClick. In the console log, arguments prints out as:
[undefined, Object]
Where Object is graph. How can I also pass back the event?
Since you could pass locals with $eval method, consider using it while calling attrs.iosDblclick. Internally it uses $parse API to evaluate method and uses local as a parameter.
scope.$eval(attrs.iosDblclick, {$event: e});
Plunker Demo
See also
Custom ng-enter directive not passing $event from html to the controller
UPDATED
http://jsfiddle.net/ADukg/14470/ - working example.
So, you could pass the function to directive like this:
<div ios-dblclick="onDoubleClick" ios-dblclick-arg="graf" ></div>
inside your directive:
return {
restrict: 'A',
scope: {
myCallback: '=iosDblclick',
graph: '=iosDblclickArg'
},
link: function (scope, element, attrs) {
element.bind('click', function (e) {
if (!waitingSecondClick) {
firstClickTime = (new Date()).getTime();
waitingSecondClick = true;
setTimeout(function () {
waitingSecondClick = false;
}, DblClickInterval);
}
else {
waitingSecondClick = false;
var time = (new Date()).getTime();
if (time - firstClickTime < DblClickInterval) {
scope.myCallback(e, scope.graph)
}
}
});
}
}
You can pass callback to directive as: onDoubleClick: '&' - isolate scope
webApp.directive('iosDblclick',
function () {
var DblClickInterval = 300; //milliseconds
var firstClickTime;
var waitingSecondClick = false;
return {
restrict: 'A',
scope: {
onDoubleClick: '&'
},
link: function (scope, element, attrs) {
element.bind('click', function (e) {
if (!waitingSecondClick) {
firstClickTime = (new Date()).getTime();
waitingSecondClick = true;
setTimeout(function () {
waitingSecondClick = false;
}, DblClickInterval);
}
else {
waitingSecondClick = false;
var time = (new Date()).getTime();
if (time - firstClickTime < DblClickInterval) {
scope.onDoubleClick({data: {e:e, val: "someValue"}});
}
}
});
}
};
});
Where HTML is:
<div ios-dblclick on-double-click="onDoubleClick(data)" ></div>
And controller:
$scope.onDoubleClick = function(data){
var element = data.e.srcElement;
}
Plunker Demo
I am new to angular. I am working in uib time picker, it doesn't accept the string time.
For example, I have time from json like "06:00". I need 24hrs and 12hrs format also. The problem is I am creating the directive for display time in ng-model, and I have a text input also. Suppose if I enter the value in input text, example 1111, it is needed to be formatted to 11:11
Some other examples: 183 - 18:03 , 18349 - 18:34:09 like this.
Please some one help me to solve this problem.
dateTimeMask.directive('timeMask', function($filter) {
return {
restrict: 'A',
priority: 1,
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
var timeFormat;
function formatter(value) {
var formattedValueLength = timeFormat.length;
var unformattedValueLength = timeFormat.replace(':', '').length;
var timeMask = new String(timeFormat);
if (ngModel.$isEmpty(value)) {
return value;
}
if (!value) {
ngModel.$setValidity('time', true);
return null;
} else if (angular.isDate(value) && !isNaN(value)) {
ngModel.$setValidity('time', true);
return value;
}
else if (angular.isString(value)) {
var timeRegex = /^(0?[0-9]|1[0-2]):[0-5][0-9] ?[a|p]m$/i;
if (!scope.showMeridian) {
timeRegex = /^([01]?[0-9]|2[0-3]):[0-5][0-9]$/;
}
if (!timeRegex.test(value)) {
ngModel.$setValidity('time', false);
return undefined;
}
else {
ngModel.$setValidity('time', true);
var date = new Date();
var sp = value.split(":");
var apm = sp[1].match(/[a|p]m/i);
if (apm) {
sp[1] = sp[1].replace(/[a|p]m/i, '');
if (apm[0].toLowerCase() == 'pm') {
sp[0] = sp[0] + 12;
}
}
date.setHours(sp[0], sp[1]);
return date;
}
}
else {
ngModel.$setValidity('time', false);
return undefined;
}
}
var showTime = function(data) {
timeFormat = (!scope.showMeridian) ? "HH:mm" : "hh:mm a";
return $filter('date')(formatter(data), timeFormat);
};
scope.$watch('showMeridian', function(value) {
var myTime = ngModel.$modelValue;
if (myTime) {
element.val(showTime(myTime));
}
});
ngModel.$formatters.push(function (value) {
timeFormat = (!scope.showMeridian) ? "HH:mm" : "hh:mm a";
var date = formatter(value);
return date;
});
ngModel.$parsers.push(function (value) {
var date = showTime(value);
return date;
});
} //link function
};
});
Here is my html code which will display the time in pop up i need to update both text and popup values please some one to help me to solve this problem.
I need to get visible dates from the Datepicker if mode is day.
Example:
In this case I need to get these 42 days. Also if user change month, I should refresh the Datepicker controller view and get new 42 days.
So i managed to fix this.
We need to extend uibDatepickerDirective
angular.module('ui.bootstrap.datepicker')
.config(function ($provide) {
$provide.decorator('uibDatepickerDirective', function ($delegate, $timeout) {
var directive = $delegate[0];
var link = directive.link;
angular.extend(directive.scope, {
visibleDates: '=?'
});
directive.compile = function () {
return function (scope, element, attrs, ctrls) {
link.apply(this, arguments);
var datepickerCtrl = ctrls[0];
datepickerCtrl.getVisibleDates = function () {
var year = this.activeDate.getFullYear(),
month = this.activeDate.getMonth(),
firstDayOfMonth = new Date(this.activeDate);
firstDayOfMonth.setFullYear(year, month, 1);
var difference = this.startingDay - firstDayOfMonth.getDay(),
numDisplayedFromPreviousMonth = difference > 0 ?
7 - difference : -difference,
firstDate = new Date(firstDayOfMonth);
if (numDisplayedFromPreviousMonth > 0) {
firstDate.setDate(-numDisplayedFromPreviousMonth + 1);
}
return this.getDates(firstDate, 42);;
}
var firstTime = true;
$timeout(function () {
scope.$watch("activeDt", function () {
var newValues = datepickerCtrl.getVisibleDates();
if (firstTime) {
scope.visibleDates = newValues;
firstTime = false;
return;
}
if (newValues[0].getYear() !== scope.visibleDates[0].getYear() ||
newValues[0].getMonth() !== scope.visibleDates[0].getMonth() ||
newValues[0].getDate() !== scope.visibleDates[0].getDate()) {
scope.visibleDates = newValues;
}
});
});
}
};
return $delegate;
});
});
And in directive itself we need to pass atribute visible-dates and point to the variable where we want to save these 42 days.
<span uib-datepicker visible-dates="visibleDates" datepicker- ng-model="datePicked"></span>
This way we will update visibleDates (42 of them) if we change month in datepicker, but if we change activeDate (scope.activeDt) on the same month (same visible dates), it will stay unchanged.
Hi I have a situation here,
I have created a dual input directive. Can you please help to in the below scenario.
When I change the model value via controller to undefined, the view values are not cleared. Here is the Codes,
My Dual Input Directive is as follows,
angular.module("awcQuoteBuy.directives")
.directive('dualInput', function($timeout, inputValidationService) {
return {
restrict: 'E',
templateUrl: 'app/partials/common/doubleInput.html',
scope: {
modelValue: '=',
size: '#',
fieldError: '#',
blurFn: '&loseFocus'
},
link: function postLink(scope, element, attrs, ctrl) {
scope.leftFocused = false;
scope.rightFocused = false;
scope.$watch('left', function(newVal, oldVal) {
if (newVal!==oldVal) {
var tmp = (newVal) ? inputValidationService.formatNumber(newVal+'') : '';
scope.modelValue = tmp + '|'+ scope.getOtherValue(scope.right);
}
});
scope.$watch('right', function(newVal, oldVal) {
if (newVal!==oldVal) {
var tmp = (newVal) ? inputValidationService.formatNumber(newVal+'') : '';
scope.modelValue = scope.getOtherValue(scope.left) + '|' + tmp;
}
});
scope.getOtherValue = function(value) {
return (value && value!=='') ? inputValidationService.formatNumber(value+'') : '';
};
//will set the value initially
$timeout(function() {
if (!scope.modelValue || scope.modelValue===null) {
scope.modelValue = '';
}
if (scope.modelValue!=='') {
var splitIndex = scope.modelValue.indexOf('|');
if (splitIndex>=0) {
var values = scope.modelValue.split('|');
scope.left = values[0];
scope.right = values[1];
} else {
scope.left = scope.modelValue;
}
}
});
/*
Below functions will add on-blur (lose focus) support to both fields.
*/
scope.focusLeft = function() {
scope.leftFocused = true;
};
scope.blurLeft = function() {
scope.leftFocused = false;
$timeout(function() {
if (!scope.rightFocused) {
scope.blurFn();
}
}, 100);
};
scope.focusRight = function() {
scope.rightFocused = true;
};
scope.blurRight = function() {
scope.rightFocused = false;
$timeout(function() {
if (!scope.leftFocused) {
scope.blurFn();
}
}, 100);
};
}
};
});
The HTML Piece of code is as follows,
<dual-input model-value="dualInput[$index]" ng-switch-when="DUAL_NUMBER" size="{{q.length}}"
field-error="{{q.invalid || (nextClicked && !validateGeneralQuestion(acc.memberId, q))}}" id="{{'gQDual'+$index}}"
lose-focus="saveGeneralAnswer(acc.memberId, q)"></dual-input>
In My Controller when I set the scope value to undefined or null, the entered values in the view is not cleared. Please help me here what I should do to clear this value
$scope.dualInput[$index]=undefined;
The directive itself has got the auto initialize feature. So I had to re render the directive whenever I wanted to reinitialize.
If you want to explicitly update user ctrl.$setViewvalue = ""
I am new to angularjs the below directive is to support decimal and commas, it works fine when a change is made to the field how ever the data in the fields are not validated when the page loads
var app = angular.module('myApply.directives', [], function () {
});
app.directive('numericDecimalInput', function($filter, $browser, $locale,$rootScope) {
return {
require: 'ngModel',
priority: 1,
link: function($scope, $element, $attrs, ngModelCtrl) {
var replaceRegex = new RegExp($locale.NUMBER_FORMATS.GROUP_SEP, 'g');
var fraction = $attrs.fraction || 0;
var listener = function() {
var value = $element.val().replace(replaceRegex, '');
$element.val($filter('number')(value, fraction));
};
var validator=function(viewValue) {
ngModelCtrl.$setValidity('outOfMax', true);
ngModelCtrl.$setValidity(ngModelCtrl.$name+'Numeric', true);
ngModelCtrl.$setValidity('rangeValid', true);
if(!_.isUndefined(viewValue))
{
var newVal = viewValue.replace(replaceRegex, '');
var newValAsNumber = newVal * 1;
// check if new value is numeric, and set control validity
if (isNaN(newValAsNumber)) {
ngModelCtrl.$setValidity(ngModelCtrl.$name + 'Numeric', false);
} else
{
if (newVal < 0) {
ngModelCtrl.$setValidity(ngModelCtrl.$name + 'Numeric', false);
}
else {
newVal = newValAsNumber.toFixed(fraction);
ngModelCtrl.$setValidity(ngModelCtrl.$name + 'Numeric', true);
if (!(_.isNull($attrs.maxamt) || _.isUndefined($attrs.maxamt))) {
var maxAmtValue = Number($attrs.maxamt) || Number($scope.$eval($attrs.maxamt));
if (newVal > maxAmtValue) {
ngModelCtrl.$setValidity('outOfMax', false);
} else {
ngModelCtrl.$setValidity('outOfMax', true);
}
if(!(_.isNull($attrs.minamt) || _.isUndefined($attrs.minamt)))
{
var minAmtValue = Number($attrs.minamt)|| Number($scope.$eval($attrs.minamt));
if((newVal > maxAmtValue) || (newVal < minAmtValue)){
ngModelCtrl.$setValidity('rangeValid', false);
}
else
{
ngModelCtrl.$setValidity('rangeValid', true);
}
}
}
else if((!(_.isNull($attrs.minamt) || _.isUndefined($attrs.minamt))))
{
var minAmtValue = Number($attrs.minamt)|| Number($scope.$eval($attrs.minamt));
if(newVal < minAmtValue)
{
ngModelCtrl.$setValidity('outOfMin', false);
}
else
{
ngModelCtrl.$setValidity('outOfMin', true);
}
}
else {
ngModelCtrl.$setValidity('outOfMax', true);
}
}
}
return newVal;
}
};
// This runs when the model gets updated on the scope directly and keeps our view in sync
ngModelCtrl.$render = function() {
ngModelCtrl.$setValidity('outOfMax', true);
ngModelCtrl.$setValidity(ngModelCtrl.$name+'Numeric', true);
$element.val($filter('number')(ngModelCtrl.$viewValue, fraction));
};
$element.bind('change', listener);
$element.bind('keydown', function(event) {
var key = event.keyCode;
// If the keys include the CTRL, SHIFT, ALT, or META keys, home, end, or the arrow keys, do nothing.
// This lets us support copy and paste too
if (key == 91 || (15 < key && key < 19) || (35 <= key && key <= 40))
return;
});
$element.bind('paste cut', function() {
$browser.defer(listener);
});
ngModelCtrl.$parsers.push(validator);
ngModelCtrl.$formatters.push(validator);
}
};
});
Could some one please let me know as what I am missing .
Thanking you in advance.
Have you declared your app (doesn't show this in your code)? ie:
var app = angular.module('app', []);
Do you have ng-app in your HTML document anywhere?
<html lang="en-GB" ng-app="app">
Check out the documentation to get started with modules (Angular is very well documented, well worth reading): https://docs.angularjs.org/guide/module
app.directive('numericDecimalInput', function($filter, $browser, $locale,$rootScope) {
return {
require: 'ngModel',
priority: 1,
link: function($scope, $element, $attrs, ngModelCtrl) {
...
validator($element.val());
}
};
});