AngularJS - Formatting ngModel on custom datepicker directive - angularjs

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

Related

Transcluded input ng-model does not update scope variable passed to directive

I have a directive that is essentially a complicated label tag that transcludes an input element and takes the input box's ng-model as a parameter.
<div switch model="testObject.switch">
<input type="checkbox" ng-model="$parent.testObject.switch">
</div>
Prior to upgrading to angular 1.4, clicking on the directive updated the ng-model and triggered the watch inside the directive.
Now, clicking on the directive still affects the input box, but the value inside the directive is unaffected.
I would appreciate any insight into what caused this change and how to fix it.
fiddle
I have updated your fiddle with working code. If you require ngModel in your directive and watch its $modelValue, you are able to get the behavior that you are looking for.
HTML:
<div switch ng-model="testObject.switch">
Directive:
booleanSwitchModule.directive('switch', [function () {
return {
scope: {},
require: "?^ngModel",
link: function (scope, elem, attr, ngModel) {
var timesChanged = 0;
scope.$watch(function() {return ngModel.$modelValue; }, function (val) {
if (val != undefined) {
alert("model changed " + ++timesChanged + " times");
scope.switchPosition = scope.model;
}
});
},
restrict: 'EA',
replace: true,
transclude: true,
template: '<label class="switch">' +
'directive scope model: {{ngModel}}' +
'<span ng-transclude></span>' +
'</label>',
}
}]);
Here is the updated fiddle: https://jsfiddle.net/62911kx5/3/

How to use "required" attribute within Angular directive template?

I'm using an AngularJS directive to generate two radio buttons. I'm hardcoding the "required" attribute into the directive template, but it doesn't behave as expected.
When no radio buttons are checked, it correctly displays this error message, as expected.
<p ng-show="form.$invalid">Error: the form is invalid.</p>
But it doesn't display this error message.
<p ng-show="form.gender.$invalid">Error: the gender input is invalid.</p>
Any idea why?
Please see the Plunker for details:
http://plnkr.co/edit/i5kVeX8WdrUbM83lT6O6?p=preview
You need to tell your directive to validate input fields initially. You can do this by defining $formatters callback checking validity of the fields:
app.directive('dRadio', function() {
return {
require: '^form',
restrict: 'E',
scope: { model: '=ngModel' },
template: '<input required type="radio" id="{{value}}" name="{{name}}" value="{{value}}" ng-model="model"><label for="{{value}}">{{label}}</label>',
link: function(scope, element, attrs, ngModelController) {
scope.name = attrs.name;
scope.value = attrs.value;
scope.label = attrs.label;
ngModelController[attrs.name].$formatters.unshift(function(value) {
ngModelController[attrs.name].$setValidity('required', !!scope.model);
return value;
});
}
};
});
Note, that you also need to add require: '^form' rule.
Demo: http://plnkr.co/edit/rac1RNNOWHs1wTnktJHE?p=preview
You can accomplish by doing this way
Plnkr
I changed the following line
<p ng-show="!test">Error: the gender input is invalid.</p>

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?

AngularJS model not updating while typing

In the following AngularJS code, when you type stuff into the input field, I was expecting the div below the input to update with what is typed in, but it doesn't. Any reason why?:
html
<div ng-app="myApp">
<input type="text" ng-model="city" placeholder="Enter a city" />
<div ng-sparkline ng-model="city" ></div>
</div>
javascript
var app = angular.module('myApp', []);
app.directive('ngSparkline', function () {
return {
restrict: 'A',
require: '^ngModel',
template: '<div class="sparkline"><h4>Weather for {{ngModel}}</h4></div>'
}
});
http://jsfiddle.net/AndroidDev/vT6tQ/12/
Add ngModel to the scope as mentioned below -
app.directive('ngSparkline', function () {
return {
restrict: 'A',
require: '^ngModel',
scope: {
ngModel: '='
},
template: '<div class="sparkline"><h4>Weather for {{ngModel}}</h4></div>'
}
});
Updated Fiddle
It should be
template: '<div class="sparkline"><h4>Weather for {{city}}</h4></div>'
since you are binding the model to city
JSFiddle
The basic issue with this code is you aren't sharing "ngModel" with the directive (which creates a new scope). That said, this could be easier to read by using the attributes and link function. Making these changes I ended up with:
HTML
<div ng-sparkline="city" ></div>
Javascript
app.directive('ngSparkline', function ($compile) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var newElement = '<div class="sparkline"><h4>Weather for {{' + attrs.ngSparkline + '}}</h4></div>';
element.append(angular.element($compile(newElement)(scope)));
}
}
});
Using this pattern you can include any dynamic html or angular code you want in your directive and it will be compiled with the $compile service. That means you don't need to use the scope property - variables are inherited "automatically"!
Hope that helps!
See the fiddle: http://jsfiddle.net/8RVYD/1/
template: '<div class="sparkline"><h4>Weather for {{city}}</h4></div>'
the issue is that require option means that ngSparkline directive expects ngModel directive controller as its link function 4th parameter. your directive can be modified like this:
app.directive('ngSparkline', function () {
return {
restrict: 'A',
require: '^ngModel',
template: '<div class="sparkline"><h4>Weather for {{someModel}}</h4></div>',
link: function(scope, element, attrs, controller) {
controller.$render = function() {
scope.someModel = controller.$viewValue;
}
}
}
});
but this creates someModel variable in scope. that I think isn't necessary for this use case.
fiddle

Ng-transclude data from template in link

I have written the following Angular directive:
angular.module('solarquote.directives', []).directive('editfield', function() {
return {
restrict: 'A',
transclude: true,
template: '<span ng-hide="editorEnabled" ng-transclude></span>' + // viewable field
'<span ng-show="editorEnabled"><input class="input-medium" ng-model="editableField"></span>', // editable field
link: function(scope, elm, attrs, ctrl) {
scope.editorEnabled = false;
scope.editableField = elm.children[0].children[0].innerText;
}
};
})
And in the html, inside a ng-repeat:
<span editfield>{{ item.fields.name }}</span>
I would like to prepopulate the input field in the directive's template with the same content in the ng-transclude. Going through the DOM and grabbing the text yields: {{ item.fields.name }} instead of the rendered data: "Bob" (or whatever name).
What is the best way to access the transcluded data?
Thanks
It is not possible to assign to ng-model an expression that you specify in transclusion block. This is because a transclusion block can be an expression like {{ functionValue() }} or {{ field1+':'+field2 }}. Angular simply does not know how to reverse those expressions.
What you can do, is provide a reference to the model you want to update. See the following punkler http://plunker.co/edit/NeEzetsbPEwpXzCl7kI1?p=preview (needs jQuery)
directive('editfield', function() {
var template = ''+
'<span ng-show="editorEnabled"><input class="input-medium" ng-model="editfield"></span>'+
'<span ng-hide="editorEnabled" ng-transclude></span>';
return {
restrict: 'A',
template: template,
scope:{
editfield:'='
},
transclude:true,
link: function(scope, element, attrs) {
var input = element.find('input');
input.on('blur',function(){
scope.editorEnabled=false;
scope.$apply();
});
element.bind('click',function(){
scope.editorEnabled=!scope.editorEnabled;
scope.$apply();
input.focus();
})
}
};
})

Resources