AngularJS: directive in loop not called when removing elements, trigger it manually? - angularjs

I have a couple of input fields in a ng-repeat block, and the user can add and remove fields with a button. A directive is used to mark invalid fields on blur.
My problem is this:
When I add new fields to the model in the controller, everything works fine. The view is updated, and the directive is called again to handle errors on the input fields.
But when I remove Fields from the model, the view is updated but the directive does not run. When the ng-repeat is rendered, a function is bound to the blur event of the fields. (inputNgEl.bind). When the user removes fields from the list, say, the 2nd field of 4, the rest of the fields get new names, so the function has to be bound again. Thats why I need the directive to run again after a person has been removed.
My demo code: http://plnkr.co/edit/EBggptFsMFld2fMfzS83?p=preview
You can see in the console how the directive is called when you add new fields, but not when you remove them.
What I would need is a way to trigger the directive manually in the removePersonFields function of the controller, or another, more elegant solution. Is this possible?
Here is the HTML:
<form novalidate name="form">
<div ng-repeat="person in persons" >
<div class="form-group" show-errors errorfieldindex="{{$index}}">
<label class="control-label">Name ({{'name_'+$index}})</label>
<input ng-model="person.name" name="name_{{$index}}" required class="form-control">
<span ng-if="form['name_'+$index].$error.required" class="help-block">Name invalid.</span>
</div>
<div class="form-group" show-errors errorfieldindex="{{$index}}">
<label class="control-label">Mail ({{'email_'+$index}})</label>
<input type="email" ng-model="person.email" name="email_{{$index}}" required class="form-control">
<span ng-if="form['email_'+$index].$error.required" class="help-block">Email required.</span>
<span ng-if="form['email_'+$index].$error.email" class="help-block">EMail invalid.</span>
</div>
<div class="btn-group">
<button type="button" class="btn btn-default" ng-click="removePersonFields($index)" ng-show="persons.length > 1">remove</button>
<button type="button" class="btn btn-default" ng-click="addPersonFields()">add</button>
</div>
</div>
</form>
The corresponding controller:
app.controller('MainCtrl', function($scope) {
$scope.init = function() {
$scope.persons = [{name: 'User One', email: 'aaa.bbb#ccc.org'},
{name: 'User Two', email: 'ddd.eee#fff.org'}];
}
$scope.addPersonFields = function() {
console.log("add person row");
$scope.persons.push( {name: '', email: ''});
}
$scope.removePersonFields = function(index) {
console.log("remove person row");
$scope.persons.splice(index,1);
// the view is updated after the splice, but the directive does not run.
}
$scope.init();
});
Every input field has a name with the current index of the loop. I use this name in the directive 'show-errors' to assign and remove a css-class when the field looses focus. The class is only there to hide the error messages until the user leaves the input field.
The directive 'showErrors':
app.directive('showErrors', function() {
return {
restrict: 'A',
require: '^form',
scope: {
errorfieldindex : '#'
},
link: function (scope, el, attrs, formCtrl) {
console.log("showErrors - link!");
// find the text box element, which has the 'name' attribute
var inputEl = el[0].querySelector("[name]");
// convert the native text box element to an angular element
var inputNgEl = angular.element(inputEl);
// get the name on the text box so we know the property to check
// on the form controller
var inputName = inputNgEl.attr('name');
if(typeof inputName != 'undefined') {
console.log("inputName: " + inputName);
var errorFieldIndex = scope.errorfieldindex;
if(typeof errorFieldIndex != 'undefined') {
console.log("errorFieldIndex: " + errorFieldIndex);
if(inputName.indexOf("{{$index}}") > 0) {
console.log("replace {{$index}} with " + errorFieldIndex);
inputName = inputName.replace("{{$index}}", errorFieldIndex);
}
}
// only apply the has-error class after the user leaves the text box
inputNgEl.bind('blur', function() {
console.log("bind Blur-event on element: " + inputName);
el.toggleClass('has-error', formCtrl[inputName].$invalid);
});
}
}
}
});

Related

AngularJs - show-hide password on click event using directive

