Removing last item when rendering in vue.js - vue.js

I am facing an issue where a item is getting rendered even though there is no associated Title with it as shown in my JSON. Please see the attached screenshot which will make you understand my problem (marked in red). I know this is happening due to lid in my JSON for which vue is rendering that without any associated (i.e Title) values. How do I solve this issue. Is there a way to remove the last item when rendering or is there any other way ?. I need the lid in this.dino but do not need it when rendering in my vue-app. Is there a way to pop out the last item from the JSON when rendering.
<div id="vue-app">
<div id="myList" v-for="item in items">
<p>{{item.Title}}</p>
<button v-on:click="loadmore()" class="fluid ui button">Load More</button>
Below is my vue function
new Vue({
el: '#vue-app',
delimiters: ['[[', ']]'],
data: {
dino: d_var,
cati: d_catox,
items: []
},
methods: {
loadmore: function () {
axios.get(this.dino)
.then(response => {
this.items.push(...response.data);
this.dino = "/api/search/" + this.items[this.items.length - 1].lid + "/" + this.cati;
})
}
}
})
Below is my JSON
[
{
"Title": "HealthXP1"
},
{
"Title": "HealthXP2"
},
{
"Title": "HealthXP3"
},
{
"lid": "A1234567890"
}
]

you can use 'v-if' or 'v-show' directive as:
<div id="vue-app">
<div id="myList" v-for="item in items" v-show="item.Title">
<p>{{item.Title}}</p>
<button v-on:click="loadmore()" class="fluid ui button">Load More</button>
that will show that item on the list if the item.Title is defined, otherwise it will not be rendered in DOM

Related

How to render HTML from a property of items being displayed with v-for loop?

I send an array of objects which each have a .html property that has HTML text in it, e.g. <h1>...</h1> or <h2>...</h2>
I want to have the HTML from each item display one after another in the DOM, like this:
<h1>...</h1>
<h2>...</h2>
<h2>...</h2>
<h1>...</h1>
<h2>...</h2>
However, all of these attempts do not work:
<div v-for="item in outlineItems" v-html="item.html"></div>
displays HTML wrapped in divs: <div><h1>...</h1></div> and <div><h2>...</h2></div>
<template v-for="item in outlineItems" v-html="item.html"></template>
displays nothing
<template v-for="item in outlineItems">{{item.html}}</template>
displays the literal HTML instead of rendering it
<template v-for="item in items"><template v-html="item.html"></template></template>
displays nothing
How can I simply display the contents of the .html property of each item so that the HTML in it renders, without any wrapping elements on it?
You could do it using a single wrapper element for the whole lot by concatenating all the HTML in a computed property:
new Vue({
el: '#app',
data () {
return {
outlineItems: [
{ html: '<h1>Heading 1</h1>' },
{ html: '<h2>Heading 2</h2>' },
{ html: '<h3>Heading 3</h3>' }
]
}
},
computed: {
outlineHtml () {
return this.outlineItems.map(item => item.html).join('')
}
}
})
<script src="https://unpkg.com/vue#2.6.11/dist/vue.js"></script>
<div id="app">
<div v-html="outlineHtml"></div>
</div>
Behind the scenes v-html sets the innerHTML of its corresponding DOM node. A <template> tag doesn't create a DOM node so the innerHTML can't be set anywhere.
I would add that v-html is considered an 'escape hatch'. Where possible you should avoid using it and let Vue create the HTML itself. Generally the approach would be to use a suitable data structure to hold the data (rather than a blob of markup) and then render that data structure within the template.
One possible solution is to create multiple unique components. You can even pass in props, and there are no wrappers
Vue.component('greeting', {
template: '<h1>Welcome to coligo!</h1>'
});
Vue.component('titles', {
template: '<h1>title 1</h1>'
});
Vue.component('title2', {
template: '<h2>Welcome to coligo!</h2>'
});
Vue.component('title3', {
template: '<h3>{{text}}</h3>',
props: ['text']
});
var vm = new Vue({
el: '#app',
data: {
items: [
{ type: 'greeting' },
{ type: 'titles' },
{ type: 'title2' },
{ type: 'title3', text: 'test' }
]
}
});
<script src="https://unpkg.com/vue#2.6.11/dist/vue.js"></script>
<div id="app">
<component v-for="(item,i) in items" :is="item.type" :text="item.text" :key="i"></component>
</div>

