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
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>
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
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.
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.
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>