Parameterizing the data model in an Angular directive - javascript

I am implementing a control/widget that has three options, only one of which may be selected, which led me to using radiobuttons. This widget has to appear several times on various forms so I embarked on creating (incrementally) a dedicated directive.
The template of the directive is as follows:
<div class="row">
<span class="fieldlabel col-xs-3">{{title}}</span>
<div>
<label>
<input type="radio" data-ng-model="modelName" value="{{value1}}">
{{label1}}
</label>
<label>
<input type="radio" data-ng-model="modelName" value="{{value2}}">
{{label2}}
</label>
<label>
<input type="radio" data-ng-model="modelName" value="{{value3}}">
{{label3}}
</label>
</div>
The title, labels and values are correctly defined/computed either through using the custom directives or in the controller.
The last question I am facing now is how to specify different model bindings for each such widget? All instances of this widget currently share their model binding, which is of course not what I need. For instance, both of the divs in the fictitious example below would bind to "modelName" but I need them to bind to say "annotationsPos" and "menuPos" in the view's controller.
<div my-3option-radiobutton title="Show annotations"></div>
<div my-3option-radiobutton title="Menu position"></div>
How can I specify bindings in a custom directive?
EDIT 1
I think either I haven't really made myself clear or I lack some elements that would have helped me understand the answers that were offered.
If I had written the HTML by hand, I would have had something like this:
<div class="row">
<span class="fieldlabel col-xs-3">Position of your annotations</span>
<div>
<label>
<input type="radio" data-ng-model="annotationsPos" value="left">
Left of the element
</label>
<label>
<input type="radio" data-ng-model="annotationsPos" value="middle">
Through the element
</label>
<label>
<input type="radio" data-ng-model="annotationsPos" value="right">
Right of the element
</label>
</div>
<!-- -->
<div class="row">
<span class="fieldlabel col-xs-3">Position of the top menu</span>
<div>
<label>
<input type="radio" data-ng-model="menuPos" value="left">
Top left
</label>
<label>
<input type="radio" data-ng-model="menuPos" value="middle">
Top middle
</label>
<label>
<input type="radio" data-ng-model="menuPos" value="right">
Top right
</label>
</div>
<!-- -->
<div class="row">
<span class="fieldlabel col-xs-3">Position of notifications</span>
<div>
<label>
<input type="radio" data-ng-model="notificationPos" value="left">
Bottom left
</label>
<label>
<input type="radio" data-ng-model="notificationPos" value="middle">
Bottom middle
</label>
<label>
<input type="radio" data-ng-model="notificationPos" value="right">
Bottom right
</label>
</div>
Instead of copying and pasting this boilerplate code multiple times, I'm looking to do this thanks to an attribute directive:
<div my-3option-radiobutton title="Position of your annotations"></div>
<div my-3option-radiobutton title="Position of system notifications"></div>
<div my-3option-radiobutton title="Position of the top menu"></div>
What changes between these block is made of titles, values and, most importantly, model attribute values. I've covered the titles and values in the directive's controller in a non elegant way (see plunk further below). My problem is that I can't seem to:
determine where to specify an ng-model AND
have the "generated" HTML code refer correctly to that model attribute value (i.e. 'annotationPos', 'notificationsPos' and 'menuPos') AND
have two-way binding with the parent controller
EDIT 2
This plunk shows that #Suresh's answer is working, with a minor modification concerning the field name. However, the directive that I have written does not work (all widgets on the page bind to the same value), maybe due to it being an attribute directive and not an element directive. I don't want to have the latter type as it doesn't make sense to me and to top it all, this is to be integrated in an existing larger project, with other developers on it, that uses no element directive. This however does not mean that element directives are never to be used on the project.
Anyway, I'll keep looking for a solution. Thanks.
EDIT 3
I have resorted to using an ng-repeat directive in the template, just like #Suresh did. Using a developed template (i.e. repeating the input tag manually) does not work but I don't know whether that has to do with using/not using ng-repeat or rather with the way I "build" the values and labels in the controller.
Lessons learned from my plunk: even with a two-way binding over ngModel (below) in the controller of the widget:
all controls on the page will bind to the same value/variable unless ng-repeat is used
the parent controller's bound model is not updated if the template has data-ng-model="ngModel" instead of data-ng-model="$parent.ngModel"
scope: {
ngModel: "="
}