can't display array inside of html using vuejs v-for

I am returning back an array of object to render all on the screen. I am making a Ajax request to get a bunch of titles. Right now I can only manage to make it bring back one by writing vm.CourseTitle=data.d.results[0].Title; Instead, I want it to return all, I have tried vm.CourseTitle=data.d.results.Title; but that breaks it. How do I need to format the vuecode to make this display all the titles? In theory, this would be enclosed within a return{}. No idea why I can't get this to work!
new Vue({
el: "#app",
data: {
CourseTitle: [{ "Title":"Ford", "models":[ "Fiesta", "Focus", "Mustang" ] },
{ "Title":"BMW", "models":[ "320", "X3", "X5" ] },
{ "Title":"Fiat", "models":[ "500", "Panda" ] }
],
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h2>Todos:</h2>
<li v-for="course in CourseTitle"></li>
{{CourseTitle}}
</div>
You're not correctly manipulating html. It should be:
<li v-for="course in CourseTitle">{{course.Title}}</li>
Further, li tag should be wrapped with ul tag.

Array of inputs, with last field always blank for new add

JSBin and Stackoverflow snippet are below.
I am trying to have a list of input components. If all input components are filled with a value (not blank), then there should be a "new blank field" visible at the end for the user to type into. When he types into it, it should make this field apart of the list above it, maintaining focus in it.
However the problem I'm having is, focus maintains in the new field, and never moves into the array. Here is my code:
JSBIN and stackoverflow snippet - https://jsbin.com/cudabicese/1/edit?html,js,output
const app = new Vue({
el: '#app',
data: {
inputs: [
{ id:'foo', value:'foo' },
{ id:'bar', value:'bar' }
]
},
methods: {
addRow(e) {
this.inputs.push({
id: Date.now(),
value: e.target.value
})
},
deleteRow(index) {
this.inputs.splice(index, 1)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
<div id="app">
<ul>
<li v-for="(input, index) of inputs">
<input type="text" v-model="input.value">
</li>
<li v-if="inputs.filter(input => !!input.value).length">
<input type="text" #input="addRow">
</li>
</ul>
</div>
I'd recommend you put the input for the list within a computed function vs directly using the data. The examples at https://v2.vuejs.org/v2/examples/ are a good place to start.

Data attribute bound to external variable not reactive to variable changes

I'm beginning to incorporate Vue.js (v2.5.17) for a few small things on an ecommerce website, but I'm new to Vue and having some troubles. Due to some Laravel blade structure that I don't have the option of refactoring at this time, I have to split the various parts of this functionality into distinct components, and I'm having trouble with the data in one Vue instance reacting to events on the other.
Here's a CodePen with a stripped down version of the issue: https://codepen.io/jgabrielsen/pen/bxRroM/
For easy reference, the JS:
var cartStore = {
state: {
products: [PRODUCT ARRAY],
active: false
},
}
var cartHeader = new Vue({
el: '#cartHeader',
data: {
products: cartStore.state.products,
},
methods: {
setActiveTrue: function() {
cartStore.state.active = true;
console.log('Show Cart');
},
setActiveFalse:function() {
cartStore.state.active = false;
console.log('Hide Cart');
},
},
})
var cartPreview = new Vue({
el: '#cartPreview',
data: {
products: cartStore.state.products,
active: cartStore.state.active,
},
methods: {
total: function() {
var sum = 0;
for (var i = 0; i < this.products.length; i++) {
sum += this.products[i][1]
}
return sum
},
},
})
And the HTML
<div class="header">
<a id="cartHeader" #mouseover="setActiveTrue" #mouseout="setActiveFalse">CART({{ this.products.length }})</a>
</div>
<div id="cartPreview" v-show="active">
<ul>
<li v-for="product in products">
<div class="col-80">
{{ product[0] }}
</div>
<div class="col-20" style="text-align:right;">
${{ product[1] }}
</div>
</li>
</ul>
<div class="row">
<div class="col-100" style="text-align:right;">
Total Products: {{ this.products.length }}<br>
Total: ${{ this.total() }}
</div>
</div>
</div>
Long story short, I need the cartPreview instance to show up when cartHeader instance is hovered over (a basic popover effect).
Here's how I'm currently trying to do this:
I have a var cartStore that contains an array of all the cart preview data, as well as an active state.
cartPreview has a data attribute active bound to cartStore.state.active (which is false by default) and v-show="active", so it's hidden until something sets it's data attribute active to true.
cartHeader has #mouseover="setActiveTrue" and #mouseout="setActiveFalse", to toggle cartPreview's active attribute by way of the bound state in cartStore.
I can tell that the mouseover and mouseout events are firing because cartStore.state.active does change to true and false correctly and the console logs fire, but the corresponding data attribute in the cartPreview is not reactive to these changes.
I feel like I must be overlooking something super simple and/or making some major noob mistakes, but after poring over my code dozens of times and searching high and low, I'm stumped as to why it's not reactive.
I finally figured out a solution.
cartStore.state.products was reactive between cartHeader and cartPreview for adding and removing the products (methods I removed from my example because I didn't think they were relevant), but cartStore.state.active wasn't. The only difference I could see was that the cart data in products was stored in an array and active wasn't, so I made cartStore.state.active an array:
var cartStore = {
state: {
products: [PRODUCT ARRAY],
active: [ false ]
},
}
...updated it with splice:
methods: {
setActiveTrue: function() {
this.active.splice(0,1,true);
},
setActiveFalse:function() {
this.active.splice(0,1,false);
},
},
...and added v-show="active[0]" to the component, and lo and behold, it works.

Vue v-for list does not update after data changed

The idea is simple:
The Vue instance loads groups from an API.
This groups are shown using a v-for syntax.
The groupdata is formatted properly, and added to app.groups using .push function, which should update the v-for list.
However, the v-for list will not update.
When I change v-for="group in groups" (loaded externally) to v-for="group in people" (already in the app), it does give output.
HTML
<div id="app" class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Realtime Socket Chat</div>
<div class="panel-body" v-if="dataReady" v-for="group in groups"> #{{ group.name }}
<a v-bind:href="group.link" class="pull-right">
<div class="btn btn-xs btn-primary">
Join
</div>
</a>
</div>
</div>
</div>
</div>
Vue
var app = new Vue({
el: "#app",
data: {
groups: [],
people: [{name: 'hi'}, {name: 'lol'}]
},
mounted: function() {
this.load()
},
methods: {
load: function() {
axios.get('http://example.com/groups')
.then(function (response) {
console.log(response.data.groups);
response.data.groups.forEach(function(group) {
app.groups.push(group);
});
// app.groups.forEach(function (group) {
// group.link = '/g/' + group.id + '/join';
// });
// console.log(response.data.groups);
console.log(this.groups); //outputs [{...}, {...}] so this.groups is good
})
.catch(function (error) {
this.errors.push(error);
console.log(error);
})
}
}
});
API
{
"groups": [
{
"id": 1,
"name": "Photography Chat",
"created_at": "2017-11-26 08:50:16",
"updated_at": "2017-11-26 08:50:16"
},
{
"id": 2,
"name": "Media Chat",
"created_at": "2017-11-26 08:50:16",
"updated_at": "2017-11-26 08:50:16"
}
]
}
It seems like app is undefined when your load function is executed. So, using ES6 arrow function syntax, your load function should look like this:
load: function() {
axios.get('http://example.com/groups')
.then(response => {
let groups = response.data.groups || []
groups.forEach(group => {
this.groups.push(group)
})
console.log(this.groups)
})
.catch(error => {
this.errors.push(error)
console.log(error)
})
}
A fact I left out in the question (because I assumed it would not matter) was that I am working within the Laravel framework, Laravel automatically imports Vuejs within main.js. Which does not play nice when Vue is loaded in additionally. I removed main.js and it works.