Angularjs ng-repeat: iterate over a special object fields - javascript

I have an object:
var options = {
NAME_1 : "Name 1",
TEXT_1 : "Description goes here",
NAME_2 : "Name 2",
TEXT_2 : "Description2 goes here",
};
Is it possible to iterate over the fields of the object inside the ng-repeat?
E.g.
<div ng-repeat="item in options">
<span class="title">{{item.name}}</span>
<span class="text">{{item.text}}</span>
</div>
I will greatly appreciate any advice.
Thanks in advance.

Absolutely! Use the following syntax:
<div ng-repeat="(k, v) in options">
As you can guess, k is the key and v is the value of the key.

this is not going to work as you imagine, perhaps your object can look like this,
var options = [
{
name : "Name 1",
text : "Description goes here"
},
{
name : "Name 2",
text : "Description2 goes here"
}
];
now you can iterate just as you want it.
Or, you can I guess iterate over all your values in the object but you need to keep in mind that these values are not really related to each other, besides just being in the same object.
This is the solution with ng-if and iterate over the object. But as I mentioned, javascript iterates over objects not in order so your best bet to reorder your properties in an array and then iterate again. This below iterate as NAME_1, NAME_2, TEXT_1, TEXT_2
app.controller('MainCtrl', function($scope) {
var options = {
NAME_1 : "Name 1",
TEXT_1 : "Description goes here",
NAME_2 : "Name 2",
TEXT_2 : "Description2 goes here",
};
$scope.options = options;
$scope.check = function(title, key) {
if(key.split('_')[0] === title) {
return true;
}
}
});
<div ng-repeat="(k,v) in options|orderBy:k">
<span ng-class="title" ng-if="check('NAME',k)">{{k}}</span>
<span ng-class="text" ng-if="check('TEXT',k)">{{k}}</span>
</div>
COMPLETE SOLUTION:
If you also use underscore, you can do this.
<div ng-repeat="x in optionsArray">
<span ng-class="title" ng-if="check('NAME',x)">{{x[1]}}</span>
<span ng-class="text" ng-if="check('TEXT',x)">{{x[1]}}</span>
</div>
app.controller('MainCtrl', function($scope) {
var options = {
NAME_1: "Name 1",
TEXT_1: "Description goes here",
NAME_2: "Name 2",
TEXT_2: "Description2 goes here",
};
$scope.options = options;
$scope.optionsArray = _.pairs(options);
$scope.check = function(title, key) {
if (key[0].split('_')[0] === title) {
return true;
}
}
});

Related

"keys is not defined" in AngularJS's custom filter function [duplicate]

This question already has answers here:
Pro AngularJS - Could you help explain part of this code?
(2 answers)
Closed 6 years ago.
I was going through examples in one of Angular JS books that I have and ran into something I do not clearly understand. It has to do with custom filter and ng-repeat. Here are the codes
<a ng-click="selectCategory()" class="btn btn-block btn-default btn-lg">
Home
</a>
<a ng-repeat="item in data.products | orderBy: 'category' | unique: 'category'" ng-click="selectCategory(item)" class="btn btn-block btn-default btn-lg">
{{item}}
</a>
The following code is the controller attached to html body tag.
angular.module("sportsStore").controller("sportsStoreCtrl", function ($scope) {
$scope.data = {
products: [
{
name: "Product #1",
description: "A product",
category: "Category #1",
price: 100
},
{
name: "Product #2",
description: "A product",
category: "Category #1",
price: 100
},
{
name: "Product #3",
description: "A product",
category: "Category #2",
price: 210
},
{
name: "Product #4",
description: "A product",
category: "Category #3",
price: 202
}
]
};
});
The code for the custom filter is
angular.module("customFilters", []).filter("unique", function () {
return function (data, propertyName) {
if (angular.isArray(data) && angular.isString(propertyName)) {
var results = [];
var keys = {};
for (var i = 0; i < data.length; i++) {
var val = data[i][propertyName];
if (angular.isUndefined(keys[val])) {
keys[val] = true;
results.push(val);
}
}
return results;
} else {
return data;
}
}
});
What the custom filter is supposed to do is basically create a list of categories in $scope.data.products.
The code is working fine. What I do not understand is the role played by "var keys = {};" in the custom filter functions.
What is the intention for having variable "keys" and setting its properties' value to true?
The filter is executing on the data provided by $scope.data and the property "category". It checks for unique categories, only showing the first instance of a category.
As the loop executes, it is reading the value for the "category" key on each index of data.
The keys object acts as a tracker to track what values have been added to the results array.
By setting a value on the keys object, it makes it so if that value is encountered again, it will not satisfy the if condition and not add it to the results array.
In other words it executes on Product #1, reads the category as Category #1, adds it to the results array and creates a key["Category #1"] value of true.
Next, Product #2, it extracts the category value of "Category #1". Category #1 is already on the key object, so it will not add Product #2 to the result.
Next, Product #3, it extracts the category value of "Category #2", sees that it is not in the keys object, so it adds the "Category #2" to the results.
The resulting display should be the Product #1, #3, and #4 info if I am not mistaken.
By removing the keys[val] = true, it will cause all of the products to appear.