'use strict';
var app = angular.module('MyApp',[]);
app.directive("myRadiobutton", function () {
var templateHtml = function () {
return '<div class="form-group" >' +
'<label style="margin-right: 10px"; ng-repeat="(key, option) in options.valueList">' +
'<input type="radio" name="myfield" ng-value="option.value" ng-model="$parent.ngModel" ng-required="options.required" />{{option.title}}' +
'</label>' +
'</div>';
};
return {
scope: { options: '=', ngModel: '=' },
required: ['ngModel'],
restrict: 'E',
template: templateHtml,
};
});
app.controller('myController', function ($scope) {
$scope.radioGender = {
"label": "Gender",
"required": true,
"className": "",
"valueList": [
{
"title": "Male",
"value": "1"
},
{
"title": "Female",
"value": "2"
},
{
"title": "Others",
"value": "3"
}
]
};
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="MyApp" ng-controller="myController" >
<my-radiobutton options="radioGender" ng-model="genderValue"></my-radiobutton>
<span>{{genderValue}}</span>
</div>

You should use the "=" in the directive scope to bind an object:
directives.directive("dirname", function () {
return {
restrict: 'A',
replace: false,
scope: {
model: '=', // pass a referecne object
title: '#' // path as string
},
controller: function ($scope, $rootScope) {
...
},
}
});
<div dirname title="Menu Position" model="modelName" ></div>

Related

AngularJs : ng-model not binding to ng-checked for input type="radio", using with ng-repeat [duplicate]

This question already has answers here:
AngularJS: ng-model not binding to ng-checked for checkboxes
(7 answers)
Closed 3 years ago.
I am trying to use ng-repeat directive with track by expression, to show radio buttons, when I submit the value gets attached in the model and when I reopens the page using the values in model, the radio button does not show checked.
I have implemented same with plane string model + string values . But this time am trying with objects.
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js"></script>
<div ng-app="app" ng-controller="MyCtrl">
<form name="myForm">
<p>New TRY</p>
<ul>
<li ng-repeat="i in peopleNew.person">
<label>
{{i}}
<input type="radio" ng-model="peopleServer.person"
name="same" ng-value="i" />
</label>
</li>
</ul>
</form>
<div>
JS code
angular.module('app', [])
.controller('MyCtrl', ($scope) => {
$scope.peopleNew ={
person: {
"name": "Ringo",
"id": "R",
"subj": "Sci"
}
}
$scope.peopleServer= {
person: {"name":"Ringo"}
}
});
As per above, I should have 4 radio buttons on the screen, I am able to select 1 and submit. And then when I again open it in my model the person have the right value which was saved thorough ng-value but still on UI i don't see the radio button as checked for name Ringo should be checked. Model have:
$scope.peopleServer= {
person: {name:"Ringo"}
}
Tried Solutions
ng-checked expression , although I read that ng-model and ng-checked
should not be used together, ideally using model binding it should be chcked.
explanationI read about ,ng-repeat is not rendering properly so i tried to re render forcefully but did not work.
Removed ng-checked from the template still did not work.
Track by works for string values in ng-repeat.In ng-options it worked for object values also, but then its not input element but a select element
Someone help understand, when you reload or you already have the value in model,how the radio button be selected
angular.module('app', [])
.controller('MyCtrl', ($scope) => {
$scope.peopleNew ={
person: {
"name": "Ringo",
"id": "R",
"subj": "Sci"
}
}
//uncomment for testing.
$scope.peopleServer= {
person: {"name":"Ringo"}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js"></script>
<div ng-app="app" ng-controller="MyCtrl">
<form name="myForm">
<p>New TRY</p>
<ul>
<li ng-repeat="i in peopleNew.person">
<label>
{{i}}
<input type="radio" ng-model="peopleServer.person"
name="same" ng-value="i" />
</label>
</li>
</ul>
</form>
<div>
automatically ? all my tries above are not working am i missing something.
You have some syntax errors, missing model values and some unnecessary markup. First, you can get rid of the ng-checked altogether. That will be handled automatically if you set your ng-model and ng-value properly. Since your objects are unique there's no need for track by.
When using AngularJS directives (such as ng-value) you do not need to use braces to reference your model values. So ng-value="{{person}}" becomes ng-value="person".
You refer to myObj.person, but there doesn't appear to be a corresponding model value. Below is an edit of the code and markup you have provided showing that it really does work to use an object as the value for your radio buttons.
angular.module('app', [])
.controller('MyCtrl', ($scope) => {
$scope.people = [{
name: "John",
id: "J"
}, {
name: "Paul",
id: "P"
}, {
name: "George",
id: "G"
}, {
name: "Ringo",
id: "R"
}];
$scope.myObj = {
person: $scope.people[1]
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.7.2/angular.min.js"></script>
<div ng-app="app" ng-controller="MyCtrl">
<form name="myForm">
<p>Decisions</p>
<ul>
<li ng-repeat="person in people">
<label>
{{ person.name }}
<input type="radio" ng-model="myObj.person"
name="sameName" ng-value="person" />
</label>
</li>
</ul>
</form>
<div>
{{ myObj.person }}
</div>
</div>

Argument '' is not a function, got undefined in Kendo ( AngularJS )

I want to make a Kendo grid with 4 tabstrips, 4 children grids, 5 controllers, first is parent, others are children. Here is a part of code, with one parent and one child controller. Problem is that all the time I got an error "Argument '' is not a function, got undefined" Where should I define it? Everything is stored locally so the preview is not possible
Check this out:
http://fdietz.github.io/recipes-with-angular-js/controllers/sharing-models-between-nested-controllers.html
You don't nest the controllers in your javascript. This is from that link:
var app = angular.module("MyApp", []);
app.controller("MyCtrl", function($scope) {
$scope.name = "Peter";
$scope.user = {
name: "Parker"
};
});
app.controller("MyNestedCtrl", function($scope) {
});
Instead, you nest the controllers in your markup. I don't see where you are binding the controllers in your markup, btw.
<body ng-app="MyApp">
<div ng-controller="MyCtrl">
<label>Primitive</label>
<input type="text" ng-model="name">
<label>Object</label>
<input type="text" ng-model="user.name">
<div class="nested" ng-controller="MyNestedCtrl">
<label>Primitive</label>
<input type="text" ng-model="name">
<label>Primitive with explicit $parent reference</label>
<input type="text" ng-model="$parent.name">
<label>Object</label>
<input type="text" ng-model="user.name">
</div>
</div>
</body>
This is all from that link I provided.

How to get an element's attribute with AngularJS

I have the following code:
<div class="col-md-10" data-ng-controller="type-controller">
<div class="btn-group" data-toggle="buttons">
<label class="btn btn-success" ng-model="typeId" data-btn-radio="'1'">
Option 1
</label>
<label class="btn btn-success" ng-model="typeId" data-btn-radio="'2'">
Option 2
</label>
</div>
<input data-ng-model="typeId" name="typeId" type="hidden" data-start="2" />
</div>
My type-controller is empty so I'm omitting it - but I want to get the value of the attribute data-start from the last input inside the type-controller.
I'm not using jQuery.
IF the attribute data-start is significant because it is being used by some other 3rd party library, then you might consider simply using ng-init when you create this on the server:
<input data-ng-model="typeId" name="typeId" type="hidden" data-start="2"
ng-init='start = 2' />
This will essentially run any code you need, and doesn't involve you having to parse out data attributes from the DOM.
You could write a pretty trivial directive to pull in the value and publish using an expression. This will essentially accomplish the same thing, but is more difficult in my opinion:
angular.module('data-pluck', [])
.controller('fooController', function() {
this.name = 'Foo Controller';
})
.directive('pluckData', ['$parse',
function($parse) {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
var expression = function() {};
expression.assign = function() {};
scope.$watch(attrs.placeData, function() {
expression = $parse(attrs.placeData);
});
scope.$watch(attrs.pluckData, function() {
expression.assign(scope, attrs[attrs.pluckData]);
});
}
};
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='data-pluck' ng-controller='fooController as ctrl'>
<h1>{{ctrl.name}}</h1>
<div data-my-val="I'm value one" pluck-data='myVal' place-data='ctrl.valueOne'>
<p>I'm a regular old <code><p></code> tag</p>
<input type='hidden' data-my-val="I'm the second value" pluck-data='myVal' place-data='ctrl.valueTwo' />
</div>
<h3>These Values Populated Dynamically</h3>
<ul>
<li>ctrl.valueOne = {{ctrl.valueOne}}</li>
<li>ctrl.valueTwo = {{ctrl.valueTwo}}</li>
</ul>
</div>
Angular comes with jqLite built in, which still has the attr() function. But it's not the Angular "way" to be manually fiddling around in the DOM from a controller. Your scope should be the interface between them.
I'm curious as to why you have a value in an attribute in your UI that isn't defined first in your model / scope? How does this value get changed? Is there a reason why you can't set it in the controller:
$scope.start = 2;
and then:
<input data-ng-model="typeId" name="typeId" type="hidden" data-start="{{start}}" />
Can you explain a little about what data-start is meant to do?

Show a dropdown based on proper selected radio button

Question:
How do I show all radio buttons but only one dropdown. This shown dropdown is one that reflects the currently selected radiobtn?
Live Code
Html:
<form ng-app="app" ng-controller="Ctrl">
<div class="color-pick" ng-repeat="(key, val) in productData.colors_and_sizes.data">
<input type="radio" name="colors" ng-model="myColor" ng-value="{{key}}" />{{key}}
<div class="size-pick">
<select ng-model="mySize" ng-options="size for size in val.sizes.split(',')"></select>
</div>
</div>
</form>
javascript:
var app = angular.module("app", []);
app.controller('Ctrl', function ($scope, $filter, $http) {
$scope.productData = {
"colors_and_sizes": {
"data": {
"Black": {
"sizes": "X Small, Small, Medium, Large, Xlarge, XX Large"
},
"Blue": {
"sizes": "X Small, Small, Medium, Large, Xlarge, XX Large"
}
}
}
};
});
Use the ng-hide or show directive on the div wrapping the select statement. The problem you're probably running into is that each ng-repeat iteration creates it's own scope, this means that each ng-model you create is completely independent.
You can see this here: http://jsfiddle.net/v2r9y6x2/, when each select radio is clicked it prints a different value underneath itself, in both cases this is the value of ng-Model being set:
You can fix this by referring to the parent scope with $parent.property instead of just the property name. This will assign the value to the parent controllers scope and work as you expect.
http://jsfiddle.net/djwruftr/
<form ng-app="app" ng-controller="Ctrl">
<div class="color-pick" ng-repeat="(key, val) in productData.colors_and_sizes.data">
<input type="radio" name="colors" ng-model="$parent.myColor" ng-value="key" />{{key}}
<div class="size-pick" ng-show="$parent.myColor==key">
<select ng-model="$parent.mySize" ng-options="size for size in val.sizes.split(',')"></select>
</div>
</div>
myColor: {{myColor}}<br/>
mySize: {{mySize}}
</form>

AngularJS: Radio buttons do not work with Bootstrap 3

I have a radio button, which sets the value of True or False based on the value of transaction type
The demo can be found here
The problem is when I click on any of the radio button, the value of $scope.transaction.debit does not change
My javascript code is
var app = angular.module('myApp', []);
app.controller("MainCtrl", function($scope){
$scope.transaction = {};
$scope.transaction.debit=undefined;
console.log('controller initialized');
});
Please let me know what I am doing wrong.
Also, I do not want to use Angular-UI or AngularStrap for this purpose, unless no other option is available.
I modified dpineda's solution. You can use without removing bootsrap.js dependency. Also there is a working example here.
This is the flow:
Remove data-toggle="buttons" for preventing bootstrap execution.
Add some CSS for fixing the broken view (btn-radio css class)
Add some AngularJS logic for checked style effect.
html
<div class="btn-group col-lg-3">
<label class="btn btn-default btn-radio" ng-class="{'active': transaction.debit == '0'}">
<input type="radio" data-ng-model="transaction.debit" value="0"> Debit
</label>
<label class="btn btn-default btn-radio" ng-class="{'active': transaction.debit == '1'}">
<input type="radio" data-ng-model="transaction.debit" value="1"> Credit
</label>
</div>
<p>Transaction type: {{transaction.debit}}</p>
JavaScript
var app = angular.module('myApp', []);
app.controller("MainCtrl", function($scope) {
$scope.transaction = {
debit: 0
};
});
Style
.btn-radio > input[type=radio] {
position : absolute;
clip : rect(0, 0, 0, 0);
pointer-events : none;
}
I found the problem in bootstrap.js. Comment the line e.preventDefault(), it works.
// BUTTON DATA-API
// ===============
$(document)
.on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
var $btn = $(e.target)
if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
Plugin.call($btn, 'toggle')
e.preventDefault() //Before
//e.preventDefault() //After
})
.on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) {
$(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))
})
You have a large label stuck over the top of the radio buttons which prevents input to your radio buttons.
The html should read:
<input type="radio" data-ng-model="transaction.debit" value="True">Debit</input>
<input type="radio" data-ng-model="transaction.debit" value="False">Credit</input>
It then works, of course it may not look the way you want it to then.
if you remove de bootstrap code you can control the styles with conditionals
<label class="btn btn-default" ng-class="{'active': transaction.debit == 'some'}">
<input type="radio" data-ng-model="transaction.debit" name="debit" value="some"> Some
</label>
<label class="btn btn-default" ng-class="{'active': transaction.debit == 'other'}">
<input type="radio" data-ng-model="transaction.debit" name="debit" value="other"> Other
</label>
Here's a working version using a new directive:
html
<section ng-controller="MainCtrl">
<div class="form-group">
<label class="col-lg-2 control-label">Type</label>
<div class="btn-group col-lg-3" data-toggle="buttons">
<label class="btn btn-default" radio-button ng-model="transaction.debit" value="True">
Debit
</label>
<label class="btn btn-default" radio-button ng-model="transaction.debit" value="False">
Credit
</label>
</div>
<p>Transaction type: {{transaction.debit}}</p>
</div>
</section>
javascript
// Code goes here
var app = angular.module('myApp', []);
app.controller("MainCtrl", function($scope){
$scope.transaction = {};
$scope.transaction.debit=undefined;
console.log('controller initialized');
});
app.directive("radioButton", function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
element.bind('click', function () {
if (!element.hasClass('active')) {
scope.$apply(function () {
scope.transaction.debit = attrs.value;
});
}
});
}
};
})
Based on francisco.preller's answer I wrote two solutions trying to make it fit for generic use, without loosing the input tags:
html:
<label class="btn btn-info" radiobuttonlbl>
<input ng-model="query.gender" type="radio" value="0">male
</label>
solution #1:
.directive("radiobuttonlbl", function() {
return {
restrict: 'A',
link: function(scope, element, attrs, ctrl) {
element.bind('click', function () {
var input_elem = angular.element(element.find('input')[0]);
(function(o, s, v) {
s = s.replace(/\[(\w+)\]/g, '.$1');
s = s.replace(/^\./, '');
var a = s.split('.').reverse();
while(a.length>1) {
var k = a.pop();
o = o[k];
}
scope.$apply(function(){ o[a.pop()]=v;});
})(scope, input_elem.attr('ng-model'), input_elem.attr('value'));
});
}
};
})
Solution #2:
.directive("radiobuttonlbl", function() {
return {
restrict: 'A',
link: function(scope, element, attrs, ctrl) {
element.bind('click', function () {
var input_elem = angular.element(element.find('input')[0]);
input_elem.prop('checked',true);
input_elem.triggerHandler('click');
});
}
};
})
I have a feeling the first one is better because it make angular do the updating work.
If someone is still searching for an easy way to do this (I personally am hesitant to overload my code with directives), here is what I did:
You can set the value using ng-click on the label. Furthermore, notice the ng-init and active class on the label of the first radio item. This way, you can let bootstrap do its thing, and angular do its thing. The only drawback is you are not letting angular control this using ng-model.
<div class="btn-group col-lg-3" data-toggle="buttons">
<label class="btn btn-default active" ng-init="transaction.debit=true" ng-click="transaction.debit=true">
<input type="radio" checked> Debit
</label>
<label class="btn btn-default" ng-click="transaction.debit=false">
<input type="radio"> Credit
</label>
</div>
I had the same problem. Use ng-click on your labels and it will work fine with bootstrap
<label class="btn btn-default" ng-click="transaction.debit = 'debit'">
Here it's working in plunker
I have the same problem, in my case, the default style change and can't use angular ng-model inside any radio or checkbox button. So i read some articles and found that sometimes if you load JQuery after Bootstrap it overwrites any other instance of jQuery, and it prevent default styles and components to be loaded as bootstrap components, this also happens if you load angularJS after jQuery or viceversa.
PS.- My answer: Check your load script stack, play with it and find which order works for you. (first jquery, then angularJs, finally bootstrap). Usually you require to jQuery to be the first option, Angular and almost every new framework works on top of it. Cheers.

Categories