vue.js - Dynamically render items on scroll when visible - vue.js

I have list of 100+ items and rendering takes too much time. I want to show just the once that are visible, and rest on scroll.
What's the best approach?
I have this snippet below, but the vue.set() isn't working.
var dbItems = [{name: 'New item'}, {name:'Another'}, {name:'Third'}];
var app = new Vue({
el: '#app',
data: {
// if I put items : dbItems, then for some reason the Vue.set() doesn't work!!
items : [],
},
methods: {
init: function () {
this.items = dbItems; // we add all items
},
makeItemVisible : function(id) {
console.log("Making visible #"+id);
this.items[id].show = 1;
Vue.set(this.items, id, this.items[id]);
}
}
});
app.init();
app.makeItemVisible(1); // this works
$(document).on('scroll', function(){
// function to show elements when visible
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<div id="app" v-cloak>
<button v-on:click="makeItemVisible(0)">MAKE VISIBLE - This button doesn't work</button>
<div class="items" v-show="items.length">
<!-- I dont know why, but (key, item) had to be switched compared to VUE documentation! -->
<div v-for="(key, item) in items">
<div v-if="item.show" style="border:2px solid green;height:700px">
You can see me: {{ item.name }} | ID: {{ key }}
</div>
<div class="item-blank" data-id="{{ key }}" v-else style="border:2px solid red;height:700px">
{{ item.name }} invisible {{ key }}
</div>
</div>
</div>
</div>

Solved.
Edit: This Vue.js is only useable in Chrome... otherwise it is incredibly slow (Firefox is slowest), it works better when loading the whole document in HTML at once.
var dbItems = [{name: 'New item'}, {name:'Another'}, {name:'Third'}];
var app = new Vue({
el: '#app',
data: {
items : dbItems
},
methods: {
makeItemVisible : function(id) {
console.log("Making visible #"+id);
Vue.set(this.items[id], 'show', 1);
}
}
});
function isScrolledIntoView(elem)
{
var docViewTop = $(window).scrollTop();
var docViewBottom = docViewTop + $(window).height();
var elemTop = $(elem).offset().top;
var elemBottom = elemTop + $(elem).height();
return (elemTop <= docViewBottom && elemTop >= docViewTop) || (elemBottom >= docViewTop && elemBottom <= docViewBottom);
}
var fn = function(){
$('.item-blank').each(function(){
if(isScrolledIntoView(this)) {
app.makeItemVisible($(this).attr('data-id'));
}
});
};
$(window).scroll(fn);
fn(); // because trigger() doesn't work
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app" v-cloak>
<div class="items" v-show="items.length">
<div v-for="(item, index) in items">
<div v-if="item.show" style="border:2px solid green;height:700px">
You can see me: {{ item.name }} | ID: {{ index }}
</div>
<div class="item-blank" :data-id="index" v-else style="border:2px solid red;height:700px;position:relative;">
{{ item.name }} invisible {{ index }}
</div>
</div>
</div>
</div>

Related

VueJS renders variable twice in loop

When I press some key in editable <li> it shows this content twice. Out of this v-for loop, it shows only once. So in this array is for example ['a'] but in <li> it shows 'aa'
new Vue({
el: '#app',
data: {
component: {
items: ['']
}
},
methods: {
onKeydown(e, index) {
if(e.key === 'Enter') {
e.preventDefault()
this.component.items.push('')
}
},
onInput(e, index, item) {
this.component.items.splice(index, 1, e.target.innerHTML)
}
}
});
<div id="app">
<ul>
<li contenteditable="true"
v-for="(item, index) in component.items"
:key="index"
#keydown="onKeydown($event, index)"
#input="onInput($event, index, item)"
>{{ item }}</li>
</ul>
{{ component.items }}
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

Run a Vue function to fetch array data inside a v-for when a Bootstrap collapse is clicked

Good day forks. Please help. I want to Run a Vue function to fetch array data inside a v-for when a Bootstrap collapse is clicked. So I have an array of items with (id, title, description etc) attributes. Then for each of the item, it has an array of sub-items. So I want to fetch the sub-items when I click the item and shows in a bootstrap collapse div as follows:
<div v-for="item in items" :key="item.id">
<p>
<a :href="'#' + item.id" data-toggle="collapse">{{
item.item_name
}}</a>
</p>
<div class="collapse" :id="item.id">
<div v-html="getSubItems(item.id)">
<p v-for="sub_item in sub_items" :key="sub_item.id">
{{ sub_item.sub_item_name }}
</p>
</div>
<p>
<span class="glyphicon glyphicon-time"></span> 5:44 Status
<span class="label label-success pull-right">{{
item.item_status ? "Done" : "Pending"
}}</span>
</p>
</div>
<hr />
</div>
And the JavaScript is as follows:
export default {
props: {},
data() {
return {
id: 1,
items: [],
sub_items: []
};
},
created() {
axios
.get("http://ip/api/v1/items")
.then(response => {
console.log(response.data);
this.topics = response.data.data;
})
.catch(error => {
console.log(error);
});
},
methods: {
getSubItems: function(item_id) {
return axios
.get("http://ip/api/v1/sub-items/" + item_id)
.then(response => {
console.log(response.data);
this.sub_items = response.data.data;
})
.catch(error => {
console.log(error);
});
}
}
};
If I was you, I would respond to a click event on the anchor tag to get the sub_items.
v-html is used to render raw HTML which is probably why your code doesn't work.
I've created a snippet below (without the axios) to show one way you could get it working.
new Vue({
el: "#app",
data() {
return {
id: 1,
items: [],
sub_items: []
};
},
created() {
this.getItems();
},
methods: {
getItems: function() {
this.items = [{
id: 1,
item_name: "Test Item"
}];
},
getSubItems: function(item_id) {
this.sub_items = [{
id: 1,
sub_item_name: "Test Sub Item"
}];
}
}
});
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js#1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.2/axios.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<div id="app">
<div v-for="item in items" :key="item.id">
<p>
<a :href="'#panel-' + item.id" data-toggle="collapse" #click="getSubItems(item.id)" role="button" aria-expanded="false" aria-controls="collapseExample">{{
item.item_name
}}</a>
</p>
<div class="collapse" :id="'panel-' + item.id">
<div>
<p v-for="sub_item in sub_items" :key="sub_item.id">
{{ sub_item.sub_item_name }}
</p>
</div>
<p>
<span class="glyphicon glyphicon-time"></span> 5:44 Status
<span class="label label-success pull-right">{{
item.item_status ? "Done" : "Pending"
}}</span>
</p>
</div>
<hr />
</div>
</div>
Try this:
<a :href="'#' + item.id" data-toggle="collapse" #click="getSubItems(item)">{{
item.item_name
}}</a>
//
getSubItems: function(item) {
if (!item.subItems){
return axios
.get("http://ip/api/v1/sub-items/" + item_id)
.then(response => {
console.log(response.data);
this.sub_items = response.data.data;
})
.catch(error => {
console.log(error);
});
}
}
}
only fetch subitems if you don't already have. So you should make a check in the function if you have them.
The simplest way I think would be to save the results on the item itself as item.subItems then once you open you don't need to open again

