Nested checkboxes in AngularJS resource with external options - angularjs

I have this data structure, search:
{
id: '1',
name: 'Foo'
service_ids:
[
3,
8,
12
]
}
I then have another data structure, services, that matches the ids from service_ids above with the below:
[
{
id: 3,
name: 'Fighter'
},
{
id: 4,
name: 'Typhoon'
},
{
id: 8,
name: 'Kung'
},
{
id: 12,
name: 'Builder'
}
]
I want to display this in a form using AngularJS. The name is fine. I want to display all possible services as checkboxes and if the search has one of the services checked then it is ticked in the checkbox. Something like:
<li ng-repeat="search in searches">
<input ng-model="search.name">
<ul>
<li ng-repeat="service in services">
<input type="checkbox">
{{service.name}}
</li>
</ul>
</li>
I don't know how to link that checkbox to the service_ids and the services. Any help appreciated.
I am using $resource.

Update
Ignore my answer, misread you nesting request.
If anyone needs nesting guidance see below:
The best way to nest in angular that I have found is to create a template which is aware of child items like this:
<script type="text/ng-template" id="tree_item_renderer.html">
<span>{{tag.Name}}</span>
<ul ng-show="tag.Children.length > 0">
<li ng-repeat="tag in tag.Children" ng-include="'tree_item_renderer.html'" ></li>
</ul>
</script>
<ul class="tag-list">
<li ng-repeat="tag in tags" ng-include="'tree_item_renderer.html'" ></li>
</ul>

If I understand your question it should be as easy as:
<input type="checkbox" ng-model="search.service_ids[service.id]">
And you should also get bi-directional binding.
Updated
Just realized search.service_ids is an array not a map. So the solution above is not accurate.
You might want instead to use a filter:
<input type="checkbox" ng-checked="search.service_ids | contains:service.id">
Then
filter('contains', function() {
return function(haystack, needle) {
return haystack.indexOf(needle) >= 0;
}
});

Related

AngularJS ng-repeat with radio inputs

I have a list of options as an enum:
var Options = {
Option0: 0,
Option1: 1,
Option2: 2,
Option3: 3,
Option4: 4
};
And a sub-list of available options:
var availabledOptions = [
Options.Option0,
Options.Option2,
Options.Option4
];
I then try to show the list of available options as radio inputs but it doesn't work and I'm wondering why. $scope.selectedOption is not updated.
function main($scope) {
$scope.availabledOptions = availabledOptions;
$scope.selectedOption = Options.Option2;
}
</script>
<div ng-app ng-controller="main">
<ul>
<li ng-repeat="option in availabledOptions">
<input name="options" type="radio" ng-model="selectedOption" ng-value="option">option #{{option}}
</li>
</ul>
selected option: option #{{selectedOption}}
</div>
http://jsfiddle.net/1xgsqL67/
I tried ng-repeat="option in availabledOptions track by $index" and also without the name attribute, but it still doesn't work.
Thanks in advance!
Try the following:
ng-model="$parent.selectedOption"
My understanding is that ng-repeat creates it's own scope, and you have to go up one level.

How to escape an ng-Repeat loop?

I have the following ng-repeat loops that unpack JSON data into an Accordion:
<accordion close-others="oneAtATime">
<accordion-group ng-repeat="service in userPF.custom.services">
<accordion-heading><input type="checkbox" name="status" disabled>On <input type="checkbox" name="status" disabled checked>Off {{service.name}}</accordion-heading>
Related Items:<br>
<div class="secondary" ng-repeat="fields in userPF.custom.fields">
<span ng-show="checkForMatch(service.name, fields.services)">
<span ng-repeat="value in fields.values">
{{value.name}}<br>
</span>
</span>
</div>
</accordion-group>
</accordion>
The first trip through the loop {{service.name}} will always return data but the second value {{value.name}} might not. If it doesn't I don't want the div or span to render, just the heading. I've tried some variations on ng-if but haven't come close to being able to tell if I'm getting data back or not.
Refer this Link.
AS this code shows and its working fine you can have ng-show inside ng-repeat.
<div ng-app ng-controller="RepeatTestCtrl">
<ul ng-repeat="item in items">
<li ng-show="item.id.length == 0">{{item.name}} is even</li>
<li ng-show="item.id % 2 == 1">{{item.name}} is odd</li>
</ul>
</div>
function RepeatTestCtrl($scope) {
$scope.items = [
{ id: 1, name: 'test 1' },
{ id: 2, name: 'test 2' },
{ id: 3, name: 'test 3' },
{ id: 4, name: 'test 4' }
];
}
http://jsfiddle.net/b004t06z/41/

