Adding some logic in v-for with v-if Vue - javascript

I have some json with below results
{
"module": [
{
"id": 1,
"title": "Module 1",
"type": "URL",
"size": 1,
"image": "https://localhost/image1.png"
},
{
"id": 2,
"title": "Module 2",
"type": "YOUTUBE",
"size": 2,
"image": "https://localhost/image2.png"
}
]
}
Now i want to render it on a page with some loop and conditional, like below
<template>
<section class="page-section homescreen mt-4">
<div class="container">
<div class="row">
<div class="col-lg-3" v-bind:key="data.index" v-for="data in modules" v-if="data.size == 1">
<img class="img-fluid" :src="`${data.image}`" :alt="`${data.title}`" />
</div>
<div class="col-lg-6" v-bind:key="data.index" v-for="data in modules" v-if="data.size == 2">
<img class="img-fluid" :src="`${data.image}`" :alt="`${data.title}`" />
</div>
</div>
</div>
</section>
</template>
<script>
export default {
data() {
return {
modules: [
{
"id": 1,
"title": "Module 1",
"type": "URL",
"size": 1,
"image": "https://localhost/image1.png"
},
{
"id": 2,
"title": "Module 2",
"type": "YOUTUBE",
"size": 2,
"image": "https://localhost/image2.png"
}
]
};
}
};
</script>
But instead of success, i got some error saying
5:99 error The 'modules' variable inside 'v-for' directive should be
replaced with a computed property that returns filtered array instead.
You should not mix 'v-for' with 'v-if' vue/no-use-v-if-with-v-for
8:99 error The 'modules' variable inside 'v-for' directive should be
replaced with a computed property that returns filtered array instead.
You should not mix 'v-for' with 'v-if' vue/no-use-v-if-with-v-for
Actually i want to create the <div class="col-lg-3"> part to be dynamic based on the json, if size:1 mean to have col-lg-3 and if size:2 mean to have col-lg-6
Any explanation and suggestion will be appreciated.
Thank you

eslint-plugin-vue was telling you to do this:
<div class="col-lg-3" v-bind:key="data.index" v-for="data in modules.filter(o=>o.size == 1)">
<img class="img-fluid" :src="`${data.image}`" :alt="`${data.title}`" />
</div>
<div class="col-lg-6" v-bind:key="data.index" v-for="data in modules.filter(o=>o.size == 2)">
<img class="img-fluid" :src="`${data.image}`" :alt="`${data.title}`" />
</div>
In your case, it can be simplified as
<div :class="'col-lg-'+data.size*3" v-bind:key="data.index" v-for="data in modules">
<img class="img-fluid" :src="`${data.image}`" :alt="`${data.title}`" />
</div>

If it's essentially something with css classes, why don't you use v-bind:class or :class with your condition ?
https://v2.vuejs.org/v2/guide/class-and-style.html
But like said in the error message you'll certainly have to create a sub component and then use props on it
https://v2.vuejs.org/v2/guide/components-props.html

In case you have just two sizes:
You can use Computed Properties to achieve this requirement.
First, create two new computed properties like:
computed: {
modulesSize1: function() {
return this.modules.filter(x => x.size == 1)
},
modulesSize2: function() {
return this.modules.filter(x => x.size == 2)
}
}
Now, you can easily loop through computed properties modulesSize1 && modulesSize2 like:
<div class="row">
<div class="col-lg-3" v-bind:key="data.index" v-for="data in modulesSize1" >
<img class="img-fluid" :src="`${data.image}`" :alt="`${data.title}`" />
</div>
<div class="col-lg-6" v-bind:key="data.index" v-for="data in modulesSize2" >
<img class="img-fluid" :src="`${data.image}`" :alt="`${data.title}`" />
</div>
</div>
In case you have multiple sizes:
First, create a simple method like:
methods: {
getClass: function(size) {
return `col-lg-${size * 3}`
},
}
and then we can update template and use Class Bindings like:
<div :class="getClass(data.size)" :key="data.index" v-for="data in modules">
<img class="img-fluid" :src="`${data.image}`" :alt="`${data.title}`" />
</div>

