Issues indirectly updating ngModel from ngClick - angularjs

I have an ngRepeat that populates a list of customers, shown here :
<div ng-repeat="terr in terrData.data">
<div class="customer-row" ng-click="clickCustomerSelect(terr)">
<input class="customer-radio" type="radio" name="customer-select" ng-model="selectedCustomerRow" value="{{terr.customerID}}">
<div class="contact-data-column">{{terr.custNm}}</div>
<div class="primaryphone-data-column">{{terr.primaryPhone}}</div>
<!-- other customer data -->
</div>
</div>
There is a click event on the customer-row div that says if the row is clicked, the radio button for the row should be checked.
The basic controller logic shown here :
$scope.clickCustomerSelect = function(customer){
$scope.selectedCustomerRow = customer.customerID.toString();
//Other business logic
};
I see that whenever the customer row is clicked (not the radio button), the model gets properly updated, and the radio button corresponding to that value is checked. Note that this is expected functionality.
The issue i'm seeing is that if you check the radio button manually (i.e. not clicking the row), the model will no longer respond to updates from clicking the row.
I'm wondering if somehow once you select the radio button you're going outside of angular scope?
EDIT: Sample model
terrData.data = [{
"customerID": 1,
"companyName": "Name 1",
"custNm": "Blank",
"primaryPhone": "111-111-1111"
}, {
"customerID": 2,
"companyName": "Name 2",
"custNm": "Blank",
"primaryPhone": "111-111-1112"
}, {
"customerID": 3,
"companyName": "Name 3",
"primaryPhone": "111-111-1113"
}];

$scope.selectedCustomerRow = customer.customerID.toString();
Replace To:
$scope.selectedCustomerRow = customer.customerID;
onclick with it set selectedCustomerRow.
in HTML
<input class="customer-radio" type="radio" name="customer-select" ng-model="selectedCustomerRow" value="{{terr.customerID}}">
Replace To:
<input class="customer-radio" type="radio" name="customer-select" ng-model="$parent.selectedCustomerRow" value="{{terr.customerID}}">
DEMO

Likely your problem is this: https://stackoverflow.com/a/17607794/170407
Short answer: ng-model almost always needs a dot in it.

Related

Vue JS for updating checkboxes based on radio selection

