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);
Related
I have a custom directive and want to use the validators from an input field used inside this directive template. Is there a way to extend ngModelCtrl validators with the input validators?
This is my directive:
angular.module('myModule')
.directive('myUrl', function() {
return {
restrict: 'A',
require: ['ngModel', '^form'],
replace: true,
templateUrl: '/components/url/my-url.html',
scope: {},
link: function(scope, element, attrs, ctrls) {
var ngModelCtrl = ctrls[0];
var formCtrl = ctrls[1];
scope.formField = ngModelCtrl;
// Scope data vars
scope.url = '';
// Watchers
scope.$watch('url', function(newValue) {
ngModelCtrl.$setViewValue(newValue);
});
// Validators
var inputValidators = formCtrl['my-url'].$validators;
ngModelCtrl.$validators = angular.copy(inputValidators);
// Custom render
ngModelCtrl.$render = customRender;
function customRender() {
scope.url = ngModelCtrl.$viewValue;
}
}
}
});
This is my view:
<div>
<label>My URL</label>\
<input type="url" name="my-url" placeholder="http://" ng-model="url">
<div ng-messages="formField.$error" ng-show="formField.$invalid">
<span ng-message="url">Invalid URL.</span>
</div>
</div>
Here's a fiddle with this problem: https://jsfiddle.net/bsmaniotto/gzLmy1op/
Thanks.
EDIT:
Just realized that the problem is not on binding the input[url] validators to my custom directive ngModelCtrl's validators. The input[url] element is being validated and when an invalid input is entered the $modelValue is not set, and therefore, for my custom directive it is as nothing is inputted.
input validation is pretty easy in angular if you're using a controller. you can add:
ui-event: {keyup: 'controller.validationFunctionName(whatever)'}
Just fill in 'whatever' with the text in your input field and every time a key is let go it will check your validation function
If you want it to display an error beneath it just create another div that has an ng-if= to the result of that keyup function
I am playing with AngularJS directive. I want to format the data which is going to be displayed. This is what I am doing:
HTML:
<div ng-app="myApp" ng-controller="myCtrl">
<input ng-model="data" type="text" test />
<input type="button" ng-click="change()" value="Change"> {{data}}
<br>Hello <span ng-bind="data" test></span>
</div>
JS:
angular.module('myApp', [])
.controller('myCtrl', function($scope) {
$scope.data = 'yellow';
$scope.change = function() {
$scope.data = 'black';
};
})
.directive('test', function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ngModel) {
ngModel.$formatters.push(function(value) {
//formats the value for display when ng-model is changed
return 'brown';
});
ngModel.$parsers.push(function(value) {
//formats the value for ng-model when input value is changed
return 'green';
});
}
};
});
I am able to format the data for the model and display it for input text. But I am not able to format the data for the span which is bound to a model. span is showing the model as it is. I don't know why the span is showing the value of the model. I want to show the formatted value to the span as well. Here is the jsFiddle.
You can use ng-model for only input. If you want to change a text in a div,span,label or any other element without input you can use seperators . {}
When you create a variable in $scope you can show it in your html.
<div ng-app="myApp" ng-controller="myCtrl">
<input ng-model="data" type="text" test />
<input type="button" ng-click="change()" value="Change"> {{data}}
<br>Hello <span test>{data}</span>
</div>
Fiddle
I think it is working perfectly fine only thing is you are not taking the right example here.
First of all please read below:
Formatters change how model values will appear in the view.
Parsers change how view values will be saved in the model.
and now replace the code below
ngModel.$formatters.push(function(value) {
//formats the value for display when ng-model is changed
return 'brown';
});
ngModel.$parsers.push(function(value) {
//formats the value for ng-model when input value is changed
return 'green';
});
with
ngModel.$formatters.push(function(value) {
//formats the value for display when ng-model is changed
return value.toLowerCase();
});
ngModel.$parsers.push(function(value) {
//formats the value for ng-model when input value is changed
return value.toUpperCase();
});
I hope this help!!
I have a custom angular directive that I'm using to change the display of price on an item. It's stored as a yearly value and I need to toggle between allowing users to see/edit as yearly or as monthly. So my custom directive is working on my input boxes (not able to use it for a label, div, span, but that's another issue).
I'm able to see that when I toggle the attribute, it picks up the change, but the value in the input doesn't change and I assume it's because not re-rendering the element. Is there any way I can force that to happen?
Here's my directive
angular.module('common.directive').directive('priceMonthly', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attr, ngModel) {
var perMonth = false;
attr.$observe('priceMonthly', function(isMonthly) {
perMonth = (isMonthly.toLowerCase() === "true");
console.log("per month is " + perMonth); //updating perMonth correctly at this point
//guessing I need to do something here or in the function that changes the value of the attribute
});
function fromView(value){
return perMonth ? value * 12 : value;
}
function toView(value){
return perMonth ? value / 12 : value;
}
ngModel.$parsers.push(fromView);
ngModel.$formatters.push(toView);
}
};
});
Here's where I'm using it.
<input type="text" price-monthly="{{monthlySqftView}}"
class="form-control no-animate" name="item.priceLow"
ng-model="item.priceLow" ui-money-mask="2"/>
Here's a jsfiddle of where I'm currently at. So if the dropdown is on monthly, and I type in say 12, the price is correctly 144 which would be the full year price. Now if you change the dropdown to yearly, I need the input to update to 144.
https://jsfiddle.net/6tnbonzy/
Try scope.$apply() after you made required changes
#rschlachter, i am not sure if this can help you. AngularJS uses scope.digest() to clean the dirty data. you might need to put this line of code into your method
attr.$observe('priceMonthly', function(isMonthly) {
perMonth = (isMonthly.toLowerCase() === "true");
console.log("per month is " + perMonth); //updating perMonth correctly at this point
//guessing I need to do something here or in the function that changes the value of the attribute
//put it here, if you instantiated scope already.
scope.digest();
//scope.apply();
});
hope it works
It seems you want to do something like this jsfiddle?
<form name="ExampleForm">
<select ng-model="period">
<option value="monthly">monthly</option>
<option value="yearly">yearly</option>
</select>
<pre>
period = {{period}}
</pre>
Edit as {{period}}: <input price-monthly="obj.price" period="period" ng-model="obj.price" >
<pre>
price = {{obj.price}}
</pre>
</form>
And JS controller
.controller('ExampleController', function($scope) {
$scope.period = 'monthly';})
And JS directive
.directive('priceMonthly', function() {
return {
restrict: 'A',
scope:{
priceMonthly:"=",
period:"="
},
link: function(scope) {
scope.$watch('period',function(newval){
if(newval=='monthly')
scope.priceMonthly = scope.priceMonthly*12;
if(newval=='yearly')
scope.priceMonthly = scope.priceMonthly/12;
})
}
};});
I hope this will help you.
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 Created js fiddle: Fiddle
I create a form with some ng-options in it, and it have strange behavior when you use the button instead of mouse (just click on the textbox and press "tab" and you can select it using arrow key).
<form ng-controller="MyApp" id="Apps" name="Apps" ng-submit="SendApp()" role="form" novalidate>
<input type="text" name="title" ng-model="Info.Title" />
<select id="Formula" ng-model ="Info.Genre" ng-change= "ChangeGenre()"
ng-options="id as name for (id, name) in Genre" blank></select>
<select class="form-control" ng-model ="Info.Program"
ng-options="Program as Name for (Program, Name) in Program" ng-change="ChangeProgram()" blank></select>
<h3>{{Info.Genre}}</h3>
<h3>{{Info.Program}}</h3>
<button type=submit>Submit this </button>
</form>
Javascript:
var theApp = angular.module("TheApp", []);
theApp.controller("MyApp", ['$scope', function($scope){
$scope.Program = {"1":"Music","2":"Theater","3":"Comedy"};
$scope.Genre = {"1":"Mystery", "2":"Thriller", "3":"Romance"};
$scope.ChangeProgram = function(){
alert($scope.Info.Program + " " + $scope.Info.Genre);
}
$scope.ChangeGenre = function (){
console.log($scope.Info.Genre);
}
$scope.SendApp = function(){
alert($scope.Info.Program + " " + $scope.Info.Genre);
}
}]);
The ng-model are not updated when you select the first options on First try.
What's Wrong, and How To Fix this?
Update:
As Mentioned on comment below, To reproduce, enter mouse into textfield, tab to combobox and try to select the second option (Thriller) using keyboard. This will fail on the first attempt, once the third or first option is selected, the second option is also recognized.
Using the the directive proposed here, this works for me:
theApp.directive("select", function() {
return {
restrict: "E",
require: "?ngModel",
scope: false,
link: function (scope, element, attrs, ngModel) {
if (!ngModel) {
return;
}
element.bind("keyup", function() {
element.triggerHandler("change");
})
}
}
})
I forked the fiddle.