Removing text from title in an array using Vue

I am trying to replace the text "this | " with "" from the titles in an array.
What is the best way to do this?
Any help would be greatly appreciated :)
my js code:
let Feed=require('rss-to-json')
Feed.load('http://localhost:3000/news', function (err, content) {
let appTitleList = new Vue({
el: '#news',
data: {
rssItems: content.items
},
methods:{
removeFunction: function () {
this.content.title = this.content.title.replace("this | ", "");
}
})
})
the html:
<div class="card" id="news">
<ul class="list-group list-group-flush">
<li class="list-group-item" v-for="item in rssItems" >
<b>{{ item.title }}</b>
<p>{{ item.description }}</p>
</li>
</ul>
</div>
I don't see what this.content is. I don't see where you are using removeFunction, but if you are, try this:
removeFunction: function () {
const rssItems = [...this.rssItems]
for (const item of rssItems) {
item.title = item.title.replace("this | ", "");
}
this.rssItems = rssItems
}
Alternatively, mutate the rssItems before setting them in the state, and maybe you won't need the removeFunction.
data: {
rssItems: content.items.map(i => ({
...i,
title: i.title.replace("this | ", "")
}))
}
This can be a possible solution: fetching your API's posts when the Vue.JS instance is created, mutating the related titles and enqueue each post.
<head>
... your imports from CDN
</head>
<body>
<div id="app">
<div class="card" id="news">
<ul class="list-group list-group-flush">
<li class="list-group-item" v-for="item in items">
<b>{{ item.title }}</b>
<p>{{ item.description }}</p>
</li>
</ul>
</div>
</div>
<script>
new Vue({
el: '#app',
data () {
return {
items: []
}
},
created () {
Feed.load('your-api-endpoint', (err, content) => {
// TODO: error handling...
for (let i = 0; i < content.items.length; i++) {
const item = content.items[i];
// Mutate the title and enqueue the post
item.title = item.title.replace('Title | ', '');
this.items.push(item);
}
});
}
})
</script>
</body>
Also, watch out that the field data in the Vue.JS instance must be a function, not an object. More about this, here.

Apply v-focus to the first input field on a page

I've a Vue component in which I'm trying to autofocus the first field using v-focus. But my problem is, I've dynamic components that will be included at the top of the page. So in that case how can I apply autofocus to dynamically included component?
They key is to set ref on all your inputs to the same string like this:
<input type="text" ref="myInputs"/>
Then you will have access to an array called this.$refs.myInputs inside an event handler.
So you just need to do
this.$refs.myInputs[0].focus();
new Vue({
el: "#app",
mounted() {
this.$refs.myInputs[0].focus();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="app">
<div>
<div v-for="index in 3" :key="index">
<input ref="myInputs" type="text" />
</div>
</div>
</div>
It's hard to tell how you're adding the input(s) to the DOM, without any pseudo code from you, but this is one way to do it..
[CodePen mirror]
new Vue({
el: "#app",
data: {
inputs: ["firstName", "lastName"]
},
watch: {
inputs() {
this.$nextTick(() => {
this.focusFirstInput();
});
}
},
methods: {
focusFirstInput() {
let first = this.inputs[0];
let firstInput = this.$refs[first][0];
firstInput.focus();
},
handleClick() {
this.inputs.push("newInput");
}
},
mounted() {
this.focusFirstInput();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="app">
<div>
<div v-for="(input, index) in inputs" :key="index">
<input :ref="input" type="text" />
</div>
<div>
<button type="button" #click="handleClick">Click to add input</button>
</div>
</div>
</div>
I found this answer on Laracast and it worked for me. All I did was insert the code below in my dynamic form field.
this.$nextTick(() => {
let index = this.items.length - 1;
let input = this.$refs.title[index];
input.focus();
});
HTML
<div id="app">
<ul v-for="item in items">
<li>
<input :ref="'title'" v-model="item.title">
</li>
</ul>
<button v-on:click="addItem">Add Item</button>
</div>
JS
let app = new Vue({
el: '#app',
data: {
items: [
{title: 'Apple'},
{title: 'Orange'},
]
},
methods: {
addItem(){
this.items.push({title: "Pineapple"});
this.$nextTick(() => {
let index = this.items.length - 1;
let input = this.$refs.title[index];
input.focus();
});
}
}
});
Note: make sure to add :ref="'title'" into your dynamic form field.
Credits to the original author of the solution.

Vue: Getting a default value for v-select

I have a list of cartItems and I'm generating a dropdown for each one. Each cartItem has a field called orig_quantity that I'm looking to set the default value of the drop down to. I tried doing :value="item.orig_quantity" but that doesn't seem to be doing it.
computed: {
quantityOptions: function() {
return [1,2,3]
}
}
<div v-for="(item, index) in cartItems"
<div>{item.product_name}</div>
<v-select :options="quantityOptions"
v-on:change="updateQuantity($event,item)">
</v-select>
</div>
Sorry about that - I misunderstood your question at first.. I have updated my answer below.. This should be sufficient to get the idea across (the code stands to be cleaned - its 'pseudo' enough to get the idea across, though)..
In CodePen form, which I find easier to read:
https://codepen.io/oze4/pen/vMLggE
Vue.component("v-select", VueSelect.VueSelect);
new Vue({
el: "#app",
data: {
cartItems: [{
product_name: "Chair",
original_quantity: 7,
total_quantity: 9,
pending_quantity: null,
price: "$19.99"
},
{
product_name: "Couch",
original_quantity: 3,
total_quantity: 6,
pending_quantity: null,
price: "$29.99"
}
],
},
methods: {
getStock(cartItem) {
let ci = this.cartItems.find(i => {
return i.product_name === cartItem.product_name;
});
return [...Array(ci.total_quantity + 1).keys()].slice(1);
},
updateQty(cartItem) {
alert("this is where you would post");
let ci = this.cartItems.find(i => {
return i.product_name === cartItem.product_name;
});
ci.original_quantity = ci.pending_quantity;
}
}
});
h5,
h3 {
margin: 0px 0px 0px 0px;
}
.reminder {
color: red;
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.10/dist/vue.js"></script>
<script src="https://unpkg.com/vue-select#2.6.4/dist/vue-select.js"></script>
<div id="app">
<h4>Cart Items:</h4>
<div v-for="(item, index) in cartItems">
<h3>{{ item.product_name }}</h3>
<h5>Price: {{ item.price }} | InBasket: {{ item.original_quantity }}</h5>
<small>Change Quantity: </small>
<v-select :value="item.original_quantity" :options="getStock(item)" #change="item.pending_quantity = $event"></v-select>
<button #click="updateQty(item)" type="button">Update {{ item.product_name }} Qty</button><small class="reminder">*update after changing quantity</small>
<br/>
<hr/>
</div>
</div>
You should be able to add a v-model attribute to the select.
<div v-for="(item, index) in cartItems"
<v-select v-model="item.orig_quantity" :options="quantityOptions"
v-on:change="updateQuantity($event,item)">
</v-select>
</div>