I am trying to add a class to an object within an ng-repeat if a property of that object (name) exists within another array.
Basically, a user can flag an object in the items ng-repeat as correct or incorrect which creates a "judgement" object within the judgements array. Onload of the page, I want to be able to compare the two arrays to add/remove a class based on if the most recent judgement of the object is incorrect or correct.
Based on my fiddle below, item1 and item3 should have a class of "incorrect". How could I accomplish this?
I tried using inArray (see http://jsfiddle.net/arunpjohny/wnnWu/) but could not figure out how to get it to work with specific properties rather than the entire array.
fiddle: http://jsfiddle.net/bTyAa/2/
function itemCtrl($scope) {
$scope.items = [{
itemname: "item1"
}, {
itemname: "item2"
}, {
itemname: "item3"
}];
$scope.judgements = [{
judgementResult: "incorrect",
date: "2016-02-01T11:03:16-0500",
item: {
itemname: "item1"
}
}, {
judgementResult: "correct",
date: "2016-01-06T11:03:16-0500",
item: {
itemname: "item1"
}
}, {
judgementResult: "incorrect",
date: "2016-01-04T11:03:16-0500",
item: {
itemname: "item3"
}
}]
}
<div ng-app>
<div ng-controller="itemCtrl">
<ul>
<li ng-repeat="item in items" class="item"> <span>{{item.itemname}}</span>
</li>
</ul>
</div>
</div>
Try to define new function to get proper class for your item
http://jsfiddle.net/bTyAa/8/
$scope.getJudgementsClass = function(itemName) {
var matched = $scope.judgements.filter(function(el) {
return el.item.itemname === itemName;
}).sort(function(a,b){
// Turn your strings into dates, and then subtract them
// to get a value that is either negative, positive, or zero.
return new Date(b.date) - new Date(a.date);
});
if (matched.length == 0)
{
return "";
}
console.log(itemName);
return matched[0].judgementResult;
}
why not doing the NG-repeat on the second array since you already have the information in the first array as item in the second array.
and just use the judgement Result as the class ?
function itemCtrl($scope) {
$scope.items = [{
itemname: "item1"
}, {
itemname: "item2"
}, {
itemname: "item3"
}];
$scope.judgements = [{
judgementResult: "incorrect",
date: "2016-02-01T11:03:16-0500",
item: {
itemname: "item1"
}
}, {
judgementResult: "correct",
date: "2016-01-06T11:03:16-0500",
item: {
itemname: "item1"
}
}, {
judgementResult: "incorrect",
date: "2016-01-04T11:03:16-0500",
item: {
itemname: "item3"
}
}]
}
<div ng-app>
<div ng-controller="itemCtrl">
<ul>
<li ng-repeat="obj in judgements" ng-class="obj.judgementResult"> <span>{{obj.item.itemname}}</span>
</li>
</ul>
</div>
</div>
You cant iterate through $scope.judgements array on each ng-repeat iteration. Something like:
$scope.isItemInCorrect = function(itemname){
console.log($scope.judgements);
for(var i = $scope.judgements.length; i--;) {
if ($scope.judgements[i].item.itemname == itemname
&& $scope.judgements[i].judgementResult == 'incorrect') {
return true;
}
}
}
<li ng-repeat="item in items" ng-class="{'incorrect' : isItemInCorrect(item.itemname)}" class="item"> <span>{{item.itemname}}</span>
http://jsfiddle.net/n0eb82j3/7/
Related
I am trying to use ng-repeat to iterate through an array of objects and use each objects ID to look up the data binded to a checklist model.
I have the following javascript object in a project I'm working on:
{
diagnosis: {
mainfractures: [
{
id: "metacarpal",
textinput_id: "metacarpal_text",
title: "5th Metacarpal",
},
{
id: "proximal_phalanx",
textinput_id: "proximal_phalanx_text",
title: "Proximal Phalanx",
},
{
id: "middle_phalanx",
textinput_id: "middle_phalanx_text",
title: "Middle Phalanx",
},
{
id: "distal_phalanx",
textinput_id: "distal_phalanx_text",
title: "Distal Phalanx",
},
{
id: "scaphoid_fracture",
textinput_id: "scaphoid_fracture_text",
title: "Scaphoid Fracture",
}
]
}}
Here is what I have for my checklist model. As the user selects a checkbox, a value is binded to the array associated with that fracture.
$scope.checklists = {
"diagnosis": {
metacarpal: [],
proximal_phalanx: [],
middle_phalanx: [],
distal_phalanx: [],
scaphoid_fracture: []
}
}
Checklist Image Example
Once a users makes a selection similar to the image above the the checklist model for metacarpal should look like this: metacarpal: ["head"]
What I'm trying to do is list each of the users selection in bulletpoint via fracture.id. I'm trying to accomplish it with this piece of code but it's only listed the fracture title so far. is it a problem with trying to interpolate fracture.id into ng-repeat?
<div ng-repeat="fracture in diagnosis.mainfractures">
<div > <!--ng-if="checklists['diagnosis'][fracture.id] > 0"-->
<h4>{{ fracture.title }}</h4>
<div class="row">
<ul type="disc">
<li ng-repeat="selection in checklists['diagnosis'][fracture.id]">
• {{ capitalize(selection) }}
</li>
</ul>
</div>
</div>
</div>
Based on your supplied code, I'd have to say your issue is actually due to JS syntax errors. You're missing commas after each object item and there is a random double quote here scaphoid_fracture"[].
$scope.checklists = {
"diagnosis": {
metacarpal: []
proximal_phalanx: []
middle_phalanx: []
distal_phalanx: []
scaphoid_fracture"[]
}
}
Here is a fully working jsfiddle
Adjusted the code a bit:
capitalize ->
uppercase(https://docs.angularjs.org/api/ng/filter/uppercase)
some syntax errors
But seems that access by dynamic object key inside ng-repeat works pretty correct
https://jsbin.com/rodecosoyo/1/edit?html,js,output
Angular Application
var app = angular.module('arrayid', []);
app.controller('arrayContainerCtrl', function($scope) {
$scope.diagnosis = {
mainfractures: [{
id: "metacarpal",
textinput_id: "metacarpal_text",
title: "5th Metacarpal",
}, {
id: "proximal_phalanx",
textinput_id: "proximal_phalanx_text",
title: "Proximal Phalanx",
}, {
id: "middle_phalanx",
textinput_id: "middle_phalanx_text",
title: "Middle Phalanx",
}, {
id: "distal_phalanx",
textinput_id: "distal_phalanx_text",
title: "Distal Phalanx",
}, {
id: "scaphoid_fracture",
textinput_id: "scaphoid_fracture_text",
title: "Scaphoid Fracture",
}]
};
$scope.checklists = {
"diagnosis": {
metacarpal: ['1', '2'],
proximal_phalanx: ['2', '3'],
middle_phalanx: ['3'],
distal_phalanx: ['4'],
scaphoid_fracture: ['5']
}
};
});
Markup:
<body ng-app='arrayid' ng-controller='arrayContainerCtrl'>
<div ng-repeat="fracture in diagnosis.mainfractures">
<h4>{{ fracture.title }}</h4>
<div class="row">
<ul type="disc">
<li ng-repeat="selection in checklists['diagnosis'][fracture.id]">
• {{ selection | uppercase }}
</li>
</ul>
</div>
</div>
</body>
I have the following ViewModel:
var myViewModel = {
categories: ko.observableArray([
{ name: ko.observable('Fruit'),
items: ko.observableArray([
ko.observable('Apple'),
ko.observable('Orange'),
ko.observable('Banana'),
]),
},
]),
};
I want to add a computed value for each item of this array:
$.each(myViewModel.categories(), function(index, cat){
cat.num = ko.computed(function(){
return cat.items().length;
}, cat);
});
This is my HTML:
<ul data-bind="foreach: { data: categories, as: 'category' }">
<li>
<ul data-bind="foreach: { data: items, as: 'item' }">
<li>
<span data-bind="text: category.name"></span>:
<span data-bind="text: item"></span>
</li>
</ul>
<span data-bind="text: category.num"></span>
</li>
</ul>
And it works just great! Here is the result:
Fruit: Apple
Fruit: Orange
Fruit: Banana
3
But now let's say my data has changed and I need to reflect those changes:
myViewModel.categories([
{
name: "Fruit",
items: ["Apple", "Banana"],
},
{
name: "Veg",
items: ["Potato", "Carridge"],
}
]);
And it doesn't work - I don't see num at the end of each list. Could you please help me to fix this? Here is the codepen
Of course it does not work, because you are replacing the entire content of the observableArray. This code of yours:
$.each(myViewModel.categories(), function(index, cat){
cat.num = ko.computed(function(){
return cat.items().length;
}, cat);
});
adds the num property to the items that exist in the array at the time of the call. By giving a new backing array to your observableArray by doing this:
myViewModel.categories([
{
name: "Fruit",
items: ["Apple", "Banana"],
},
{
name: "Veg",
items: ["Potato", "Carridge"],
}
])
you, of course, lose the old array and thus the computed observables which are defined on the items and not the observable array. Knockout of course has no way to know how and whether it should add the computed property to every item in the new array.
Solution:
You can either do the same property addition when you replace the backing array of your observableArray like this:
myViewModel.categories([
{
name: "Fruit",
items: ["Apple", "Banana"],
},
{
name: "Veg",
items: ["Potato", "Carridge"],
}
]);
$.each(myViewModel.categories(), function(index, cat){
cat.num = ko.computed(function(){
return cat.items().length;
}, cat);
});
or better yet, you could instantiate the objects in the array with the property already defined. Note: you don't have to write the function over and over again for each item, you could, for instance, define a constructor for the array items:
function Category(name, items) {
var self = this;
this.name = name;
this.items = ko.observableArray(items);
this.num = ko.computed(function() {
return self.items().length;
});
};
and then, this is how you create a new one:
var newItem = new Category('Category 1', [ 'Apple', 'Banana']);
and insert it into the observable array:
myViewModel.categories.push(newItem);
this adds one item and keeps the existing ones, or, as you did, you could supply a whole new array as so:
myViewModel.categories([
new Category('Fruit', ['Apple', 'Banana' ]),
new Category('Veg', ['Potato', 'Carridge' ])
]);
I have a an array in js it's something like this
menu = [
{
name: 'Item1',
//submenuName: 'submenu-1',
},
{
name: 'Item2',
submenuName: 'submenu-2',
sub: [
{
name: 'Item2_1',
//submenuName: '',
},
{
name: 'Item2_2',
//submenuName: '',
},
{
name: 'Item2_3',
//submenuName: '',
}
]
},
{
name: 'Item3',
//submenuName: 'submenu-3',
}
]
And i need to list them in ul tag, but every level has to be closed before the other.
<ul data-menu="main">
<li data-submenu>Item1</li>
<li data-submenu='submenu-2'>Item2</li>
<li data-submenu>Item3</li>
</ul>
<ul data-menu="submenu-2">
<li data-submenu>Item2_1</li>
<li data-submenu>Item2_2</li>
<li data-submenu>Item2_3</li>
</ul>
and so on. I've mange to print them but only the first level. Cannot print the sub level.
If the menus need to be listed one after another and not nested, then maintain an array of menus to print and fill that array with submenus while printing parent menus.
Since you mentioned, that you're using jQuery, here is an example using this library.
function generateMenu (menu, container) {
var menus = [{name: 'main', entries: menu}];
while (menus.length) {
var current = menus.shift();
var ul = $("<ul />").attr('data-menu', current.name);
$.each(current.entries, function (index, menuItem) {
var li = $('<li />')
.attr('data-submenu', menuItem.submenuName || '')
.text(menuItem.name);
if ($.isArray(menuItem.sub)) {
menus.push({name: menuItem.submenuName, entries: menuItem.sub});
}
li.appendTo(ul);
});
ul.appendTo(container);
}
}
generateMenu(menu, $('body'));
JSFiddle example
I have two arrays, Users and Employments like so:
Users = [{id:1, name: "ryan"}, {id:2, name:"Julie"}]
Employments = [{user_id: 1, title: "manager"}, {user_id: 2, title: "Professor"}]
I'd like to display the Employments array in an ng-repeat like so:
<li ng-repeat="employment in Employments">
{{employment.user.name}}
</li>
How do I map the Users array to the Employments array?
If you want the employee name to get displayed based on id, the simplest way is just pass that id to a function and return the name, like as shown below
Working Demo
html
<div ng-app='myApp' ng-controller="ArrayController">
<li ng-repeat="employment in Employments">{{getEmployeeName(employment.user_id)}}
</li>
</div>
script
var app = angular.module('myApp', []);
app.controller('ArrayController', function ($scope) {
$scope.Users = [{
id: 1,
name: "ryan"
}, {
id: 2,
name: "Julie"
}];
$scope.Employments = [{
user_id: 1,
title: "manager"
}, {
user_id: 2,
title: "Professor"
}];
$scope.getEmployeeName = function (empId) {
for (var i = 0; i < $scope.Users.length; i++) {
if ($scope.Users[i].id === empId) {
return $scope.Users[i].name;
}
};
};
});
UPDATE 2
If you want to embed the User array in the Employments array, try the following stuff
$scope.Users = [{id: 1, name: "ryan"}, {id: 2, name: "Julie"}];
$scope.Employments = [{user_id: 1, title: "manager"},
{user_id: 2, title: "Professor"}
];
code for flattening Employments array by adding User properties
angular.forEach($scope.Users, function (user, userIndex) {
angular.forEach($scope.Employments, function (employee, employeeIndex) {
if (employee.user_id === user.id) {
employee.name = user.name;
}
});
});
Output
$scope.Employments = [ { user_id: 1, title: "manager", name: "ryan" },
{ user_id: 2, title: "Professor", name: "Julie" }
]
Working Demo
UPDATE 3
Code for making a nested employee structure like as shown below from $scope.Users and $scope.Employments
$scope.employees = [];
angular.forEach($scope.Employments, function (employee, employeeIndex) {
var employeeObject = {};
employeeObject.title = employee.title;
angular.forEach($scope.Users, function (user, userIndex) {
if (employee.user_id === user.id) {
employeeObject.user = user;
}
});
$scope.employees.push(employeeObject);
});
Output
[ { title: "manager", user: { "id": 1, "name": "ryan" } },
{ title: "Professor", user: { "id": 2, "name": "Julie" } }
]
Working Demo
If you wanted to match up the two following arrays purely with a template you could take the following arrays
Users = [{id:1, name: "ryan"}, {id:2, name:"Julie"}]
Employments = [{user_id: 1, title: "manager"}, {user_id: 2, title: "Professor"}]
And nest a repeat like:
<li ng-repeat="employment in Employments">
<div ng-repeat="user in Users" ng-if="user.id === employment.user_id" >
{{user.name}}:{{employment.title}}
</div>
</li>
Two more nice little thing to do to avoid any risk of getting those brackets showing on a slow page load is to use the ng-bind and prefix the attributes with data so its with the html spec
<li data-ng-repeat="employment in Employments">
<div data-ng-repeat="user in Users" data-ng-if="user.id === employment.user_id" >
<span data-ng-bind="user.name"></span>:<span data-ng-bind="employment.title"></span>
</div>
</li>
I know you didn't have the need for anything but the name, but figured a quick example of using the outer loop in the inner still could be helpful. Also this would be the case for ng-init if you needed to reference the the $index of the outer ng-repeat from the inner, but that might be more than you're looking for here.
Plunker
This sorts the users names into the employments array:
var sortUsers = function() {
var i = 0;
for (i; i < $scope.users.length; i++) {
console.log($scope.users[i].id)
for(var z = 0; z < $scope.employments.length; z++) {
if($scope.employments[z].user_id === $scope.users[i].id) {
$scope.employments[z].name = $scope.users[i].name;
}
}
}
}
HTML:
<ul>
<li ng-repeat="employment in employments">
{{employment.name}}
</li>
</ul>
I dealt similar problem yesterday. If you want to use js, have to loop twice.
I recommend to use the best way is to select in one query by join table if data come from single database.
You select User by one query, and Employment for another query in database. Then, twice ng-repeat to re-arrange. Here is my solution.
select users.*, employments.title from `users` inner join `employments` where users.id = employments.user_id;
Hope be be helpful.
I have a ul containing li's which contain names of different recipe ingredients for a recipe page. I'm trying to get those ingredients and store them into a JavaScript array within an object. I already know the title of the recipe so I put that right into the object property title, but I don't know how many ingredients there will be for each recipe. Here is what I have:
var recipeobj = {
title: $('h3.title').val(),
ingredients: [
ingredient,
optional
]
}
$.each($('ul.ingredients > li > h4'), function (index, ingredient) {
recipeobj.ingredients[index].ingredient = $(ingredient).html();
recipeobj.ingredients[index].optional = false;
})
If I try to do console.log(recipeobj.ingredients) I just get the error Uncaught ReferenceError: ingredient is not defined
No doubt this is simple, I just rarely need to use arrays in JavaScript so have little experience with them.
Open your console and run it
var recipeobj = {
title: $('h3.title').html(),
// ingredients is empty for now
ingredients: []
};
$.each($('ul.ingredients > li > h4'), function(index, ingredient) {
// Get the name
var name = $(ingredient).html(),
// Find out if it is 'optional'(using a class here)
optional = $(ingredient).hasClass('optional');
// Push a new ingredient into the array
recipeobj.ingredients.push({ name: name, optional: optional });
});
console.log(recipeobj);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h3 class="title">Pork and beans</h3>
<ul class="ingredients">
<li>
<h4>Pork</h4>
</li>
<li>
<h4>Beans</h4>
</li>
<li>
<h4 class="optional">Salt*</h4>
</li>
</ul>
This should output:
{
"title": "Pork and beans",
"ingredients": [
{ name : "Pork", optional : false },
{ name : "Beans", optional : false },
{ name : "Salt*", optional : true}
]
}
var rObj = {
title: $('h3.title').val(),
ingredients: [
'source cream',
'cheese',
'chopped meat'
],
optional: true
};
accessing
var rItem = rObj.ingredients[1];
or you want
var rObj = {
title: $('h3.title').val(),
ingredients: {
ingredient_list: ['one','two','three'],
optional: true
}
};
accessing
var rItem = rObj.ingredients.ingredient_list[1];
The structure you are attempting to use looks like the structure should be like
var rObj = {
title: $('h3.title').val(),
things: [{
ingredient: 'source cream',
optional: true
},
{
ingredient: 'cheese',
optional: false
}]
};
accessing
var ingred = rObj.things[1].ingredient;
var rObj = {
title: $('h3.title').val(),
ingredients : []
};
you can add ingredients:
$.each($('ul.ingredients > li > h4'), function (index, ingredient) {
rObj.ingredients.push({ingredient: $(ingredient).html(), optional :false})
})