AngularJS showing value based on id

How can I change data based on id that is passed from json file.
JSON:
{
"hotels": [
{
"id" : 1,
"name": "some hotel 1",
"category" : [{
"id" : 1,
"hotel_id" : 1,
"name" : "Cat name 1",
"bnb" : "yes",
"simple" : "yes"
}]
},
{
"id" : 2,
"name": "some hotel 2",
"category" : [{
"id" : 1,
"hotel_id" : 2,
"name" : "Cat name 1",
"bnb" : "yes",
"simple" : "yes"
}]
}
]
}
in my html I have ng-repeat like:
<p>Hotel names</p>
<ul>
<li ng-repeat="hotel in list.hotels">
{{hotel.name}}
<ul class="thumbnails">
<p>Category</p>
<li ng-repeat="cat in hotel.category">
{{cat.name}}
</li>
</ul>
</li>
</ul>
So this will show all what I have in that json file and I'm trying to limit it to show only data for one hotel (I know that I can do it with something like {{hotel[0].name}}) but there must be better approach, and also how can I use some kind of a switch by pressing the button to show data from hotel with id 1 to hotel with id 2 in the same div and vice versa?
Thank you.
You could use ng-repeat to create the links to display the hotel based on the click like in the following demo or in this fiddle.
For the categories you can use another ng-repeat (not added in the demo).
angular.module('demoApp', [])
.controller('mainController', MainController);
function MainController() {
this.hotels = [
{
"id" : 1,
"name": "some hotel 1",
"category" : [{
"id" : 1,
"hotel_id" : 1,
"name" : "Cat name 1",
"bnb" : "yes",
"simple" : "yes"
}]
},
{
"id" : 2,
"name": "some hotel 2",
"category" : [{
"id" : 1,
"hotel_id" : 2,
"name" : "Cat name 1",
"bnb" : "yes",
"simple" : "yes"
}]
}
];
this.selectedHotel = this.hotels[0];
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="demoApp" ng-controller="mainController as mainCtrl">
{{hotel.name}}
<div>
Hotel name: {{mainCtrl.selectedHotel.name}}
Category: {{mainCtrl.selectedHotel.category}}
</div>
</div>
To answer your question, notice ng-if added
<p>Hotel names</p>
<ul>
<li ng-repeat="hotel in list.hotels">
{{hotel.name}}
<ul class="thumbnails">
<p>Category</p>
<li ng-repeat="cat in hotel.categories" ng-if="cat.id == yourid">
{{cat.name}}
</li>
</ul>
</li>
</ul>
You can change yourid by using the current controller. And as other people suggest, you shouldn't use ng-repeat if you want to display only one element

ng-options nested in ng-repeat

I'm having the angular feature issue where my select list has an empty first option, but this situation is a little different from the research I've done online. When I place the select tag outside of the ng-repeat, there is no blank option as the default selected value. When I place the select tag using the ng-option attribute within the ng-repeat, I have the blank issue. I've tried setting the default value for the ng-model attribute on the select tag with no luck. Here is the html fragment:
<tr ng-repeat="item in todo.items">
<td>{{item.project}}</td>
<td>{{item.action}}</td>
<td>
<select ng-model="ttdSelect" ng-change="moveItem(item.id, ttdSelect);" ng-options="option.name for option in todo.options track by option.name">
</select>
</td>
</tr>
javascript:
var items = [{"id" : 1, "name" : "ttd" , "action" : "do it"}];
var selectOptions = [{ "name" : "next", "value" : "nextUp"},
{ "name" : "in progress", "value" : "inProgress"},
{ "name" : "waiting", "value" : "waiting"},
{ "name" : "done", "value" : "done"},
{ "name" : "trash", "value" : "trash"}];
app.controller("appController", function ($scope)
{
$scope.todo.items = items;
$scope.todo.options = selectOptions;
}
Similar to the answer of jusopi but here in a SO snippet:
var app = angular.module('myApp', []);
var items = [{
"id": 1,
"name": "ttd",
"action": "do it"
},
{
"id": 2,
"name": "zzz",
"action": "do it 2"
}];
var selectOptions = [{
"name": "next",
"value": "nextUp"
}, {
"name": "in progress",
"value": "inProgress"
}, {
"name": "waiting",
"value": "waiting"
}, {
"name": "done",
"value": "done"
}, {
"name": "trash",
"value": "trash"
}];
app.controller("appController", function($scope) {
$scope.todo = {};
$scope.todo.items = items;
$scope.todo.options = selectOptions;
angular.forEach($scope.todo.items, function(item, key) {
item.ttdSelect = $scope.todo.options[0];
});
});
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="utf-8" />
<script data-require="angular.js#1.4.x" src="https://code.angularjs.org/1.4.8/angular.js" data-semver="1.4.8"></script>
<script src="app.js"></script>
</head>
<body ng-controller="appController">
<div>
<table class="table">
<tr ng-repeat="item in todo.items">
<td>{{item.project}}</td>
<td>{{item.action}}</td>
<td>
<select ng-model="item.ttdSelect"
ng-change="moveItem(item.id, item.ttdSelect);"
ng-options="option.name for option in todo.options track by option.name">
</select>
</td>
</tr>
</table>
<b>Trace:</b>
<pre>
items = {{todo.items | json}}
</pre>
<pre>
options = {{todo.options | json}}
</pre>
</div>
</body>
</html>
I had a hard time following what exactly it is that you wanted so I tried to do it the way I'd normally tackle a problem like this.
example - http://codepen.io/jusopi/pen/PZzxPY
There are a few problems I addressed in reworking your code:
ttdSelect was not pointing to anything in your code. I assumed you meant to update the status value of the todo item so I assigned it to item.status
I created a status option to match a falsy value when a todo item doesn't have a current status
I demonstrated that you can actually bypass ng-options on the <select> and instead use ng-repeat on the <option> element instead to make it a little easier to read.
I hope this helps. Keep in mind I did this in jade/coffeescript because I work faster and better that way. You can easily see the compiled html/js if you need to.

Render a directive with two datasources

My problem is a bit complex, so i will try to explain it as detailed as possible.
I have a directive in a SPA that render their components based on a JSON data that i'm getting from an API. Based on the elements and their types (the JSON is an array of different objects) i'm rendering every object in an specific directive:
Objects Type 1: Renders in a Directive Type 1.
Objects Type 2: Renders in a Directive Type 2.
Objects Type 3: Renders in a Directive Type 3.
Directives Type 1-2-3 are contained in the parent directive and every directive has different controls (select, checkbox). This is a very simple Sketch:
And the "sub-directives":
I'm rendering my elements as follows (Container directive):
<div ng-repeat="element in elementList | customFilter:itemsType1">
<div class="line"></div>
<div class="form-group">
<directivetype1 itemdata="element" modeldata="data"></directivetype1>
</div>
</div>
<div ng-repeat="element in elementList | customFilter:itemsType2">
<div class="line"></div>
<div class="form-group">
<directivetype2 itemdata="element" modeldata="data"></directivetype2>
</div>
</div>
...
And this is the Directive 1 code:
<div class="container-fluid">
<div class="form-group">
<div class="col-xs-6 col-sm-3">
<div class="checkbox">
<label><input type="checkbox"/>{{itemdata.metadata.description}}</label>
</div>
</div>
<div class="col-xs-6 col-sm-3">
<label>Option</label>
<select class="form-control" ng-model="" ng-options="list.id as list.label for list in item.optionData"></select>
</div>
</div>
</div>
My problem goes when i try to attach the model to every element rendered, because of:
The model data comes from another API, in another structure.
I'm iterating the list of controls with ng-repeat, but, when i pass the model data to the sub-directive i'm passing all the possible data (as Array) and i'm not being capable of filter and know what object in that array belongs to an specific view element.
The data has the following structure:
View data:
[
{
"elementA": {
"metadata": {
"id": "001",
"subId": "016",
"description": "Element 1"
},
"optionData": [
{
"id": "5",
"label": "Option 1"
},
{
"id": "6",
"label": "Option 2"
},
{
"id": "7",
"label": "Option 3"
}
]
}
},
{
"elementB": {
"metadata": {
"id": "002",
"subId": "024",
"description": "Element 2"
},
"optionData": [
{
"id": "1",
"label": "Option 1"
},
{
"id": "2",
"label": "Option 2"
},
{
"id": "3",
"label": "Option 3"
}
]
}
}
]
Model data:
[
{
"metadata": {
"id": "002",
"subId": "024",
"description": "Element 2",
"selected": "1"
},
...(Some other data belonging to the model)
},
{
"metadata": {
"id": "001",
"subId": "016",
"description": "Element 1",
"selected": "5"
},
...(Some other data belonging to the model)
},
...
]
As you can see, the only way to correlate both models is with id and subId Fields in the metadata object (because the metadata itself can vary having more or less fields).
QUESTION
How can i filter my model object, based on the view object? My goal is to get the model object that correlates to the view object and pass it to the sub-directive for setting it as the model of the control that i'm rendering at that point.
EDIT:
As cmw pointed out, i've coded a function to correlate every model object with their respective view object, but that object is not reflected in the directive scope. itemdata and modeldata are passed to the directive using a bi-directional scope ('='). I think (but i'm not entirely sure) that, when i pass a function to modeldata the directive is not being capable of setting the returned object. The solution that i've coded based on the cmw answer is as follows:
Directive:
<directivetype1 itemdata="element" modeldata="getModelObject(data)"></directivetype1>
JS (coded in the Ctrl of the parent):
$scope.getModelObject = function(element){
var id = typeof element.metadata === 'undefined' ? null : element.metadata.id;
var subid = typeof element.metadata === 'undefined' ? null : element.metadata.subid;
var modelElement = null;
for (var i = 0; i < $scope.data.length; i += 1){
element = $scope.data[i];
if (modelElement.metadata.id === id && modelElement.metadata.subid === id) return element;
}
return null;
};
But when i try to work in the directive with modeldata i see "null" in FF/Chrome Console.
Any guideline to know what's happening?
Thanks.
EDIT 2:
I've added a version of my code here: http://plnkr.co/edit/xjp1l3PuWczdqYf5LP8q?p=preview. Sadly, in that Plunkr it works as expected but my code does not (i'm expecting to see the output of <h1>{{modeldata}}</h1>). I'm comparing the two versions to see any difference (note that i've included the same AngularJS version that i'm using in my project).
As you've pointed out, I believe the key is simply to make use of the id and subId properties on the meta object.
Something like this would probably work...
<div ng-repeat="element in elementList | customFilter:itemsType1">
<div class="line"></div>
<div class="form-group">
<directivetype1 itemdata="element" modeldata="modelDataFor(element)">
</directivetype1>
</div>
</div>
Then, in your controller, define a function like the following...
$scope.modelDataFor = function (element) {
var id = element.meta.id,
subId = element.meta.subId,
curr;
for (var i = 0; i < $scope.data.length; i += 1) {
curr = $scope.data[i];
if (curr.meta.id === id && curr.meta.subId === subId) {
return curr;
}
}
return null;
}
This seems like the most natural place to pluck out the relative data model object to pass into your nested directives.