I basically have the same question as this guy, but using Vue JS instead of jQuery.
I have a list of N groups bound to my array ensemble_groups and represented by radio buttons. Selected value is mapped to selected_group.
I have a list of actors bound to my array cast with the variables actor_id, actor_name and groups. Each actor is pre-assigned to any number of groups. They're represented by checkboxes and mapped to an array visible_actors (when checked).
Here's my Vue JS powering the above data (I imagine the method is all jacked up, and I probably need a computed property of some sort):
new Vue({
el: '#schedule-builder',
data: {
selected_group: 'Entire Cast',
visible_actors: [],
ensemble_groups: [
"Entire Cast",
"Leads",
"Dancers",
"Children",
"Deselect All",
],
cast: [
{
actor_id: "123",
actor_name: "Carl",
groups: ["Entire Cast","Leads",],
},
{
actor_id: "234",
actor_name: "Max",
groups: ["Entire Cast","Leads","Children",],
},
{
actor_id: "345",
actor_name: "Sheryl",
groups: ["Entire Cast","Dancers",],
},
{
actor_id: "456",
actor_name: "Chip",
groups: ["Entire Cast","Children",],
},
],
},
methods: {
selectGroup: function() {
// uncheck all
visible_actors=[];
// add people in this group to visible_actors
for person in cast {
if (person.groups.indexOf(selected_group) > -1) {
visible_actors.push(person.actor_id);
}
}
}
})
When a user clicks on a radio button for a group, I want to select only the actors' checkboxes who are in that group. So if the user selects "Children", only the actors in the "Children" group should be checked (Max and Chip, per my example).
And, obviously, checking an actor (rather than a group) shouldn't affect the rest of the selections. I mention this because I got it partially working at one point, where selecting a group also selected the correct people, but when I clicked on a person suddenly everyone else was deselected. User can click either a group OR a person, and the expected behavior is that
Here's my HTML template:
<div id="schedule-builder">
<div class="select-groups">
<h3>Ensemble Groups</h3>
<template v-for="group in ensemble_groups">
<input name="select_group[]" id="group_#{{ $index }}"
v-model="selected_group"
:value="group"
#click="selectGroup"
type="radio">
<label for="group_#{{ $index }}">#{{ group }}</label>
</template>
</div>
<div class="select-individuals">
<h3>Cast Members</h3>
<template v-for="person in cast">
<input name="actors[]" id="actor-#{{ $index }}"
v-model="visible_actors"
:value="person.actor_id"
:checked="visible_actors.indexOf(person.actor_id) > -1"
type="checkbox">
<label for="actor-#{{ $index }}">
#{{ person.actor_name }}
</label>
</template>
</div>
</div>
Any help is appreciated... I've been banging my head on it for a couple days already.
This is a tough question to answer well but I'll try.
I would not rely on a computed property for the checked state. Let v-model handle that for you. You can do
<input
type="checkbox"
name="actors[]"
v-model="selected_actors"
:value="actor">
and that will manage the array of selected_actors for you as their values change.
I'm at work and plan on elaborating on this answer a little later but here's a fiddle of how I'd approach the situation: https://jsfiddle.net/crswll/806shzzg/7/

How to remove initial blank option and select <option> value when using ng-repeat or ng-options?

Angular newbie. Filtering json data returned via dataservice, and filter is done via dropdown. What i would like to do is a combination of things:
I would like to remove the initial blank value returned via Angular;
I would also like to have it pre-select a value by default that is not
included in my data object (in my case, returning all objects, added as a select option via my html); and
I would like to have that initial default value displayed when the
page is rendered.
I can remove the initial blank, but can't get that to incorporate the option I'm including.
I've looked into doing this with ng-repeat and ng-options, and can set the default value to one of the objects in my json via ng-options, but that doesn't seem to allow me to add in an option that doesn't exist in the data. Since I don't have a group that returns all values (and think it'd be best not to, since it that will make my data file much larger), adding that in as an option seemed the best way to do it.
There are many threads out there on how to remove the blank value returned for Angular (e.g. Empty first element in dropdown list and
Angular JS Remove Blank option from Select Option, etc.). I also see how to pre-select a value via ng-init, but that seems to require the value be a part of my data object, and not an option added in post-hoc via select option as I've done. Further, I see how to pre-select an empty data value via ng-options (e.g. http://ng-learn.org/2013/11/Draw_a_select_with_default_option/) but that doesn't seem to allow me to add in a data value which would return all data objects. When I add ng-selected='true' to my option data value, it appears to be selected when the page loads, but then that value disappears and the select dropdown is rendered as ng-pristine per developer tools. Not sure what I'm missing.
Working plunk here. As is, it has the initial blank, although again, you can see that "Show All" is displayed in the drop down when the page first loads but that it goes away. Again, I would like to remove the blank and select the option as the default value, and have that displayed in the dropdown so people know that's being rendered.
Thanks for any assistance!
html:
<div>
<div>
<form action="">
Group:
<select name="groups" id="grp" ng-model="groupid" ng-change="selectGroup()">
<option value="0" ng-selected="true">Show All</option>
<option ng-repeat="option in groups" value="{{option.ID}}" ng-selected="$first">{{option.name}}
</option>
</select>
</form>
</div>
<div>
<form action="">
Asset:
<select name="assets" id="assets" ng-model="selectedAsset">
<option ng-repeat="option in assets | filter: myFilter" value="{{option.ID}}">{{option.driverName}}</option>
</select>
</form>
</div>
</div>
filter:
$scope.myFilter = function(val, i, a) {
if($scope.groupid==0){
return true;
};
var m = false;
var grp = angular.fromJson($scope.selectedGroup);
angular.forEach(grp.members, function(v, k){
if(val.vpkDeviceID == v.vpkDeviceID){
m = true;
}
}, m);
return m;
};
$scope.selectedAsset = {};
$scope.groupid = 0;
$scope.selectedGroup = {};
$scope.selectGroup = function(){
var grp = [];
angular.forEach($scope.groups, function(v,i){
if($scope.groupid == v.ID){
$scope.selectedGroup = v;
}
})
}
json:
{
"assets": [
{
"deviceName": "vehicle 25",
"vpkDeviceID": 352964050744425,
"driverName": "Mike Smith"
},
{
"deviceName": "vehicle 52",
"vpkDeviceID": 352599041910352,
"driverName": "John Doe"
},
{
"deviceName": "vehicle 11",
"vpkDeviceID": 862170016156711,
"driverName": "Sarah Johnson"
},
{
"deviceName": "vehicle 28",
"vpkDeviceID": 862193020453528,
"driverName": "Eleanor Petit"
}
],
"groups":
[
{"ID": 1, "name": "nairobi", "members": [{"vpkDeviceID": 352964050744425}, {"vpkDeviceID": 352599041910352}, {"vpkDeviceID": 862170016156711}]},
{"ID": 2, "name": "karen", "members": [{"vpkDeviceID": 352964050744425}, {"vpkDeviceID": 352599041910352}]},
{"ID": 3, "name": "langata", "members": [{"vpkDeviceID": 352599041910352}, {"vpkDeviceID": 862170016156711}, {"vpkDeviceID": 862193020453528}]},
{"ID": 4, "name": "downtown", "members": [{"vpkDeviceID": 352964050744425}, {"vpkDeviceID": 862170016156711}]},
{"ID": 5, "name": "westlands", "members": [{"vpkDeviceID": 862193020453528}]}
]
}
The solution is simple: use ng-options, and bind to an object rather than an ID. Add a single option with a blank value to the select in order to display your "Show all" option.
Here's an updated plunkr: http://plnkr.co/edit/E8I6D6Ti3Kzw1eukuRBQ?p=preview
Key parts:
<select name="groups" id="grp" ng-model="selectedGroup" ng-options="group.name for group in groups">
<option value="">Show All</option>
</select>
$scope.myFilter = function(val, i, a) {
if(!$scope.selectedGroup){
return true;
}
var m = false;
var grp = $scope.selectedGroup;
angular.forEach(grp.members, function(v, k){
if(val.vpkDeviceID == v.vpkDeviceID){
m = true;
}
}, m);
return m;
};
No need to use ng-repeat. No need for a selectGroup() function. No need for ng-selected. No need for angular.toJson(). No need for a groupid in the scope.

Dropdown depends on other dropdown - angularjs

I'm having data in the form given below
var servers = [
{
"name": "server1",
"version":
[
"10.x"
]
},
{
"name": "server2",
"version":
[
"1", "2"
]
}
]
I want to have two drop down.
First dropdown will display "name".
When user selects name from the first dropdown, second dropdown should be populated with corresponding "version".
Non-working jsfiddle link: http://jsfiddle.net/fynVy/174/
You need to tweak your HTML template, so that the first drop down is displaying the server name, and that the options for the 2nd drop down are based upon the versions in the selected drop down (ngModel of the first drop down).
<div ng-controller="MyCntrl">
<select ng-model="server" ng-options="x.name for x in servers"></select>
<select ng-model="version" ng-options="val for val in server.version"></select>
</div>
working fiddle here

AngularJS custom directive within ng-repeat with dynamic attributes and two way binding

I'm banging my head on the wall over this for days and finally decided to post this question since I can't find an answer that matches what I'm trying to do.
Context: I'm building a dynamic form building platform that describes form elements in a JSON structure like this -
{
"name": "email",
"type": "email",
"text": "Your Email",
"model": "user.profile.email"
}
And then in the View I have a recursive ng-repeat that includes the field template like this -
<script type="text/ng-template" id="field.html">
<div ng-if="field.type === 'email'" class="{{field.class}}">
<p translate="{{field.text}}"></p>
<input type="{{field.type}}" name="{{field.name}}" class="form-control" dyn-model="{{field.model}}">
</div>
</script>
As you see, I use a custom directive dynModel to create the ng-model attribute with interpolated value of the model from the string value. So far do good.
Now I have a more complex scenario in which I have a collection of fields that can be added or removed by clicking on Add button or removeMe button. See below -
{
"name": "urls",
"type": "collection",
"text": "Your Profile URLs",
"model": "user.profile.urls",
"items": [
{
"name": "url",
"type": "url",
"text": "Facebook URL",
"model": "url"
},
{
"name": "url",
"type": "url",
"text": "Facebook URL",
"model": "url"
}
],
"action_button": {
"name": "add",
"type": "action",
"action": "addURL"
}
}
<div ng-if="field.type === 'collection'">
<button class="btn btn-info" dyn-click click-action="{{field.action_button.action}}" click-model="{{field.model}}">{{field.action_button.text}}</button>
<div dyn-ng-repeat="item in {{field.model}}" >
<div ng-repeat="field in field.items" ng-include src="'field.html'"></div>
</div>
</div>
As you'll notice, I have another custom directive that takes care of interpolation of {{field.model}} from the previous ng-repeat (not shown).
Now to the crux of the issue. As you see in the template, I have nested ng-repeats, the first one iterates through user.profile.urls and the second one iterates through the field parameters in JSON and creates the HTML tags, etc. One of those fields is a button (action_button) that is used to add more URLS to the list. When I click the button, I want it to trigger a function in my controller and effectively add a new child to the parent model (user.profile.urls). I then also want each URL, existing and new to have a remove button next to them that will be dynamic and will remove that particular item from the model.
If you see the code above, I have a custom directive dyn-click that reads in the
click-action="{{field.action_button.action}}"
That contains the function name (addURL) to be called that resides in my controller and the model
click-model="{{field.model}}"
(user.profile.urls) to which the new item is to be added. This is not working. The reason for this complexity is that I have multiple levels of nesting and at each level there are dynamic elements that need to be interpolated and bound. The directive dyn-click looks like this right now -
exports = module.exports = function (ngModule) {
ngModule.directive("dynClick",function() {
return {
restrict: 'A',
link: function(scope,element,attrs) {
$(element).click(function(e, rowid){
scope.clickAction(scope.clickModel, scope.$index);
});
}
};
});
};
With this code, when I click on the rendered form's Add button, the code in the $(element).click method above gets executed giving the following error -
Uncaught TypeError: undefined is not a function
I have tried a few different things with scope:{} in the dyn-click directive, with different errors and none of them have worked completely with two way binding of the model and calling the function as expected.
Help!
EDIT-1 - please see the comments:
$(element).click(function(e, rowid){
scope.$eval(attrs["clickAction"])(scope.$eval(attrs["clickModel"]), scope.$index);
});
EDIT-2: The plunker is here - http://plnkr.co/edit/DoacjRnO61g4IYodPwWu?p=preview. Still tweaking it to get it right, but you guys should be able to see the necessary pieces. Thanks!
EDIT-3: Thanks Sebastian. The new plunker is here - http://plnkr.co/edit/Z6ViT7scubMxa17SFgtx?p=preview . The issue with the field.items ng-repeat still exists. For some reason the inner ng-repeat is not being executed. Any ideas? Josep, Sebastian?

Unselected radio button value in Angularjs

I've got an angularjs application that has a form/controller that look essentially like this (boiled down to the pertinent stuff):
angular.module('testApp', [])
.controller('testCtrl', function ($scope) {
$scope.envelopes = [{
id: 1,
name: 'first',
default_spend: '1'
}, {
id: 2,
name: 'second',
default_spend: '0'
}, {
id: 3,
name: 'third',
default_spend: '0'
}, ];
});
And a form that looks roughly like this:
<div ng-app="testApp">
<div ng-controller="testCtrl">
<div ng-repeat="envelope in envelopes">
<div>{{envelope.name}}
<input type="radio" name="default_spend" ng-model="envelope.default_spend" ng-value="1" />
Default Spend: {{envelope.default_spend}}
</div>
</div>
</div>
</div>
You can see this in action with this fiddle.
As you can see, the first envelope is marked as the default_spend envelope and the other two aren't. When I select a different envelope, that envelope also gets marked as the default_spend, but when the radio button is unselected, the model value stays the same ("1"). I understand that I'm dealing with a child scope here due to ng-repeat, but is there a way for me to set an "unselected" value without having to jump through hoops with ngChange?
Not really. When you use ng-value it is what is going to get bound to the ng-model and in your case all of them are having value 1. Also i really did not get the purpose of toggling 1 and 0, however you could just achieve it this way:-
<input type="radio" name="default_spend"
ng-click="selected.envelope = envelope" /> <!--Register an ng-click and set selected one
Default Spend: {{getDefSpend(envelope)}}</div> <!-- Show the text based on selection-->
And in your controller:-
$scope.selected = {envelope: $scope.envelopes[0]};
$scope.getDefSpend = function(envelope){
return $scope.selected.envelope === envelope ? 1 : 0;
}
//$scope.selected.envelope will be your selected option at any point.
Demo
It's because what you have is actually 3 different model values. To work as you want it, you would have ONE model value for all 3. You can either restructure your data, or use ng-change to modify your model manually.

Resources