AngularJS filter nested ng-repeat based on repeated object properties

I have an array of restaurant objects and I want to list them by grouping their cities
My object is like;
restaurant = {
id: 'id',
name: 'name',
city: 'city'
}
This HTML Markup can give some info about what I want to do.
<div ng-repeat="restaurant in restaurant | filter: ???">
<div class="header">
<h1 ng-bind="restaurant.city"></h1>
<a>Select All</a>
</div>
<div class="clearfix" ng-repeat="???">
<input type="checkbox" id="restaurant.id" />
<label ng-bind="restaurant.name"></label>
</div>
</div>
Can I do it with one single array or do i need to create seperate city and restaurant arrays to do it?
If you want to group restaurants by city, you can use groupBy of angular.filter module.
Just add the JS file from here: http://www.cdnjs.com/libraries/angular-filter to your project and use following code.
var myApp = angular.module('myApp',['angular.filter']);
function MyCtrl($scope) {
$scope.restaurants = [
{id: 1, name: 'RestA', city: 'CityA'},
{id: 2, name: 'RestB', city: 'CityA'},
{id: 3, name: 'RestC', city: 'CityC'},
{id: 4, name: 'RestD', city: 'CityD'}
];
}
<div ng-controller="MyCtrl">
<ul ng-repeat="(key, value) in restaurants | groupBy: 'city'">
<b>{{ key }}</b>
<li ng-repeat="restaurant in value">
<i>restaurant: {{ restaurant.name }} </i>
</li>
</ul>
</div>
I've created JSFiddle for you with working example.

How do I only show an element if nested ng-repeat is not empty?

