Angular UI Bootstrap datepicker: ng-readonly support - angularjs

Angular UI Bootstrap datepicker does not honors ng-readonly attribute.
If ng-readonly expression is true, text input field is greyed and can not be modified, but datepicker's calendar pops up, allowing modification of form field.
So far i tryed 3 approaches (see http://plnkr.co/edit/KHrbbI6W1pWG5ewSsE9r?p=preview), both of which are rather hackish and ugly:
Disabling of all dates in datepicker if it should be readonly.
<input type="text" datepicker-popup="dd.MM.yyyy" ng-model="dt" ng-readonly="ro" date-disabled="disabled(date, mode)" />
in html file and
$scope.$watch('ro', function(ro) {
$scope.dt = new Date($scope.dt); // force datepicker div refresh
});
$scope.disabled = function(date, mode) {
return $scope.ro;
};
in controller.
Not allowing datepicker popup div to pop up.
<input type="text" datepicker-popup="dd.MM.yyyy" ng-model="dt" ng-readonly="ro" is-open="opened" />
in html file and
$scope.$watch('opened', function(v1, v2, v3) {
if ($scope.ro && v1) {
$timeout(function() {
$scope.opened = false;
});
}
});
in controller. Blinking datepicker looks terrible.
Replacing datepicker's text input with another readonly input field without datepicker attached.
<datepicker-ro-fix datepicker-popup="dd.MM.yyyy" ng-model="dt" ng-readonly="ro" />
in html file and
m.directive('datepickerRoFix', function() {
return {
restrict: 'E',
require: 'ngModel',
scope: {
ngModel: '=',
ngReadonly: '=',
},
template: '<span>'
+ '<input ng-hide="ngReadonly" type="text" datepicker-popup="dd.MM.yyyy" ng-model="ngModel" />'
+ '<input ng-show="ngReadonly" type="text" readonly="true" value="{{ ngModel | date:\'dd.MM.yyyy\'}}" />'
+ '</span>',
};
});
in js file. This seems to be the best solution so far, but the downside is that now there are two input elements instead of one, both have some hardcoded properties.
First and second approaches require me to add a bunch of code into form controller per each date input field, Third is much harder to customise.
I am new to angular and should be missing something.
Is there some better way to make input fields with datepicker really read-only?

ng-disabled="true"
worked for me.
Angularjs: 1.2.9 and
ui-boostrap: 0.8.0
Unfortunately I do not have reputation enough to comment on the original answer

Your third approach is quite nice, imho.
Given that the datepicker itself obviously does not support readonly property, I don't see another way how to make it read-only (and you even created a directive for it)
Your second approach sometimes results in minor flickering when clicking on the input (is it just me?)
As to customization, could you elaborate why it is difficult to do do? You'd have to pipe all the possible attributes of the original directive through to your directive, I guess?

Use ng-disabled="true"
<input type="text" class="form-control" datepicker-popup="{{format}}" ng-model="date" min="minDate" max="maxDate" ng-disabled="true" readonly="true"/>

Related

Angular - Watching a form whether its $dirty - performance

In my Angular app, I'm setting the placeholders in my form through some code in the controller (showing particular text for particular times of day).
When a user begins typing into any field of that form, I want all placeholders to be cleared.
To do this I understand I need to use $dirty using $watch
$scope.$watch('myForm.$dirty', function() {
//clear the placeholders
}, true);
My question is watch quite performance intensive in this situation or is there a more optimised way?
Thanks.
If you use $dirty and $watch it will work but it will clear all placeholders before you begins type or on controller load.
So, you can try this its work for me.
<div ng-controller="Ctrl">
<input type="text" ng-model="name1" ng-change="change()" placeholder={{placeholder1}}>
<input type="text" ng-model="name2" ng-change="change()" placeholder={{placeholder2}}>
<input type="text" ng-model="name3" ng-change="change()" placeholder={{placeholder3}}>
</div>
function Ctrl($scope) {
$scope.placeholder1="name";
$scope.placeholder2="city";
$scope.placeholder3="address";
$scope.change=function() {
$scope.placeholder1="";
$scope.placeholder2="";
$scope.placeholder3="";
};
}

Delayed bind on ngModel

I am trying to create a typeahead directive that does not bind the typed text to the model while typing.
This is as such no problem, but I would like to use the ngModel directive for my binding so I am able to use something similar to
<input type="text" ng-model="model.field" typeahead="sourceForTypeahead" />
instead of my current approach which works as a charm
<input type="text" ng-model="tmpFieldForInput" typeahead="sourceForTypeahead" typeahead-model="model.field" />
I can't figure if it is possible to change the "target" of ng-model internally in the directive so I get the typed input, and then is able to set the external model when an result from the source is selected.
Use ngModelOptions to specify when you'd like to bind the input text to the model:
<input type="text" ng-model="myModel" ng-model-options="{ updateOn: 'blur' }">
<p>Hello {{myModel}}!</p>
There are different events you can trigger on, but in this case, the text will only be bound to the model once the end-user leaves focus from the field.
Additional resources: https://docs.angularjs.org/api/ng/directive/ngModelOptions
Like #lux has mentioned, the right way to go about it is to use ng-model-options
But in your case, the ideal solution would be to do wrap your input in a form and bind on submit:
<form name="myForm">
<input type="text" ng-model="myModel" ng-model-options="{ updateOn: 'submit' }">
<p>Hello {{myModel}}!</p>
<input type="submit" value="Submit">
</form>
This will bind the value to the model only when you click your Submit button. This of course can be put anywhere you please.
I found a solution after looking into an old version of the checkbox-list module.
The solution is to change the ngModel attribute on compile time and make it point to an internal property in the directive and then compile in the postlink method.
I have updated the plunker for others to see the prototype: http://plnkr.co/edit/LbHH2pJGX1Iii8ZqqSie?p=preview
(Stack requires me to post code - so here is the )
app.directive('typeahead', ['$parse', '$compile', function ($parse, $compile) {
return {
priority: 1000,
terminal: true,
scope: {
source: '=typeahead',
ngModel: '='
},
compile: function(tElement, tAttrs) {
tElement.removeAttr("typeahead");
tElement.attr("ng-model", "searchTerm");
return function(scope, element, attrs) {
$compile(element)(scope);
// all my logic here

Replace checkbox with images angularjs

I have to style/replace all checkboxes and radio buttons with images.
I can do this easily by simply adding a span tag after every input(checkbox / radio) like.
<label for="rememberme">
<input type="checkbox" class="unique" id="rememberme"/>
<span class="icon-checkbox"></span>
</label>
How can i do this in angular js.
I cannot modify all htmls with adding a span element, as if something went wrong it will take time to revert.
But i have a unique class or i can add an attribute to write a directive.
I tried using an attribute but i am unable to inspect span element generated, it is working good if i use it as an element.
If i use as an attribute it is giving an output as
<input type="text" class="form-control" custom-input="">
<input type="text" class="form-control">
<span class="icon-checkbox">Checkbox icon</span>
</input>
is this Valid ?
Plunker here
I wouldn't worry about a custom directive just to replace the checkboxes with images. You could do this with CSS like this:
<!--HTML-->
<input type="text" class="form-control checkboxwithicon">
/*CSS*/
.checkboxwithicon{
position:relative;
}
.checkboxwithicon::after{
content:url('http://icons.iconarchive.com/icons/icojam/blueberry-basic/32/check-icon.png');
position:absolute;
top:-10px;
left:15px;
}
And position the image properly so that the checkbox is hidden.
You can write a directive that does generate the span icon-checkbox after the element (it's supposed to be a checkbox) for you and make it act like an attribute, then you can tag it in every input checkbox that you want. Even something wrong happens and you are not happy with the change, you don't have to waste your time and effort to revert the things back, just modify on the directive only.
I made it working from your suggestions with out any change to HTML for easy fallback
.directive('input',function(){
return{
require: '?ngModel',
restrict:'EAC',
link:function(scope,element,attr){
var html = angular.element("<span class='icon-checkbox'>Checkbox icon</span");
if( (angular.lowercase(element[0].tagName)==="input") && (angular.lowercase(attr.type)=== "checkbox"))
element.after(html)
}
}
})
Working Plunker

Get the Date from Datepicker programmatically as Text

I'm trying to get a date, which is displayed in an input of the bootstrap ui datepicker as pure text. Therefore I use a span.
Now I want to get the text of the span, but it doesn't work. Actually, I get an undefined. Do you have some tips for me?
HTML
<input type="text" class="form-control" datepicker-popup="{{format}}" ng-model="dt" is-open="opened" min-date="minDate" max-date="'2020-06-22'" datepicker-options="dateOptions" date-disabled="disabled(date, mode)" ng-required="true" close-text="Close" />
<span ng-model="textDate">{{dt | date:'dd.MM.yyyy' }}</span>
JS
$scope.$watch('dt', function() {
var tD = $scope.textDate.value;
console.log(tD);
});
Inject $filter into your controller/service and extract the date as a string like so
$scope.getDate = function() {
return $filter('date')($scope.dt, 'dd.MM.yyyy');
}
Plunkr here
Side-note: Assuming that dt already has two-way binding, I don't think it's necessary to $watch for changes. Instead, you can directly use $filter('date')($scope.dt, 'dd.MM.yyyy') where you need the string value.
Angular's ngModel won't work on a span since it is read only. You need some kind of input for two-way-binding with a ng-model. For this you could use a binding to a getter/setter of the model used on the input field. Here is an example:
<input type="text" name="userName"
ng-model="vm.dt"
ng-model-options="{ getterSetter: true }" />
<span ng-bind="vm.dt()"></span>
By adding ng-model-options="{ getterSetter: true }" will expose the model to a getter/setter which is a function you can simply call on your span. Also I would recommend to use the controllerAs syntax to avoid problems with scopes. Notice the vm is the alias for the controller around the input and span.
For more information check the documentation on ngModel.

How to make angular-ui inputs cooperate with angularjs attributes (particularly ng-readonly)?

I want to achieve read/write access control for select input using angularjs and angular-ui (particularly ui-select2 feature).
Scenario is simple: by using ng-readonly attribute I can control whether given input value can be changed by user or not.
<input id="clientShortName" class="span4" type="text" ng-readonly="readOnly" ng-model="client.shortName" />
<input ui-select2="{ tags: sometags}" id="clientTagsSelection" class="span4" type="text" ng-readonly="readOnly" ng-model="client.tagsSelection"/>
<input type="button" value="Edit" ng-click="readOnly = !readOnly"/>
This works fine for standard angularjs but when I'm trying to use inputs defined by angular-ui it doesn't work (doesn't change the read/write state of input).
Full scenario is covered here: http://plnkr.co/edit/pKs4Tq
Unfortunately the AngularUI ui-select2 directive has no integration with the angularJS ng-readonly directive.
One way for you to overcome this is to create your own directive and watch for changes on the readOnly property, like this:
app.directive('csReadonly', function() {
return {
restrict: "A",
link: function(scope, iElement, iAttrs, controller) {
scope.$watch(iAttrs.csReadonly, function(readonly) {
iElement.select2(readonly ? 'disable' : 'enable');
});
}
}
});
And use it like this:
<input ui-select2="{ tags: sometags }" cs-readonly="readOnly" ... />
Plunker: http://plnkr.co/edit/LBFDg2
The advantage of the approach is that, if in the future AngularUI decides to include support for the ng-readonly, you will only have to replace cs- with ng- and you're done.

Resources