I am working on a directive and I had form validation working by hard-coding the ng-show to form.birthdate.$touched && form.birthdate.$error.required. After getting this working I set out to removing the portion of that string where i'd hardcoded the name of the input to birthdate. See below in the code where I have two spans with the this field is required message. The bottom one works, the top one doesn't. If I look at it in the HTML elements panel they look identical so it seems like it should work but things are not being bound correctly. Any idea how to solve this problem?
angular.module('MyModule').directive('vrDatepicker', function() {
return {
scope: {
dateModel: '='
},
link: function(scope, element, attrs, ctrls) {
scope.id = attrs.id;
scope.name = attrs.name;
scope.required = attrs.required;
scope.form = ctrls[0];
},
controller: ['$scope', function($scope) {
// Removed b/c it doesn't matter
}],
template: '<p class="input-group">\
<input type="text" \
class="form-control"\
datepicker-popup="MMM d, yyyy"\
ng-model="dateModel"\
is-open="opened"\
show-weeks="false"\
close-text="Close" />\
<span class="input-group-btn">\
<button type="button"\
class="btn btn-default"\
ng-click="open($event)">\
<i class="glyphicon glyphicon-calendar"></i>\
</button>\
</span>\
<span ng-show="form[\'{{name}}\'].$touched && form[\'{{name}}\'].$error.required" class="text-danger">This field is required</span>\ // ### This one doesn't work
<span ng-show="form.birthdate.$touched && form.birthdate.$error.required" class="text-danger">This field is required</span>'\ // ### This one works
</p>'
}
});
This should work
<span ng-show="form[name].$touched && form[name].$error.required" class="text-danger">This field is required</span>
Everything within the string assigned to ng-show will be interpolated so you don't need all that extra syntax.
Related
I have a form which contains a fieldset, Form can have varying number of this fieldset, so I have a add more button in the form. There can be a situation where form has no fieldset. To fulfill these requirements I need have directive inheritance. The fieldset directive will be added on button click event. Fieldset has a template but the actual input fields HTML string is coming from server on page load. therefore I need to complete fieldset on the fly by appending input boxes with different id and then add this fieldset to the form.
To explain this scenario better I have scaled down my implementation and have created below code snippet and here is the plunker.
In scaled down scenario only form-Dir directive exist on page load. This directive has a button to fields with increasing id.
<body ng-app="mainApp" ng-controller="MainCtrl">
<div form-dir this-page="page" bind-field-id = "fieldId" bind-first-name="firstName" >
This is {{page}} page
<button class='btn-add'> add isolated fields </button>
<div class='field'>
</div>
</div>
</body>
Script
var app = angular.module('mainApp', []);
app.controller("MainCtrl", function($scope){
$scope.page = "Demo"
$scope.fieldId = "2"
$scope.firstName = "John"
});
app.directive("formDir", function($compile) {
return {
restrict:'A',
transclude: false,
scope:{
page:"=thisPage",
id:"=bindFieldId",
firstName:"=bindFirstName"
},
link : function(scope, element, attrs){
scope.page = 'demo2';
element.find('.btn-add').bind('click', function(e){
var field = angular.element(e.currentTarget).siblings('.field')
var newScope = scope.$new();
var ele = '<field-dir bind-field-id = "id" bind-first-name="firstName"></field-dir>';
var directive = $compile(ele)(newScope);
field.append(directive);
console.log('btn clicked', field)
})
}
};
});
app.directive("fieldDir", function() {
return {
restrict:'AE',
transclude: true,
replace: true,
scope:{
id:"=bindFieldId",
firstName:"=bindFirstName"
},
template: '<div></div>',
link : function(scope, element, attrs){
element.append('<label>{{scope.id}} First Name fields </label>'
+' <input type="text" class="textbox" ng-model="firstName">'
+' <button class="btn-change">change first name</button>');
element.find('.btn-change').bind('click', function(e){
scope.$apply(function(){
scope.firstName = "Doe";
scope.id = parseInt(scope.id) + 1;
});
console.log(scope)
})
}
};
});
The code will work if I place label,input and button tags in template; but as per my scenario I need to append it. I can't figure out what I am doing wrong.
I figured it out. All I needed to do is $compile before appending HTML string field.
replace
element.append('<label>{{scope.id}} First Name fields </label>'
+' <input type="text" class="textbox" ng-model="firstName">'
+' <button class="btn-change">change first name</button>');
with
var r = $compile('<label>{{id}} First Name fields </label>'
+' <input type="text" class="textbox" ng-model="firstName">'
+' <button class="btn-change">change first name</button>')(scope)
element.append(r);
I am dynamically adding a text field in DOM (from directive link function) & want to grab entered value and push it to controller scope object, but its always giving me undefined, below is my code:
<div class="input-group">
<span class="fieldIcon input-group-addon"><i class="fa fa-sticky-note" aria-hidden="true"></i></span>
<select name="addlist[]" multiple="multiple">
<option ng-repeat="options in optionList">{{options.label}}</option>
</select>
</div>
<script type="text/javascript">
angular.module('myapp')
.controller('AddContactController',[ '$scope', function ($scope) {
$scope.optionList = [{label: 'NewList'}];
$scope.addOption = function(optionList) {
console.log('List:', optionList); // its giving undefined
scope.optionList.push(optionList);
}
}])
.directive('optionList', ['$compile', function ($compile) {
return {
restrict: 'E',
templateUrl: '/templates/int_optionList.html',
controller: 'AddContactController',
link: function(scope, element, attrs) {
// Adding input field and on click of a button controllers addOption function should be called with the text field value
var addListField = '<input class="form-control" type="text" ng-modal="addList" name="addList" placeholder="Add new list...">'+
+'<button class="btn btn-default" type="button" ng-click="addOption()">';
addListField = $compile(addListField)(scope);
$(element).find('.multiselect-container').prepend(addListField);
}
}
}]);
</script>
Now here in addOption function I am getting optionList value as undefined.
Error is human :).
Replace ng-modal by ng-model in your directive
AngularJS expects models to be in object form, setting ng-modal="addList" to ng-modal="data.addList" might help.
Here's a jsfiddle example of what I'm trying to accomplish.
I'm trying to build a US phone number input where the view displays as (333) 555-1212, but the model binds to the numeric integer 3335551212.
My intention is to add custom validators to NgModelController which is why I have require: ng-model; there are simpler solutions without the isolate scope and NgModelController, but I need both.
You'll see an immediate error in the console: Error: Multiple directives [ngModel, ngModel] asking for 'ngModel' controller on: <input ng-model="user.mobile numeric" name="telephone" type="tel"> -- thought I was using an isolate scope here...
Thank you for looking #mimir137 but I appear to have solved it:
http://jsfiddle.net/hr121r18/8/
The directive was using replace: true, which ends up with this structure:
<form ng-controller="FooCtrl" class="ng-scope">
<p>Enter US phone number</p>
<input ng-model="user.mobile numeric" name="telephone" type="tel">
</form>
Both the template and the markup called for ng-model which led to the symptomatic error in the problem description. Once I removed that, it leads to this markup (note the wrapper element phone-number):
<form ng-controller="FooCtrl" class="ng-valid ng-scope ng-dirty ng-valid-parse" abineguid="BC0D9644F7434BBF80094FF6ABDF4418">
<p>Enter US phone number</p>
<phone-number ng-model="user.mobile" class="ng-untouched ng-valid ng-isolate-scope ng-dirty ng-valid-parse">
<input ng-model="numeric" name="telephone" type="tel" class="ng-valid ng-dirty ng-touched">
</phone-number>
</form>
But removing this required changes to $render; the elem passed into the link function is now phone-number and so you need to dig to grab the input inside it and set the value on that:
ngModel.$render = function () {
elem.find('input').val($filter('phonenumber')(ngModel.$viewValue));
};
There were a few other issues. $render() also needed to be called from the watcher.
Final:
var app = angular.module('myApp', []);
// i want to bind user.mobile to the numeric version of the number, e.g. 3335551212, but
// display it in a formatted version of a us phone number (333) 555-1212
// i am trying to make the directive's scope.numeric to have two-way binding with the controller's
// $scope.user.mobile (using isolate scope, etc.).
app.controller('FooCtrl', function ($scope) {
$scope.user = {
mobile: 3335551212
};
});
app.directive('phoneNumber', ['$filter', function ($filter) {
return {
restrict: 'E',
template: '<input ng-model="numeric" name="telephone" type="tel">',
require: 'ngModel',
scope: {
numeric: '=ngModel'
},
link: function (scope, elem, attrs, ngModel) {
// update $viewValue on model change
scope.$watch('numeric', function () {
ngModel.$setViewValue(scope.numeric);
ngModel.$render();
});
// $modelValue convert to $viewValue as (999) 999-9999
ngModel.$formatters.push(function (modelValue) {
return $filter('phonenumber')(String(modelValue).replace(/[^0-9]+/, ''));
});
// $viewValue back to model
ngModel.$parsers.push(function (viewValue) {
var n = viewValue;
if (angular.isString(n)) {
n = parseInt(n.replace(/[^0-9]+/g, ''));
}
return n;
});
// render $viewValue through filter
ngModel.$render = function () {
elem.find('input').val($filter('phonenumber')(ngModel.$viewValue));
};
}
};
}]);
app.filter('phonenumber', function () {
return function (number) {
if (!number) {
return '';
}
number = String(number);
var formattedNumber = number;
var c = (number[0] === '1') ? '1 ' : '';
number = number[0] === '1' ? number.slice(1) : number;
var area = number.substring(0, 3),
exchange = number.substring(3, 6),
subscriber = number.substring(6, 10);
if (exchange) {
formattedNumber = (c + '(' + area + ') ' + exchange);
}
if (subscriber) {
formattedNumber += ('-' + subscriber);
}
return formattedNumber;
}
});
HTML
<form ng-controller="FooCtrl">
<p>Enter US phone number</p>
<phone-number ng-model='user.mobile'></phone-number>
</form>
I created this fiddle that gets rid of most of your errors coming up in the console. Hopefully this will at least be able to put you on the right track.
I changed the template so that you can see that the filter is actually working.
It now has the typical {{ngModel | FilterName}} in plain text underneath the textbox.
The only real issue is displaying it in the textbox. I'm sure you will have no problem with that. I will check in the morning just in case you still have questions regarding this.
Edit: Alright it appears you have solved it already. Great job!
I am trying to wrap an input and some markup into an AngularJS directive.
However the fields are supposedly always validated which they shouldn't be.
Please check out my example # http://plnkr.co/edit/TivmuqQI4Y5K56gwcadW
here is the code for those who do not wish to look at Plunker
my directive
app.directive('myInput', function() {
return {
restrict: 'E',
require: '^ngModel',
templateUrl: 'form_control.html',
scope: {
label: '#',
placeholder: '#',
name: '#',
form: '=',
ngModel: '=ngModel'
},
}
});
this is my template
<div class="form-group" ng-class="{'has-error': form.{{name}}.$invalid && form.{{name}}.$dirty, 'has-success': form.{{name}}.$valid }">
<label for="{{name}}" class="col-sm-2 control-label">{{label}}</label>
<div class="col-sm-10">
<input type="{{type}}" class="form-control col-sm-10" id="{{name}}" name="{{name}}" ng-model="ngModel" placeholder="{{placeholder}}" ng-maxlength="10" ng-required="true">
</div>
</div>
and this is in my index.html
<my-input ng-model="website.url" name="url" label="URL" placeholder="http://example.com" form="form"></my-input>
Even though the input inside the template is required, the field is validated, which it shouldn't be if it's empty.
What am I doing wrong?
Required fields are immediately invalid on DOM load, as they are not valid until they have a value.
Also, in your plunker the top input is green, with an illusion of valid while the lower input is red showing invalid as it should.
You can't do this...
<div class="form-group" ng-class="{'has-error': form.{{name}}.$invalid && form.{{name}}.$dirty, 'has-success': form.{{name}}.$valid }">
The issue is with the form name field. When you do this...
<input type="{{type}}" class="form-control col-sm-10" id="{{name}}" name="{{name}}" ng-model="ngModel" placeholder="{{placeholder}}" ng-maxlength="10" ng-required="true">
name="{{name}} will actually compile to name: {{name}} in the form object. exmaple:
{{name}}: Constructor$dirty: false
$error: Object
$formatters: Array[1]
$invalid: true
$isEmpty: function (value) {
$modelValue: undefined
$name: "{{name}}"
$parsers: Array[1]...
This was taken from your plunker. Play around with console.log($scope.form) and look at the object.
under $error you will find that there are two inputs THIS form references, and neither of them are the expected input named 'url', they are in fact the 'title' input and your directive input '{{name}}' as seen here...
form: Constructor
$addControl: function (control) {
$dirty: false
$error: Object...
$name: "title"...
$name: "{{name}}"
Here is a forked plunker where I have already set up to console.log the form
Plunker
This shows that the form has no idea about any 'url' input. To get around this, you can write a custom directive for the input name field. Or just use the one I wrote below.
app.directive('myName', function(){
var myNameError = "myName directive error: "
return {
restrict:'A', // Declares an Attributes Directive.
require: 'ngModel', // ngModelController.
link: function( scope, elem, attrs, ngModel ){
if( !ngModel ){ return } // if no ngModel exists for this element
checkInputFormat(attrs); // check myName input for proper formatting.
var inputName = attrs.myName.match('^\\w+').pop(); // match upto '/'
assignInputNameToInputModel(inputName, ngModel);
var formName = attrs.myName.match('\\w+$').pop(); // match after '/'
findForm(formName, ngModel, scope);
} // end link
} // end return
function checkInputFormat(attrs){
if( !/\w\/\w/.test(attrs.myName )){ // should be in format "wordcharacters/wordcharacters"
throw myNameError + "Formatting should be \"inputName/formName\" but is " + attrs.myName
}
}
function assignInputNameToInputModel(inputName, ngModel){
ngModel.$name = inputName // adds string to ngModel.$name variable.
}
function addInputNameToForm(formName, ngModel, scope){
scope[formName][ngModel.$name] = ngModel; return // add input name and input object.
}
function findForm(formName, ngModel, scope){
if( !scope ){ // ran out of scope before finding scope[formName]
throw myNameError + "<Form name=" + formName + "'> element could not be found."
}
if( formName in scope){ // found scope[formName]
addInputNameToForm(formName, ngModel, scope)
return
}
findForm(formName, ngModel, scope.$parent) // recursively search through $parent scopes
}
});
// DIRECTIVE NOTES:
// This directive is used by simply including the following HTML element:
// <input my-name="email/profileForm">.
// In the example above, the input name is "email" and the form name
// that this input would be attached to would be named "profileForm"
// Like this...
// <form name="profileForm">
// <input my-name="email/profileForm">
// </form>
// Notice this "/" dividing the input name from the form name. This directive uses the '/'
// to separate the input name from the form name.
// Although it has not been tested yet, this directive should work with multi nested forms
// as well, as the recursive search only looks for the form name that was passed in, inwhich
// to bind the ngModel to.
// In this example, other form names would be skipped.
// <form name="profileForm">
// <form name="miniFormOne">
// <form name="miniFormTwo">
// <input my-name="email/profileForm">
// </form>
// </form>
// </form>
// The above example may not be the best behavior, but was just added as an example.
With this directive, you could use your nested directive input like this
<input type="{{type}}" class="form-control col-sm-10" id="{{name}}" my-name="{{ variableName + '/' + yourformnamevariable }}" ng-model="ngModel" placeholder="{{placeholder}}" ng-maxlength="10" ng-required="true">
The myName directive will get this: "url/form" in your case, then it will add the $name variable which will be 'url' to the ngModel for THAT input, as well as search up scope for the form named "form" and attach the ndModel to THAT form. The directive uses the '/' to separate names. The search is recursive and will travel until it finds the form name or error out with a message.
Of course when you create dynamic input names, how are you to call out
formName.dynamicInputName.$valid? as you don't know what the dynamic input names ARE!!!
So using form.{{name}}.$invalid && form.{{name}}.$dirty doesn't work unless you do this...
form.$valid && form.$dirty
But that won't work for each individual input. You can figure out your own way to do that...
I use another directive inside the input
Then in my-errors I just listen to the ngModel like after requiring ngModel, you can now call ngModel.$invalid then set a variable to change the CSS. If I get some time, I'll make a jsFiddle and link it here at the bottom. However, you can just use the class input.ng-invalid { border-color: red; } and the inputs will be red until they are valid.
If this doesn't clear things up, ask some more questions... We'll get it.
Hope this helpy's
For a specific use case I have to submit a single form the "old way". Means, I use a form with action="". The response is streamed, so I am not reloading the page. I am completely aware that a typical AngularJS app would not submit a form that way, but so far I have no other choice.
That said, i tried to populate some hidden fields from Angular:
<input type="hidden" name="someData" ng-model="data" /> {{data}}
Please note, the correct value in data is shown.
The form looks like a standard form:
<form id="aaa" name="aaa" action="/reports/aaa.html" method="post">
...
<input type="submit" value="Export" />
</form>
If I hit submit, no value is sent to the server. If I change the input field to type "text" it works as expected. My assumption is the hidden field is not really populated, while the text field actually is shown due two-way-binding.
Any ideas how I can submit a hidden field populated by AngularJS?
You cannot use double binding with hidden field.
The solution is to use brackets :
<input type="hidden" name="someData" value="{{data}}" /> {{data}}
EDIT : See this thread on github : https://github.com/angular/angular.js/pull/2574
EDIT:
Since Angular 1.2, you can use 'ng-value' directive to bind an expression to the value attribute of input. This directive should be used with input radio or checkbox but works well with hidden input.
Here is the solution using ng-value:
<input type="hidden" name="someData" ng-value="data" />
Here is a fiddle using ng-value with an hidden input: http://jsfiddle.net/6SD9N
You can always use a type=text and display:none; since Angular ignores hidden elements. As OP says, normally you wouldn't do this, but this seems like a special case.
<input type="text" name="someData" ng-model="data" style="display: none;"/>
In the controller:
$scope.entityId = $routeParams.entityId;
In the view:
<input type="hidden" name="entityId" ng-model="entity.entityId" ng-init="entity.entityId = entityId" />
I've found a nice solution written by Mike on sapiensworks. It is as simple as using a directive that watches for changes on your model:
.directive('ngUpdateHidden',function() {
return function(scope, el, attr) {
var model = attr['ngModel'];
scope.$watch(model, function(nv) {
el.val(nv);
});
};
})
and then bind your input:
<input type="hidden" name="item.Name" ng-model="item.Name" ng-update-hidden />
But the solution provided by tymeJV could be better as input hidden doesn't fire change event in javascript as yycorman told on this post, so when changing the value through a jQuery plugin will still work.
Edit
I've changed the directive to apply the a new value back to the model when change event is triggered, so it will work as an input text.
.directive('ngUpdateHidden', function () {
return {
restrict: 'AE', //attribute or element
scope: {},
replace: true,
require: 'ngModel',
link: function ($scope, elem, attr, ngModel) {
$scope.$watch(ngModel, function (nv) {
elem.val(nv);
});
elem.change(function () { //bind the change event to hidden input
$scope.$apply(function () {
ngModel.$setViewValue( elem.val());
});
});
}
};
})
so when you trigger $("#yourInputHidden").trigger('change') event with jQuery, it will update the binded model as well.
Found a strange behaviour about this hidden value () and we can't make it to work.
After playing around we found the best way is just defined the value in controller itself after the form scope.
.controller('AddController', [$scope, $http, $state, $stateParams, function($scope, $http, $state, $stateParams) {
$scope.routineForm = {};
$scope.routineForm.hiddenfield1 = "whatever_value_you_pass_on";
$scope.sendData = function {
// JSON http post action to API
}
}])
I achieved this via -
<p style="display:none">{{user.role="store_user"}}</p>
update #tymeJV 's answer
eg:
<div style="display: none">
<input type="text" name='price' ng-model="price" ng-init="price = <%= #product.price.to_s %>" >
</div>
I had facing the same problem,
I really need to send a key from my jsp to java script,
It spend around 4h or more of my day to solve it.
I include this tag on my JavaScript/JSP:
$scope.sucessMessage = function (){
var message = ($scope.messages.sucess).format($scope.portfolio.name,$scope.portfolio.id);
$scope.inforMessage = message;
alert(message);
}
String.prototype.format = function() {
var formatted = this;
for( var arg in arguments ) {
formatted = formatted.replace("{" + arg + "}", arguments[arg]);
}
return formatted;
};
<!-- Messages definition -->
<input type="hidden" name="sucess" ng-init="messages.sucess='<fmt:message key='portfolio.create.sucessMessage' />'" >
<!-- Message showed affter insert -->
<div class="alert alert-info" ng-show="(inforMessage.length > 0)">
{{inforMessage}}
</div>
<!-- properties
portfolio.create.sucessMessage=Portf\u00f3lio {0} criado com sucesso! ID={1}. -->
The result was:
Portfólio 1 criado com sucesso! ID=3.
Best Regards
Just in case someone still struggles with this, I had similar problem when trying to keep track of user session/userid on multipage form
Ive fixed that by adding
.when("/q2/:uid" in the routing:
.when("/q2/:uid", {
templateUrl: "partials/q2.html",
controller: 'formController',
paramExample: uid
})
And added this as a hidden field to pass params between webform pages
<< input type="hidden" required ng-model="formData.userid" ng-init="formData.userid=uid" />
Im new to Angular so not sure its the best possible solution but it seems to work ok for me now
Directly assign the value to model in data-ng-value attribute.
Since Angular interpreter doesn't recognize hidden fields as part of ngModel.
<input type="hidden" name="pfuserid" data-ng-value="newPortfolio.UserId = data.Id"/>
I use a classical javascript to set value to hidden input
$scope.SetPersonValue = function (PersonValue)
{
document.getElementById('TypeOfPerson').value = PersonValue;
if (PersonValue != 'person')
{
document.getElementById('Discount').checked = false;
$scope.isCollapsed = true;
}
else
{
$scope.isCollapsed = false;
}
}
Below Code will work for this IFF it in the same order as its mentionened
make sure you order is type then name, ng-model ng-init, value. thats It.
Here I would like to share my working code :
<input type="text" name="someData" ng-model="data" ng-init="data=2" style="display: none;"/>
OR
<input type="hidden" name="someData" ng-model="data" ng-init="data=2"/>
OR
<input type="hidden" name="someData" ng-init="data=2"/>