I have a List of lists, created with a nested ng-repeat. Each outer ng-repeat contains a div with the label of its inner list (eg: "Group A"). I'm now trying to create a way to avoid showing this label if the inner list is empty due to filtering(Applied by an input searchtext)
Here is a plunker explaining my issue and my attempted solution : Plnkr
Having a 'heavy' function like isGroupEmpty seems extremely cumbersome - Is there any way to do this in a much simpler fashion? I was toying with the idea of moving the label inside the inner ng-repeat and having ng-show="$first" but it doesnt look great
I ended up with the following solution which worked perfectly. Plnkr
By setting a variable in the inner ng-repeat I was able to evaluate ng-show based on this variables length like so :
<input ng-model='searchText'/>
<span ng-show='filtered.length > 0'>
<ul>
<li ng-repeat='el in filtered = (model | filter:searchText)'>
<div>{{el.label}}</div>
</li>
</ul>
</span>
you could leverage ng-init, that way you'll call the filter only once:
<div ng-repeat='(key,group) in model'>
<div ng-init="filtered = (group | filter:filterFn)"></div>
<div ng-show="filtered.length !== 0">
<div>{{key}}</div>
<ul>
<li ng-repeat="el in filtered">
<div>{{el.label}}</div>
</li>
</ul>
</div>
</div>
usually it is not a good practice to use ng-init out of no where, but I guess it solves calling the filter twice.
Another way is to use the filter through javascript - you could inject $filter and retrieve 'filter' $filter('filter') in your controller, calling it with group as its first argument, the filterFn as its second, and store its result in your scope.
I used the following:
<ul>
<li ng-repeat="menuItem in menuItems"><span class="fa {{menuItem.icon}} fa-lg"></span>{{menuItem.itemName}}
<span ng-show='menuItem.subItems.length > 0'>
<ul>
<li ng-repeat="subItem in menuItem.subItems">{{subItem.itemName}}</li>
</ul>
</span>
</li>
checking if an array has a length of 0 is not an expensive operation. if you want to only show lists that have item, put a filter on the outer array that takes an array of arrays and returns only the arrays that have a length different than 0.
you can also hide the inner div if the array == false.
http://plnkr.co/edit/gist:3510140
http://plnkr.co/edit/Gr5uPnRDbRfUYq0ILhmG?p=preview
Your plunkr was pretty complicated and hard to weed through so I re-created what you wanted using a fiddle. The general idea behind my approach is to filter out the items from the array, not the sub array. And only do the filtered items when the text changes. So here's the markup:
<div ng-app="app">
<div ng-controller="ParentCtrl">
<input data-ng-model="filterText" data-ng-change="updateTypes()" />
<div data-ng-repeat="type in filteredTypes">
{{ type.name }}
<ul>
<li style="margin-left:20px;" data-ng-repeat="entry in type.entries">
- {{ entry.name }}
</li>
</ul>
</div>
</div>
</div>
And here's the code:
angular.module('app', [])
function ParentCtrl($scope){
$scope.filterText = "";
$scope.types = [
{ name: "type1", entries: [{ name: "name1"}, { name: "name2"}, { name: "name3"}]},
{ name: "type2", entries: [{ name: "name1"}, { name: "name3"}, { name: "name3"}]},
{ name: "type3", entries: [{ name: "name1"}, { name: "name2"}, { name: "name5"}]},
{ name: "type4", entries: [{ name: "name4"}, { name: "name2"}, { name: "name3"}]}
];
$scope.filteredTypes = [];
$scope.updateTypes = function(){
$scope.filteredTypes.length = 0;
for(var x = 0; x < $scope.types.length; x++){
if($scope.filterText === ""){
$scope.filteredTypes.push($scope.types[x]);
}
else{
var entries = [];
for(var y = 0; y < $scope.types[x].entries.length; y++){
if($scope.types[x].entries[y].name.indexOf($scope.filterText) !== -1){
entries.push($scope.types[x].entries[y]);
}
}
if(entries.length > 0){
$scope.filteredTypes.push({
name: $scope.types[x].name,
entries: entries
});
}
}
}
}
$scope.updateTypes();
}
Fiddle: http://jsfiddle.net/2hRws/
The reason I'm creating a new array and not using an actual filter to remove the results is that angular doesn't like creating dynamic arrays on the fly in filters. This is because it doesn't assign $$hashKey and things just don't line up correctly when dirty checking. I got the idea of how to do what you needed from this topic on the matter: https://groups.google.com/forum/#!topic/angular/IEIQok-YkpU
I have only slightly modified your list-widget.html, see it in action: plunkr
The idea is simple - use the same filter for ng-show:
<div ng-show="group | filter:searchText">{{ key }}</div>
The label will be visible only if there are some unfiltered items.
In my example I'm using searchText for filter because I'm not familiar with CoffeeScript.

Creating a dynanic form in AngularJS with nested lists

I have a model in AngularJS that is structured like so:
region = { 'substances': [
{ 'id': 123, 'name': 'Hello', 'versions': ['A', 'B', 'C'] },
{ 'id': 456, 'name': 'World', 'versions': ['A', 'B', 'C'] }
]}
I want to be able to display and modify this model in a form. Currently I have nested ng-repeats:
<ul>
<li ng-repeat="substance in region.substances">
<input name="substance[]" class="input-medium" type="text" ng-model="substance.name">
<ul>
<li ng-repeat="version in substance.versions">
<input style="margin-left: 10px;" type="text" name="<% substance.id %>.version[]" class="input-medium" ng-model="version">
</li>
</ul>
</li>
</ul>
(Note: I've redefined AngularJS brackets to be <% %>).
I can modify the name, but I AngularJS prevents me from even typing in the inner inputs. I'm guessing that I'm not binding to the model correctly? Also, how would I go about adding another "substance" that has a name and a list of versions?
Is there a proper way to be naming my inputs?
As suggested by #Yoshi , is always easiest to use object inheritance rather than primitive within nested scopes.
$scope.region = { substances: [
{ name: 'Hello', versions: [{x:'A'},{x: 'B'},{x: 'C'}] },
{ name: 'World', versions: [{x:'A'},{x: 'B'},{x: 'C'}] }
]};
<ul>
<li ng-repeat="substance in region.substances">
<input class="input-medium" type="text" ng-model="substance.name">
<ul>
<li ng-repeat="version in substance.versions">
<input type="text" ng-model="version.x">
</li>
</ul>
</li>
</ul>
Demo

Resources