The v-if is essentially baked in to the v-for (if modules is empty nothing will render) so it's recommended not to use them in combination. If you need it for a separate condition, like you do here, then you'll have to move your v-for on to the <img> itself.
You also won't be able to use data.size this way so you'd have to use something like v-if="modules[0].size == 1" etc.
Edit
#Palash answer is probably the more efficient way to solve this.
Edit 2
#r0ulito and #xianshenglu also makes a really good point, if it's just a class issue, use v-bind.

Related

Vue JS: Looping through object inline attribute

I am new to vue.js so this might be an easy question, but I unfortunatelly didnt come to a result on my own.
So this is the basis of my template:
<template v-for="page in $props.data.pageInfo" >
<div class="overviewItem" :key="page.uid" :data-cat=" **looping through the object to get both cat_id's ** ">
<div v-for="(cat, index) in page.categories" :key="index" >
<p v-if="index === 0" class="subtitle">
{{ cat.title }}
</p>
</div>
</div>
</template>
{{ page.categories }} has the following values at the first .overviewItem:
[
{
"cat_id": 1,
"title": "Kategorie 1"
},
{
"cat_id": 2,
"title": "Kategorie 2"
}
]
The loop to get the subtitle works perfectly fine, but I cannot find any option on how to loop through this object so that i get the two values of data-cat attribute.
Use .map to generate a new array of just the cat_id and then join them together with a space in between:
<div
class="overviewItem"
:key="page.uid"
:data-cat="page.categories.map(c => c.cat_id).join(' ')"
>
...
</div>

Auto Increment in ng-repeat object

