apply ngClass to element where ngRepeat is declared - javascript

I have a table where rows are generated using AngularJS ngRpeat:
<tr ng-repeat="player in division">
<td>{{player.name}}</td>
<td>{{player.score}}</td>
</tr>
The array looks a bit like this:
$scope.divison = [
{
name: "John Doe",
score: "10",
goingUp : true
},
{
name: "Bob Marley",
score: "20"
}
];
Now, what if I wanted to apply ng-class to the table row based on that particular ng-repeat? I would have though this might work:
<tr ng-repeat="player in division" ng-class="'goingUp' : player.goingUp">
<td>{{player.name}}</td>
<td>{{player.score}}</td>
</tr>
Alas this doesn't work. Probably because the ng-class doesn't sit inside that repeat element. How can I get this working though?

Correct syntax is
ng-class="{'goingUp' : player.goingUp}"

It should be able to do so like in this case. You could write a function in to your controllers scope that returns appropriate class:
$scope.isGoingUp = function(player)
{
if (player.goingUp) return "goingUp";
return "notGoingUp";
}
and then call it like this
ng-class="isGoingUp(player)"

Related

v-for inside v-for displaying data in onclick event

hello everyone i have array of objects, and inside each object i have also array of objects..
i did the v-for inside the v-for to display data
at first i wanted for each row to show the data of the first element of the each child of the parent array and onClick event, i wanted to change the data only in the specifique row.
infos: [{
name: 'name1',
infosName: [{
place: 'place.1.1',
surface: '100'
},
{
place: 'place.1.2',
surface: '200'
}
]
},
{
name: 'name2',
infosName: [{
place: 'place.2.1',
surface: '300'
},
{
place: 'place.2.2',
surface: '400'
}
]
}
]
i created a method to display the data and got on parametres Two indexs
this a jsfiddle to understand more the problem
Thank you
https://jsfiddle.net/f0ehwacm/2
There are several issues needing fixing, but you are on the right lines.
Most importantly, you need to store not just one "myIndex" but a separate "myIndex" for each row
That is the root cause of your problem.
Let me rephrase your question?
I believe you are hoping for four buttons. The top two buttons choose between two options.
Completely separately, the bottom two buttons choose between two options.
It would be easier for readers to understand your intention if you called the two top buttons "Question 1, Option A", and "Question 1, Option B". And then the bottom two "Question 2...". Then they would understand why when you click on one of the buttons, you want to affect the output of the table for that row only.
Avoid using generic terms like "index" and "i"
These make it unnecessarily difficult for people to understand what you intend. Better to use a concrete noun, in this case "question" or "answer", and prefix it with "i" when you mean the index, such as "iQuestion" for the index of the question and "question" for the question itself.
You seem to have a single function "getInfos" which does BOTH getting and setting of information
This is a major problem. You should separate the two functions.
When you click, you want to run a "set" function, that updates your index.
When you are simply displaying, you can access a "get" function, which does not change anything.
You need to store an index for each row
In my terminology, you need to store the index of your answer to each question.
So instead of this.myIndex starting at 0, you have it starting at [0,0]. Each of the two values can be updated separately, allowing the program to update the answer to one row (i.e. one question), while leaving the other row unchanged.
I have renamed this variable to this.myAnswer to make it easier to understand.
this.$set when writing to an array that you want Vue to react to
I initially wrote the "setAnswer" function as follows:
this.myAnswer[iQuestion]=iAnswer
However, I found that the on-screen display was not updating. This is a common problem in Vue, when you update not the main property listed in data(), but an array element of that property.
This is because Vue is not tracking the updates of the array elements, only the array itself. So if you were to reassign the entire array, Vue would notice.
The workaround is to tell Vue explicitly that you are updating something that needs to be reactive. Vue will then update it on screen.
To do this, change your assignment from this format:
this.array[index] = value
To this
this.$set(this.array, index, value)
Vue provides this function this.$set, which executes your normal this.array[index] = value and tells Vue to do the screen update.
How to cope with missing "infosName"
In response to your question in the comments. You have a convenient place to solve this: your getAnswer() function.
Change from this:
getAnswer(iQuestion,iAnswer){
return {
'name' : this.infos[iQuestion].infosName[iAnswer].place,
'surface' : this.infos[iQuestion].infosName[iAnswer].surface
}
to this:
getAnswer(iQuestion,iAnswer){
if (this.infos.length>iQuestion &&
this.infos[iQuestion].infosName &&
this.infos[iQuestion].infosName.length>iAnswer
){
return {
'name' : this.infos[iQuestion].infosName[iAnswer].place,
'surface' : this.infos[iQuestion].infosName[iAnswer].surface
}
else return {
name : "",
surface: ""
}
}
Solution
html:
<div id="app">
<div v-for="(question,iQuestion) in infos">
<div class="row d-flex">
<span style="margin-right:10px" v-for="(answer,iAnswer) in question.infosName" class="badge badge-primary" #click="setAnswer(iQuestion,iAnswer)"><i class="fa fa-eye" style="margin-right:10px;cursor: pointer"></i>{{ answer.place }}</span> </div>
<div class="row">
<p>Name : {{ getAnswer(iQuestion,myAnswer[iQuestion]).name }} </p>
<p>Surface : {{ getAnswer(iQuestion,myAnswer[iQuestion]).surface }}</p>
</div>
</div>
</div>
JS:
new Vue({
el :'#app',
data : function(){
return {
myAnswer : [0,0],
infos : [
{
name : 'name1',
infosName : [
{
place : 'Question 1, Option A',
surface : '100'
},
{
place : 'Question 2, Option B',
surface : '200'
}
]
},
{
name : 'name2',
infosName : [
{
place : 'Question 2, Option A',
surface : '300'
},
{
place : 'Question 2, Option B',
surface : '400'
}
]
}
]
}
},
methods:{
setAnswer(iQuestion,iAnswer){
this.$set(this.myAnswer,iQuestion,iAnswer)
},
getAnswer(iQuestion,iAnswer){
return {
'name' : this.infos[iQuestion].infosName[iAnswer].place,
'surface' : this.infos[iQuestion].infosName[iAnswer].surface
}
}
}
})

Angular smart-table fail to render collection based on objects list

I converted my Items Array into an indexed object using underscorejs:
$scope.items = _.indexBy($scope.items, 'id');
And now my items looks like this:
$scope.items = {
"1": {
id: 1,
name: "Tizio"
},
"2": {
id: 2,
name: "Caio"
},
"3": {
id: 3,
name: "Sempronio"
},
};
It is exactly what I need.
Now I need to show my items in a Table and I would like to use a plugin that help me to sort, paginate, filter my data easily, so I started to use angular-smart-table. A famous and common one...
I followed the documentation and the final HTML looks like this:
<table st-safe-src="items" st-table="rowItems">
<tr>
<th>id</th>
<th>name</th>
</tr>
<tr ng:repeat="item in rowItems track by item.id">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
</tr>
</table>
Unfortunately it doesn't show my data. I tried to remove smart table and it work fine in a common table.
How can I let it work? What I'm doing wrong? If smart table doesn't support list of Objects, do you know a better plugin that should better fit my needs?
Here a JsFiddle about it.
Thanks
I wasn't sure what you were trying to do with the [].concat(OBJECT), so I changed the way of converting from Object to array using Object.keys and map.
You can check the updated jsfiddle:
https://jsfiddle.net/qfpyu1kL/2/
// Initial values converted to array
$scope.rowItems = Object.keys($scope.items).map(function(key) {
return $scope.items[key];
});
$timeout(function () {
// Add new values
$scope.rowItems.push({ id: 4, name: 'OTHER1' });
$scope.rowItems.push({ id: 5, name: 'OTHER2' });
}, 1000);
I've seen that you were using stSafeSrc, which meant you were expecting asynchronous data, thats why I added the $timeout.
I also updated the HTML to set rowItems in st-safe-srcattribute and st-table to displayedCollection.

String Interpolation not getting updated after change to object in Viewmodel in Aurelia

I have an element in my View in Aurelia that is not getting updated when an object from its Viewmodel is getting updated. I've seen the documentation about Pub/Sub and Event Aggregators, however this seems a little heavy-handed for what I want to do, since I am not trying to communicate between two different resources, but rather just within a View and its Viewmodel.
When a change occurs to the object in the Viewmodel, I don't know how to correctly update (or trigger an update to) the string interpolation in the View.
My code is as follows
myview.html
<h1>My List</h1>
<ul>
<li repeat.for="group of modelObject.groups">
<span>${group.id}</span>
<span repeat.for="state of group.region">${state}</span>
</li>
<ul>
<button click.delegate(editModelObject())>Edit</button>
myviewmodel.js
constructor()
{
this.modelObject = {
<other stuff>,
"groups": [
{
"id": "default",
"regions" : ["NY", "CT", "NJ"]
},
{
"id": "west",
"regions" : ["CA", "OR"]
}
],
<more stuff>
}
}
editModelObject() {
<something that updates both the id and regions of a group in this.modelObject>
}
For some reason, the states are correctly changing in the view, but the id's are not. Do I need to use something like Pub/Sub to get the two-way binding to work correctly? Or is there a simple thing that I am missing or doing wrong?
This works if you change a property of one of the array's objects. But this doesn't work if you assign one of the array's index because this would require dirty-checking. See https://github.com/aurelia/binding/issues/64
To solve your problem you should use splice() instead of indexed assignment. For instance:
const newItem = { id: 77, name: 'Test 77', obj: { name: 'Sub Name 77' } };
//instead of this.model.items[0] = newItem; use below
this.model.items.splice(0, 1, newItem);
Running example https://gist.run/?id=087bc928de6532784eaf834eb918cffa

restoring chained selects using angularjs

I have two select-element that are bound to a model in angular. The first should show a list of processes, the second a list of variables belonging to the process. My datamodel looks like this:
"processes": [
{
"name": "proces1",
"variables": [
"var1",
"var2"
]
},
{
"name": "proces2",
"variables": [
"var3",
"var4"
]
}
]
The result of my selection needs to end up in a 'slider' object in the 'sliders' array on the scope:
$scope.sliders =
[
{
process : "process1",
tag : "var1",
}
]
I've implemented the selects as below, inspired by this jsfiddle.
<tr ng-repeat="slider in sliders track by $index">
<td><select name="processSelect" ng-model="slider.process" ng-options="process.name for process in slider.processes"></select></td>
<td><select name="variableSelect" ng-model="slider.tag" ng-options="v for v in slider.process.variables"></select></td>
</tr>
The approach works, however if my model is already filled only the variableSelect is selected. The process select is not. This is caused because the processSelect uses a a list of process dictionaries instead of strings (which are stored in the model).
What can I do to achieve this? How does one normally accomplish this in angular?
I fixed this by changing my data model slightly to:
"processes": {
"process1": [
"var1",
"var2"
],
"process2": [
"var3",
"var4"
]
}
then I changed the ng-options to:
<tr ng-repeat="slider in sliders track by $index">
<td><select name="processSelect" ng-model="slider.process" ng-options="key as key for (key, value) in processes"></select></td>
<td><select name="variableSelect" ng-model="slider.tag" ng-options="value for (key, value) in processes[slider.process]"></select></td>
</tr>
basically following the angular docs on ng-options.

How to use parameters within the filter in AngularJS?

I want to use parameter in filter, when I iterate some arrays with ng-repeat
Example:
HTML-Part:
<tr ng-repeat="user in users | filter:isActive">
JavaScript-part:
$scope.isActive = function(user) {
return user.active === "1";
};
But I want to be able to use filter like
<tr ng-repeat="user in users | filter:isStatus('4')">
But its not working. How can I do something like that?
UPDATE: I guess I didn't really look at the documentation well enough but you can definitely use the filter filter with this syntax (see this fiddle) to filter by a property on the objects:
<tr ng-repeat="user in users | filter:{status:4}">
Here's my original answer in case it helps someone:
Using the filter filter you won't be able to pass in a parameter but there are at least two things you can do.
1) Set the data you want to filter by in a scope variable and reference that in your filter function like this fiddle.
JavaScript:
$scope.status = 1;
$scope.users = [{name: 'first user', status: 1},
{name: 'second user', status: 2},
{name: 'third user', status: 3}];
$scope.isStatus = function(user){
return (user.status == $scope.status);
};
Html:
<li ng-repeat="user in users | filter:isStatus">
OR
2) Create a new filter that takes in a parameter like this fiddle.
JavaScript:
var myApp = angular.module('myApp', []);
myApp.filter('isStatus', function() {
return function(input, status) {
var out = [];
for (var i = 0; i < input.length; i++){
if(input[i].status == status)
out.push(input[i]);
}
return out;
};
});
Html:
<li ng-repeat="user in users | isStatus:3">
Note this filter assumes there is a status property in the objects in the array which might make it less reusable but this is just an example. You can read this for more info on creating filters.
This question is almost identical to Passing arguments to angularjs filters, to which I already gave an answer. But I'm gonna post one more answer here just so that people see it.
Actually there is another (maybe better solution) where you can use the angular's native 'filter' filter and still pass arguments to your custom filter.
Consider the following code:
<li ng-repeat="user in users | filter:byStatusId(3)">
<span>{{user.name}}</span>
<li>
To make this work you just define your filter as the following:
$scope.byStatusId = function(statusId) {
return function(user) {
return user.status.id == statusId;
}
}
This approach is more versatile because you can do comparisons on values that are nested deep inside the object.
Checkout Reverse polarity of an angular.js filter to see how you can use this for other useful operations with filter.
If you have created an AngularJs custom filter, you can send multiple params to your filter.Here is usage in template
{{ variable | myFilter:arg1:arg2... }}
and if you use filter inside your controller here is how you can do that
angular.module('MyModule').controller('MyCtrl',function($scope, $filter){
$filter('MyFilter')(arg1, arg2, ...);
})
if you need more with examples and online demo, you can use this
AngularJs filters examples and demo
This may be slightly irrelevant, but if you're trying to apply multiple filters with custom functions, you should look into:
https://github.com/tak215/angular-filter-manager
Example I have a students list as below :
$scope.students = [
{ name: 'Hai', age: 25, gender: 'boy' },
{ name: 'Hai', age: 30, gender: 'girl' },
{ name: 'Ho', age: 25, gender: 'boy' },
{ name: 'Hoan', age: 40, gender: 'girl' },
{ name: 'Hieu', age: 25, gender: 'boy' }
];
I want to filter students via gender to be boy and filter by name of them.
The first I create a function named "filterbyboy" as following:
$scope.filterbyboy = function (genderstr) {
if ((typeof $scope.search === 'undefined')||($scope.search === ''))
return (genderstr = "")
else
return (genderstr = "boy");
};
Explaination: if not filter name then display all students else filter by input name and gender as 'boy'
Here is full HTMLcode and demo How to use and operator in AngularJs example

Categories