I have one input field for password and one button with that input field
now, if type of input field is passwordi.e type="password", then on click of that button it should become text i.e type="text"
if again I click on the same button it should change type="password"
Means it should toggle the value of type of input element
I have done this using controller, It's working fine with controller. below is the working code using controller
But instead of controller if i want to use directive then how to handle this toggle condition using directive
purpose - I want to use this functionality on multiple input elements
HTML
<div class="input-group">
<label>Password</label>
<input type="{{inputType}}" class="form-control" ng-model="password" />
<span ng-click="show()">show</span>
</div>
Controller
$scope.inputType = 'password';
$scope.show = function() {
if ($scope.inputType === 'password') {
$scope.inputType = !$scope.inputType;
$scope.inputType = 'text';
} else {
$scope.inputType = !$scope.inputType;
$scope.inputType = 'password';
}
}
I tried using Directive - Below is my trial code
I am not getting how to change the type of <input /> element using directive
Directive
.directive('myDir', function() {
require: 'ngModel',
link: function (scope, element, attrs) {
console.log(attrs.type);
attrs.type = 'password'
// how to call this below code on click event or on click of <span>
which I am using for password
if (attrs.type === 'password') {
attrs.type = !attrs.type;
attrs.type = 'text';
} else {
attrs.type = !attrs.type.inputType;
attrs.type = 'password';
}
}
})
HTML Using Directive
<div class="input-group">
<label>Password</label>
<input my-dir type="" class="form-control" ng-model="password" />
<span ng-click="show()">show</span>
</div>
You can use ng-attr-type directive for dynamically change the input type. For example:
<input ng-attr-type="{{ isPassword ? 'password' : 'text' }}">
you can change the value of isPassword to the click event and make it toggle.
Using directive
.directive('isPassword', function() {
return {
restrict : 'A',
scope: {
isPassword: '=isPassword'
},
link: function (scope, element, attrs) {
scope.$watch('isPassword', function(a, b){
element.attr('type', a ? 'password' : 'text')
})
}
}
});
<input is-password="checkPassword" placeholder="Put your password" />
<input type="checkbox" ng-model="checkPassword" />
update on button click
<button ng-click="checkPassword=!checkPassword">click</button>
This is what the following directive does when added to the input element.
It appends a span element "Show me" with ng-click=show() event attached to it.
scope: true enables an isolated scope for the directive. This makes sure the function show() is unique for each directive.
When clicked on "Show me", the directive changes the input type of that element.
Hope this helps.
JSfiddle link

How do I get my dual binding to work properly with bootstrap datetime picker? [duplicate]