I am taking game info from mlb.com and displaying them using angularjs and ng-repeat directive. an example of the JSON feed is below.
{
"data": {
"games": {
"next_day_date": "2017-08-19",
"modified_date": "2017-08-18T16:57:16Z",
"month": "08",
"year": "2017",
"game": {
"0": [{
"away_team_name": "CUBS"
}, {
"home_team_name": "INDIANS"
}],
"1": [{
"away_team_name": "CUBS"
}, {
"home_team_name": "INDIANS"
}]
},
"day": "18"
}
}
I am only able to display the data properly however, only 1 game. html below
<div class="card mb-3" ng-repeat="x in scoreboard" ng-if="$index > 1">
<div class="card-header" align="center">
{{x.games.game[0].away_team_name}} ({{x.games.game[0].away_win}}-{{x.games.game[0].away_loss}}) At {{x.games.game[0].home_team_name}} ({{x.games.game[0].home_win}}-{{x.games.game[0].home_loss}})<br>
<small>{{x.games.game[0].time}}</small>
</div>
<div class="card-block"></div>
</div>
I do understand [0] refers to the game ID '0' in the json feed, however is there a way to auto increment that number in {{x.games.game[0].time}} to loop through all games instead of doing each game individually like below?
<div class="card mb-3" ng-repeat="x in scoreboard" ng-if="$index > 1">
<div class="card-header" align="center">
{{x.games.game[0].away_team_name}} ({{x.games.game[0].away_win}}-{{x.games.game[0].away_loss}}) At {{x.games.game[0].home_team_name}} ({{x.games.game[0].home_win}}-{{x.games.game[0].home_loss}})<br>
<small>{{x.games.game[0].time}}</small>
</div>
<div class="card-block"></div>
</div>
<div class="card mb-3" ng-repeat="x in scoreboard" ng-if="$index > 1">
<div class="card-header" align="center">
{{x.games.game[1].away_team_name}} ({{x.games.game[1].away_win}}-{{x.games.game[1].away_loss}}) At {{x.games.game[1].home_team_name}} ({{x.games.game[1].home_win}}-{{x.games.game[1].home_loss}})<br>
<small>{{x.games.game[1].time}}</small>
</div>
<div class="card-block"></div>
</div>
I have tried
{{x.games.game[$index + 1].time}} but still return only 1 game
Any insight would be appreciated!
I adjusted my $scope in the controller from
.success(function (data) { $scope.scoreboard = data;} to
.success(function (data) {$scope.scoreboard = response.data.games.game;}
html now looks like
<div class="card mb-3" ng-repeat="x in scoreboard track by $index" ng-if="$index > 1">
<div class="card-header" align="center">{{x.away_team_name}} </div>
</div>
works perfectly
There are a couple of problems:
An iteration over the wrong object, the iteration should be made over scoreboard.games.game instead of scoreboard.
The syntax x in list should only be used in the ng-repeat to iterate over arrays, both scoreboard and scoreboard.games.game are NOT arrays, they're Object literals
The syntax to iterate over Object literals in ng-repeat is (key, value) in object as mentioned in the documentation.
To properly solve this problem we must iterate over the right object with the right syntax, here is how:
<div class="card mb-3" ng-if="key > 1"
ng-repeat="(key, game) in scoreboard.games.game">
<div class="card-header" align="center">
{{ game.away_team_name }}
({{ game.away_win }}-{{ game.away_loss }}) At
{{ game.home_team_name }}
({{ game.home_win }}-{{ game.home_loss }})<br />
<small>{{ game.time }}</small>
</div>
<div class="card-block"></div>
</div>

How to use ng-repeat only if object type is array?

I have a complex object as shown below:
$scope.document =
{
"GENERAL_FIELDS": {
"Source_Type": "custom",
"Annotations": [
"216/content/Factiva_CM_001/Proteins",
"216/content/Factiva_CM_001/Fact"
],
"Content": [
" Baculovirus; Budded virus; Ultrastructure; Cryo-EM;"
],
"Title": [
"Budded baculovirus particle structure revisited"
]
},
"stn": {
"Document_Type": [
"Journal",
"Article"
]
}
}
I want to display all the fields present in "GENERAL_FIELDS" and "stn". Fields' value can either be string or array of strings. If it is array, I further want to ng-repeat on it and display the content. Following is my html:
<div id="titsec" class="comdocdet" ng-repeat="(category, group) in document">
<div ng-repeat="(key, value) in group">
<div class="pTitle">
{{key}}
</div>
<div class="contdesc">
<div ng-if="Array.isArray(value)">
<div ng-repeat="v in value">
{{v}}
</div>
</div>
<div ng-if="!Array.isArray(value)">
{{value}}
</div>
</div>
</div>
</div>
But ng-if="Array.isArray(value)" is never true and array fields are being displayed in object form: ["Journal","Article"]. What am I missing ?
Or add this in your controller and leave rest like it is.
$scope.isArray = angular.isArray;
html would be like this :
<div ng-if="isArray(value)">
<div ng-repeat="v in value">
{{v}}
</div>
</div>
<div ng-if="!isArray(value)">
{{value}}
</div>
Instead of accessing a method on the Array object directly in the template, you should do in your controller. So for example:
<div ng-if="vm.isValueAnArray(value)">
// Some html
</div>
Your controller:
function isValueAnArray(val) {
return Array.isArray(val);
}
I haven't tested it, but logic should be in the controller, not in the template.
This is an issue of Scoping
The scope of the template is relative to $scope in the controller, so when it looks for Array, it will look for that in the controller scope (e.g. $scope.Array).
One option is to use ng-if="window.Array.isArray(value)". See the working example below.
Another option is to set $scope.Array = Array.prototype in the controller. That way there is no need to reference window before calling Array.isArray().
Another option is to create an alias for Array.isArray() in the controller scope:
$scope.isValueAnArray = Array.isArray;
Then call that function to determine if the value is an array.
angular.module('ang', [])
.controller('cont', function($scope) {
//use this to avoid referencing window in the template
//$scope.Array = Array.prototype;
$scope.document = {
"GENERAL_FIELDS": {
"Source_Type": "custom",
"Annotations": [
"216/content/Factiva_CM_001/Proteins",
"216/content/Factiva_CM_001/Fact"
],
"Content": [
" Baculovirus; Budded virus; Ultrastructure; Cryo-EM;"
],
"Title": [
"Budded baculovirus particle structure revisited"
]
},
"stn": {
"Document_Type": [
"Journal",
"Article"
]
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="ang" ng-controller="cont">
<div id="titsec" class="comdocdet" ng-repeat="(category, group) in document">
<div ng-repeat="(key, value) in group">
<div class="pTitle">
{{key}}
</div>
<div class="contdesc">
<div ng-if="window.Array.isArray(value)">
<div ng-repeat="v in value">
{{v}}
</div>
</div>
<div ng-if="!window.Array.isArray(value)">
{{value}}
</div>
</div>
</div>
</div>
</div>

displaying bootstrap thumbnails in angularjs

I have a simple angularjs app with a simple array in a controller.
(function () {
"use strict";
angular.module('stylistFormulas').controller('ServiceTypesCtrl', ServiceTypesCtrl);
function ServiceTypesCtrl() {
var vm = this;
vm.serviceTypes = [
{
"text": "Retouch",
"image": "serviceTypes/images/Retouch.jpg",
"id": "retouch"
},
{
"text": "All Over",
"image": "serviceTypes/images/AllOver.jpg",
"id": "allover"
},
{
"text": "Highlights/Lowlights",
"image": "serviceTypes/images/Highlight.jpg",
"id": "highlight"
},
{
"text": "Ombre, Balayage, Sombre",
"image": "serviceTypes/images/Balayage.jpg",
"id": "ombre"
}
]
};
}());
I want to display these records similar to bootstraps thumbnail, custom content example but using bootstraps grid with two columns for larger screens and 1 for xs.
i.e.
image image
Title of Image Title of image
image image
Title of Image Title of image
image
Title of Image
I have tried numerous examples I have found on the web with no luck. I usually get just one column or nothing but the layout without the data.
My latest attempt...
<div ng-repeat="serviceType in vm.serviceTypes">
<div class="row | $index % 3 == 0">
<div class="col-sm-6">
<div class="thumbnail">
<img ng-src="{{serviceType.image}}" />
<div class="caption">
<h3>{{serviceType.text}}</h3>
</div>
</div>
</div>
</div>
</div>
How can I display this data using the ng-repeat or something else?
Thanks
<div class="row | $index % 3 == 0"> instead
can you try with
<div ng-class="row | $index % 3 == 0">
Because you cant do manipulations inside a HTML attribute.

AngularJS: How to use the ngRepeat directive together with arrays and subarrays?

Let's assume there is an array of categories. Contained within this array, for each category, there is a variable number of images. Additionally, each image has a sub-array of paths for varying sizes of each image. Listed below is an example.
$scope.categories = [
{ "id": 1, "name": "Fashion", "images": [
{ "id": 1, "paths": [
{ "id": 1, "pathThumb":"thumbnail_A.jpg"},
{ "id": 2, "pathFull":"full_size_A.jpg"}
]}
]},
{ "id": 2, "name": "Sunsets", "images": [
{ "id": 1, "paths": [
{ "id": 1, "pathThumb":"thumbnail_B.jpg"}
{ "id": 2, "pathFull":"full_size_B.jpg"}
]},
{ "id": 2, "paths": [
{ "id": 1, "pathThumb":"thumbnail_C.jpg"}
{ "id": 2, "pathFull":"full_size_C.jpg"}
]}
]}
];
I would like to use the AngularJS ngRepeat directive to loop through the above data to produce the following
<div class="item fashion">
<img src="thumbnail_A.jpg">
</div>
<div class="item sunset">
<img src="thumbnail_B.jpg">
</div>
<div class="item sunset">
<img src="thumbnail_C.jpg">
</div>
I assume it would look something like the following:
<div class="item {{category.name}}" ng-repeat="...">
<img src="{{category.image.path.pathThumb}}">
</div>
I am not sure how to loop through an array with sub-array and extract the appropriate data. Any help would be greatly appreciated.
Use like:
<div ng-repeat="item in categories">
<div ng-repeat="image in item.images">
<a ng-href="{{image.paths[1].pathFull}}">
<img ng-src="{{image.paths[0].pathThumb}}">
</a>
</div>
</div>
you should use a wrapper container in order to get subarray of categories..
HTML
<div ng-repeat="category in categories">
<div class="item {{category.name | lowercase}}" ng-repeat="image in category.images">
<a ng-href="{{image.paths[1].path}}">
<img ng-src="{{image.paths[0].path}}">
</a>
</div>
</div>
here I used dynamic class with ng-class and using lowercase filter to get lowercase version of name string as class...
using ng-src as src attribute of img is important here because sometimes given interpolate ({{something}}) cause fail to load resource, because of data is not fecthed...
same thing for ng-href...
here is PLUNKER...

Categories

Resources