JavaScript filter JSON objects using HTML forms

I'm looking to filter a list of objects in a JSON array when an HTML checkbox is clicked. I know about the JavaScript array.sort() method but how do I eliminate items based on checkbox clicks? Do I need an event listener?
My JSON looks as so:
{ "lots" : [
{
"name" : "Parking Lot Name",
"price" : 2,
"cash" : true,
"credit" : false,
"address" : "1234 W Main Ave",
"center" : {
"lat" : 67.659414,
"lng" : -137.414730
}... etc.
So if I've got a form that includes checkboxes for eliminating parking lots based on payment type, how should I go about implementing that? I've read about a jQuery array.grep() function, is that it?
My page is being built using a JS loop like this:
makeList(){
var self = this;
self.jsonFile = $.ajax({
type: 'GET',
url: 'assets/data/default.json',
dataType: 'json',
success: function(response) {
console.log(response);
}
});
self.jsonFile.done(function(data){
//Sort low to high by default
data.lots.sort(function(a, b){
return(a.price > b.price)
});
for (var i = 0; i < data.lots.length; i++) {
document.getElementById('jsonList').innerHTML +=
'<li>
<div id="text">
<p class="price">
$' + data.lots[i].price + '.00
</p>
<p class="info">' +
data.lots[i].address +
'</p>
</div>
<form method="get">
<button type="submit" formaction="https://www.google.com/maps/dir/Current+Location/' +
data.lots[i].address +
'">Directions
</button>
<button type="submit" formaction="detail-view.html">Details
</button>
</form>
</li>';
}
});
}
You can eliminate like this:
You have to bind a onchange function to checkboxes then use following way to that function
data = [{
"name": "name",
"location1": "no",
"description": "description of services"
},
{
"name": "name2",
"location1": "yes",
"description": "description of services2"
},
{
"name": "name3",
"location1": "no",
"description": "description of services3"
}
];
b = $.grep(data, function(el, i) {
return el.location1.toLowerCase() === "yes"
});
You can get the lots where the payment type is say "cash" using:
var cashLots = data.lots.filter(function(lot){return lot.cash});
as an arrow function:
var cashLots = data.lots.filter(lot => lot.cash);

Categories

Resources