Here is the html for the date field :
<div class='form-group'>
<label>Check out</label>
<input type='text' ng-model='checkOut' class='form-control' data-date-format="yyyy-mm-dd" placeholder="Check out" required id="check-out">
</div>
<script>
$('#check-out').datepicker();
</script>
The datepicker shows up in the input field. However if I do this in my controller :
console.log($scope.checkOut);
I get undefined in the javascript console.
How to solve this ?
Is there a better way to use bootstrap-datepicker with angularjs ?
I don't want to use angular-ui/angular-strap since my project is bloated with javascript libraries.
As #lort suggests, you cannot access the datepicker model from your controller because the datepicker has its own private scope.
If you set: ng-model="parent.checkOut"
and define in the controller: $scope.parent = {checkOut:''};
you can access the datepicker using: $scope.parent.checkOut
I am using bootstrap 3 datepicker https://eonasdan.github.io/bootstrap-datetimepicker/ and angularjs, I had the same problem with ng-model, so I am getting input date value using bootstrap jquery function, below is the code in my controller, it's worked for me.
Html
<input class="form-control" name="date" id="datetimepicker" placeholder="select date">
Controller
$(function() {
//for displaying datepicker
$('#datetimepicker').datetimepicker({
viewMode: 'years',
format: 'DD/MM/YYYY',
});
//for getting input value
$("#datetimepicker").on("dp.change", function() {
$scope.selecteddate = $("#datetimepicker").val();
alert("selected date is " + $scope.selecteddate);
});
});
I just found a solution to this myself. I just pass in the model name to the directive (which I found most of online). This will set the value of the model when the date changes.
<input data-ng-model="datepickertext" type="text" date-picker="datepickertext" />{{datepickertext}}
angular.module('app').directive('datePicker', function() {
var link = function(scope, element, attrs) {
var modelName = attrs['datePicker'];
$(element).datepicker(
{
onSelect: function(dateText) {
scope[modelName] = dateText;
scope.$apply();
}
});
};
return {
require: 'ngModel',
restrict: 'A',
link: link
}
});
I am using Angular JS 1.5.0 and Bootstrap 3 Datetimepicker from https://eonasdan.github.io/bootstrap-datetimepicker/
After some time and struggling, I finally found a solution how to make it work for me :)
JSFiddle: http://jsfiddle.net/aortega/k6ke9n2c/
HTML Code:
<div class="form-group col-sm-4" >
<label for="birthdate" class="col-sm-4">Birthday</label>
<div class="col-sm-8">
<div class="input-group date" id="birthdate" ng-model="vm.Birthdate" date-picker>
<input type="text" class="form-control netto-input" ng-model="vm.Birthdate" date-picker-input>
<span class="input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
</div>
</div>
<div class="form-group col-sm-4">
<label for="birthdateText" class="col-sm-4">Model:</label>
<div class="col-sm-8">
<input type="text" class="form-control netto-input" ng-model="vm.Birthdate">
</div>
</div>
</body>
App.js:
Simply a controller setting the viewmodels Birtdate attribute:
var app = angular.module('exampleApp',[]);
app.controller('ExampleCtrl', ['$scope', function($scope) {
var vm = this;
vm.Birthdate = "1988-04-21T18:25:43-05:00";
}]);
The first directive is initializing the datetimepicker and listening to the dp.change event.
When changed - the ngModel is updated as well.
// DatePicker -> NgModel
app.directive('datePicker', function () {
return {
require: 'ngModel',
link: function (scope, element, attr, ngModel) {
$(element).datetimepicker({
locale: 'DE',
format: 'DD.MM.YYYY',
parseInputDate: function (data) {
if (data instanceof Date) {
return moment(data);
} else {
return moment(new Date(data));
}
},
maxDate: new Date()
});
$(element).on("dp.change", function (e) {
ngModel.$viewValue = e.date;
ngModel.$commitViewValue();
});
}
};
});
The second directive is watching the ngModel, and triggering the input onChange event when changed. This will also update the datetimepicker view value.
// DatePicker Input NgModel->DatePicker
app.directive('datePickerInput', function() {
return {
require: 'ngModel',
link: function (scope, element, attr, ngModel) {
// Trigger the Input Change Event, so the Datepicker gets refreshed
scope.$watch(attr.ngModel, function (value) {
if (value) {
element.trigger("change");
}
});
}
};
});
I have the same problem and resolve like this
added in html part
<input class="someting" id="datepicker" type="text" placeholder="Dae" ng-model=""/>
and simple in Controller call
$scope.btnPost = function () {
var dateFromHTML = $('#datepicker').val();
Have you tried AngularUI bootstrap? It has all the bootstrap modules rewritten in angular(including datepicker): http://angular-ui.github.io/bootstrap/
One of ways around: set the id field to the input, then call document.getElementById('input_name').value to get the value of the input field.
Using this datepicker I had the same problem. I solved it using a little trick.
In the inputs tag I added a new attribute (dp-model):
<input class="form-control dp" type="text" dp-model="0" />
<input class="form-control dp" type="text" dp-model="1" />
...
And then in the js file I forced the binding in this way:
$scope.formData.dp = []; // arrays of yours datepicker models
$('.dp').datepicker().on('changeDate', function(ev){
$scope.formData.dp[$(ev.target).attr('dp-model')] = $(ev.target).val();
});
This worked for me:
set ng-model="date"
on your angular controller:
$scope.date = '';
$('#check-out').datepicker().on('changeDate', function (ev) {
$scope.date= $('#check-out').val();
$scope.$digest();
$scope.$watch('date', function (newValue, oldValue) {
$scope.date= newValue;
});
});
My actual trouble was that the value of the model was not reflected till another component on the scope was changed.
im using this , and my code :
this.todayNow = function () {
var rightNow = new Date();
return rightNow.toISOString().slice(0,10);
};
$scope.selecteddate = this.todayNow();
$(function() {
//for displaying datepicker
$('.date').datepicker({
format: 'yyyy-mm-dd',
language:'fa'
});
//for getting input value
$('.date').on("changeDate", function() {
$scope.selecteddate = $(".date").val();
});
});
To solve this, I have my HTML looking like this:
<div class='input-group date' id='datetimepicker1'>
<input type='text' ng-model="date.arrival" name="arrival" id="arrival"
class="form-control" required />
<span class="input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
My ng-model wouldn't update, so I used this to fix it:
$("#datetimepicker1").on("dp.change", function (e) {
$scope.date.arrival = $("#arrival").val(); // pure magic
$('#datetimepicker2').data("DateTimePicker").minDate(e.date); // this line is not relevant for this example
});

How to create a directive for disable all elements into div element

how to create a directive for disable all elements into div element ?
something like this :
<div div-disabled div-disabled-condition="state=='Stack'||state=='Over'||state=='Flow'">
<input type="text"/>
<input type="url"/>
<div>
<input type="text"/>
<input type="url"/>
</div>
<div>
Is it possible? I have no idea .
angular
.module('uiRouterApp.ctrl.add', ['uiRouterApp.ctrl.customDirective'])
.controller('addCtrl', [
'$scope',
'$location',
'$stateParams',
'$state',
function ($scope, $location, $stateParams, $state) {
$scope.state = {};
}
]).directive('divDisabled', function () {
return {
scope: {
divDisabledCondition: '#'
},
link: function (scope, element, attrs) {
}
};
});
Update :
please see this :
<div class="col-sm-12 ng-isolate-scope" selected-object="SelectedAutoComplete" local-data="requirements.Item1" search-fields="NameFa,NameEn" title-field="NameFa" minlength="2" field-required="true" image-field="ImageUrl" disable-auto-compelete="response.State=='Success'||response.State=='Error'||response.State=='Warning'">
<div class="angucomplete-holder">
<input id="_value" ng-model="searchStr" type="text" placeholder="select" class="form-control ng-dirty" ng-focus="resetHideResults()" ng-blur="hideResults()" autocapitalize="off" autocorrect="off" autocomplete="off" ng-change="inputChangeHandler(searchStr)" ng-disabled="response.State=='Success'||response.State=='Error'||response.State=='Warning'" style="">
<!-- ngIf: showDropdown -->
</div>
</div>
directive :
.directive('contentsDisabled', function() {
return {
compile: function(tElem, tAttrs) {
var inputs = tElem.find('input');
for (var i = 0; i < inputs.length; i++) {
inputs.attr('ng-disabled', tAttrs['disableAutoCompelete']);
}
}
}
})
why When the state is 'Success' or 'Error' or 'Warning' Input not disabled ?
You can create a directive that alters its content during compile time by adding the condition. Something along these lines (untested):
module.directive('contentsDisabled', function() {
return {
compile: function(tElem, tAttrs) {
var inputs = tElem.find('input');
inputs.attr('ng-disabled', tAttrs['contentsDisabled']);
}
};
});
See a JSFiddle here: http://jsfiddle.net/HB7LU/6380/
This has the drawback that you just copy the expression from contents-disabled into ng-disabled attributes of any inputs - if somebody uses a directive that in turn creates <input> elements, you won't pick them up.
It'd be less fragile to get hold of the FormController instance and iterate through all its controls, but sadly AngularJS doesn't expose the controls in a form. Maybe file a feature request?
You also can use a tag fieldset :
<form>
<fieldset ng-disable="foo">
<input name="the_one"/>
<input name="the_second"/>
</fieldset>
<input name="the_thrid"/>
</form>
With this way, when the variable foo is TRUE, inputs "the_one" and "the_second" will be disabled.
Why don't you use ng-disabled on your required expression on each input?
https://docs.angularjs.org/api/ng/directive/ngDisabled
If you truly do want a grouping directive, use the compile function of the directive to insert the ng-disabled attribute on each child. Or use a paren't child directive to signify which children to apply the ng-disabled to.
There is a new option to control enable/disable input field for angucomplete-alt.
http://ghiden.github.io/angucomplete-alt/#example13

How to show form input errors using AngularJS UI Bootstrap tooltip?

For example I have the form where I am showing form input errors.
I need to show red badge (with 'hover to show errors') near input label if there are some errors. If user will hover this red badge - he will see list of errors using AngularJS UI Bootstrap tooltip.
I don't want to put list of errors into tooltip-html-unsafe attribute, because it is not convenient to edit and maintain.
This code is more declarative:
<validation-tooltip ng-show="appForm.number.$invalid && appForm.number.$dirty">
<ul>
<li ng-show="appForm.number.$error.required">this field is required</li>
<li ng-show="appForm.number.$error.number">should be number</li>
<li ng-show="appForm.number.$error.min">minimum - 5</li>
<li ng-show="appForm.number.$error.max">miximum - 20</li>
</ul>
</validation-tooltip>
than this code:
<span tooltip-html-unsafe="{{<ul><li>This field is required;</li><li>...</li></ul>}}">hover to show errors</span>
How can I write such validation-tooltip directive using AngularJS UI Bootstrap tooltip?
Or maybe can you suggest another approach to maintain validation error messages?
Demo Fiddle
Validation Tooltip Directive
The validationTooltip is the main directive. It has the following responsibilities:
Define the tool tip template through its transcluded contents
Keep track of validation expressions so that they can be evaluated with each digest cycle.
Expose a controller API for allowing valiationMessage directives to register themselves
Provide a 'target' attribute on the directive to specify which form field the badge (and the associated tooltip) will be bound to
Additional Notes
The tooltip template uses the transclusion function from the link function to bind the template to the directive's scope. There are two properties that are within scope that the template can bind to:
$form: Bound to the form model defined in parent scope. i.e. $scope.myForm
$field: Bound to the form.name model in parent scope. i.e. $scope.myForm.myInput
This allows the template to bind to validation properties such as $valid, $invalid, $pristine, $dirty, and $error without referring to the form name or the input field's name directly. For example, all of the following expressions are valid binding expressions:
$form properties:
`$form.$valid`
`$form.$invalid`
`$form.$dirty`
`$form.$pristine`
`$form.$error.required` etc...
$field properties:
`$field.$valid`
`$field.$invalid`
`$field.$dirty`
`$field.$pristine`
`$field.$error.required` etc...
Directive Implementation
app.directive('validationTooltip', function ($timeout) {
return {
restrict: 'E',
transclude: true,
require: '^form',
scope: {},
template: '<span class="label label-danger span1" ng-show="errorCount > 0">hover to show err</span>',
controller: function ($scope) {
var expressions = [];
$scope.errorCount = 0;
this.$addExpression = function (expr) {
expressions.push(expr);
}
$scope.$watch(function () {
var count = 0;
angular.forEach(expressions, function (expr) {
if ($scope.$eval(expr)) {
++count;
}
});
return count;
}, function (newVal) {
$scope.errorCount = newVal;
});
},
link: function (scope, element, attr, formController, transcludeFn) {
scope.$form = formController;
transcludeFn(scope, function (clone) {
var badge = element.find('.label');
var tooltip = angular.element('<div class="validationMessageTemplate tooltip-danger" />');
tooltip.append(clone);
element.append(tooltip);
$timeout(function () {
scope.$field = formController[attr.target];
badge.tooltip({
placement: 'right',
html: true,
title: clone
});
});
});
}
}
});
Validation Message Directive
The validationMessage directive keeps track of the validation messages to display in the tooltip. It uses ng-if to define the expression to evaluate. If there is no ng-if found on the element, then the expression simply evaluates to true (always shown).
app.directive('validationMessage', function () {
return {
restrict: 'A',
priority: 1000,
require: '^validationTooltip',
link: function (scope, element, attr, ctrl) {
ctrl.$addExpression(attr.ngIf || true );
}
}
});
Usage in HTML
Add a form with a name attribute
Add one or more form fields - each with a name attribute and an ng-model directive.
Declare a <validation-tooltip> element with a target attribute referring to the name of one of the form fields.
Apply the validation-message directive to each message with an optional ng-if binding expression.
<div ng-class="{'form-group': true, 'has-error':form.number.$invalid}">
<div class="row">
<div class="col-md-4">
<label for="number">Number</label>
<validation-tooltip target="number">
<ul class="list-unstyled">
<li validation-message ng-if="$field.$error.required">this field is required </li>
<li validation-message ng-if="$field.$error.number">should be number</li>
<li validation-message ng-if="$field.$error.min">minimum - 5</li>
<li validation-message ng-if="$field.$error.max">miximum - 20</li>
</ul>
</validation-tooltip>
</div>
</div>
<div class="row">
<div class="col-md-4">
<input type="number" min="5" max="20" ng-model="number" name="number" class="form-control" required />
</div>
</div>
</div>
#pixelbits answer is great. I used this instead:
<div class="form-group" ng-class="{ 'has-error': form.name.$dirty && form.name.$invalid }">
<label for="name" class="col-sm-4 control-label">What's your name?</label>
<div class="col-sm-6">
<input class="form-control has-feedback" id="name" name="name"
required
ng-minlength="4"
ng-model="formData.name"
tooltip="{{form.name.$valid ? '' : 'How clients see your name. Min 4 chars.'}}" tooltip-trigger="focus"
tooltip-placement="below">
<span class="glyphicon glyphicon-ok-sign text-success form-control-feedback" aria-hidden="true"
ng-show="form.name.$valid"></span>
</div>
</div>
The technique is ui-bootstrap's tooltip and set the tooltip text to '' when valid.
http://jsbin.com/ditekuvipa/2/edit
Great answer from #pixelbits. I used his directives and modified them slightly to allow the tooltip to display over the actual input as some users requested.
angular.module('app')
.directive('validationTooltip', ['$timeout', function ($timeout) {
function toggleTooltip(scope) {
if (!scope.tooltipInstance) {
return;
}
$timeout(function() {
if (scope.errorCount > 0 && (scope.showWhen == undefined || scope.showWhen())) {
scope.tooltipInstance.enable();
scope.tooltipInstance.show();
} else {
scope.tooltipInstance.disable();
scope.tooltipInstance.hide();
}
});
}
return {
restrict: 'E',
transclude: true,
require: '^form',
scope: {
showWhen: '&',
placement: '#',
},
template: '<div></div>',
controller: ['$scope', function ($scope) {
var expressions = [];
$scope.errorCount = 0;
this.$addExpression = function (expr) {
expressions.push(expr);
}
$scope.$watch(function () {
var count = 0;
angular.forEach(expressions, function (expr) {
if ($scope.$eval(expr)) {
++count;
}
});
return count;
}, function (newVal) {
$scope.errorCount = newVal;
toggleTooltip($scope);
});
}],
link: function (scope, element, attr, formController, transcludeFn) {
scope.$form = formController;
transcludeFn(scope, function (clone) {
var tooltip = angular.element('<div class="validationMessageTemplate" style="display: none;"/>');
tooltip.append(clone);
element.append(tooltip);
$timeout(function () {
scope.$field = formController[attr.target];
var targetElm = $('[name=' + attr.target + ']');
targetElm.tooltip({
placement: scope.placement != null ? scope.placement : 'bottom',
html: true,
title: clone,
});
scope.tooltipInstance = targetElm.data('bs.tooltip');
toggleTooltip(scope);
if (scope.showWhen) {
scope.$watch(scope.showWhen, function () {
toggleTooltip(scope);
});
}
});
});
}
}
}]);
The major change is that the directive uses jQuery to find the target element (which should be an input) via the name attribute, and initializes the tooltip on that element rather than the element of the directive. I also added a showWhen property to the scope as you may not always want your tooltip to show when the input is invalid (see example below).
The validationMessage directive is unchanged
angular.module('app').directive('validationMessage', function () {
return {
restrict: 'A',
priority: 1000,
require: '^validationTooltip',
link: function (scope, element, attr, ctrl) {
ctrl.$addExpression(attr.ngIf || true);
}
}
});
Usage in Html is also similar, with just the addition of showWhen if you want:
<div class="form-group" ng-class="{ 'has-error' : aForm.note.$invalid && (aForm.note.$dirty) }">
<label class="col-md-3 control-label">Note</label>
<div class="col-md-15">
<textarea
name="note"
class="form-control"
data-ng-model="foo.Note"
ng-required="bar.NoteRequired"></textarea>
<validation-tooltip target="note" show-when="aForm.note.$invalid && (aForm.note.$dirty)">
<ul class="validation-list">
<li validation-message ng-if="$field.$error.required">Note required</li>
</ul>
</validation-tooltip>
</div>
</div>
you can actually just use the tooltip-enable property:
<div class="showtooltip" tooltip-placement="left" tooltip-enable="$isValid" tooltip="tooltip message"></div>
My goal was to leverage both ng-messages and ui-bootstrap popover for validation feedback. I prefer the popover vs. the tooltip as it displays the help-block styles more clearly.
Here's the code:
<!-- Routing Number -->
<div class="form-group-sm" ng-class="{ 'has-error' : form.routingNumber.$invalid && !form.routingNumber.$pristine }">
<label class="control-label col-sm-4" for="routing-number">Routing #</label>
<div class="col-sm-8">
<input class="form-control input-sm text-box"
id="routing-number"
name="routingNumber"
ng-model="entity.ROUTINGNUM"
popover-class="help-block"
popover-is-open="form.routingNumber.$invalid"
popover-trigger="none"
required
uib-popover-template="'routing-number-validators'"
string-to-number
type="number" />
</div>
<!-- Validators -->
<script type="text/ng-template" id="routing-number-validators">
<div ng-messages="form.routingNumber.$error">
<div ng-messages-include="/app/modules/_core/views/validationMessages.html"></div>
</div>
</script>
</div>
Here is the validationMessages.html
<span ng-message="required">Required</span>
<span ng-message="max">Too high</span>
<span ng-message="min">Too low</span>
<span ng-message="minlength">Too short</span>
<span ng-message="maxlength">Too long</span>
<span ng-message="email">Invalid email</span>
Note: I had to upgrade to jQuery 2.1.4 to get the uib-popover-template directive to work.
Dependencies:
string-to-number
UI Bootstrap
ng-messages

$dirty not working as expected when the input type is "file"

Am quite new to AngularJS. The issue is i have a form with two fields- name and profile pic as shown in the code below. I am using ng-upload (https://github.com/twilson63/ngUpload). I want the 'Save' button to work only if either field is dirty and the upload isn't happening currently so that multiple post requests are not triggered on the user clicking on the 'Save' button. But looks like, $dirty works fine with the 'name' field but not with the 'profile pic' field. Am i just missing something? How to go about it keeping it as simple as possible for a beginner of AngularJS. Any help would be appreciated.
//Code
<form id='picUpload' name='picUpload' ng-upload-before-submit="validate()" method='post' data-ng-upload-loading="submittingForm()" action={{getUrl()}} data-ng-upload='responseCallback(content)' enctype="multipart/form-data">
<input type="text" name="name" data-ng-model="user.name" maxlength="15" id="user_screen_name" required>
<input type="file" name="profilePic" data-ng-model="user.profilePic" accept="image/*">
<div class="form-actions">
<button type="submit" class="btn primary-btn" id="settings_save" data-ng-disabled="!(picUpload.name.$dirty|| picUpload.profilePic.$dirty) || formUploading">Save changes</button>
</div>
</form>
//In my JS code
$scope.submittingForm = function(){
$scope.formUploading = true;
}
Regards!
I made a directive ng-file-dirty
.directive('ngFileDirty', function(){
return {
require : '^form',
transclude : true,
link : function($scope, elm, attrs, formCtrl){
elm.on('change', function(){
formCtrl.$setDirty();
$scope.$apply();
});
}
}
})
I haven't used ng-upload before, but you can use onchange event of input element. onchange event is fired whenever user selects a file.
<input type="file" onchange="angular.element(this).scope().fileNameChanged(this)" />
Javascript :
var app = angular.module('MainApp', []);
app.controller('MainCtrl', function($scope)
{
$scope.inputContainsFile = false;
$scope.fileNameChanged = function(element)
{
if(element.files.length > 0)
$scope.inputContainsFile = true;
else
$scope.inputContainsFile = false;
}
});
So now you can check if inputContainsFile variable is true along with dirty check of name field

Resources