change items to display in ox carousel with vue.js - javascript

Hi, I am using Buefy's "carousel" component with Vue.js. In laptop resolution I have to show 3 elements. But on the phone I want an article to be shown. I made a function that depends on the resolution the property of the carousel is changed "elements to show" from 3 to 1. But the bad thing that that property is loaded when the page starts not when the screen changes.
Do you have any advice on how to do it? I don't want to have to reload the page to make it happen. But at least reload the component
<b-carousel-list v-model="test" :data="items" :items-to-show="valor" :items-to-list="3" icon-size="is-large">
<template slot="item" slot-scope="props">
<div class="card redondo">
<div class="card-image">
<figure class="image is-5by4">
<a #click="info(props.index)"><img :src="props.list.image" class="imagen-redondo"></a>
</figure>
methods: {
info(value) {
this.test = value
},
cambiar() {
return this.test = 0;
},
itemMostrar() {
if ($(window).width() < 720) {
return this.valor = 1;
} else {
return this.valor = 3;
}
},
},

Use a computed instead of a method. Your method will not re-run except you call it. A computed will update by itself

Related

vue infinite load duplicate content issue

i install vue infinite loading for my chat app (backend laravel), everything is work now but if data respone from server contain something like a image or file, it seem to be load again and again like my image highlight bellow, correct the pic is load just one.
anyone can help with this issue? thanks!
infiniteHandler($state) {
setTimeout(() => {
axios.get('/messages', {
params: {
page: this.page,
room_id:this.user[0].shop_id === 0?this.user[0].id : this.user[0].shop_id
},
}).then(({ data }) => {
if (data.data.length) {
this.page += 1;
this.messages.unshift(...data.data.reverse());
$state.loaded();
} else {
$state.complete();
}
});
}, 200);
},
<div class="card-body" style="overflow: auto; height: 60vh; position: relative;" ref="card" #scroll="onScroll" >
<infinite-loading direction="top" #infinite="infiniteHandler"> </infinite-loading>
<div class="media" v-for="(message, $index ) in messages" :key="$index">
</div>
</div>
Try to set a different :key in your v-for. This is important for Vue to know which element is already rendered, and which should be re-rendered.
I don't know what your data looks like but you should have a uniq id for every items, or a name...
Using the index on dynamic lists will likely cause unecessary re-renders.
<div
class="media"
v-for="message in messages"
:key="message.id"
>
...
</div>
i remove vue-infinite-loading and end up with this solution Preserve scroll position on DOM update in vue.js . everything work fine now!

conditionally render div based on attributes of another element vue

I have a small container of text. What I'm trying to do is If the text length is large, collapse the div, then have a button that says "...show more", once pressed expands the div. pressed again collapses the div.
That’s fine and works.
I have an issue at the moment. The div is initially set to collapse=true. The “...show more” button is displayed.
The thing I want to change is, if the text content is not long, it will not be collapsed, the show more button will not be displayed.
Template
<v-card v-show="showAccount" class="mb-4">
<v-card-title class="title-container align-start">
<div class="title-data" :class="{collapsed: isElementOverflown}" ref="title-data">
<h1 class="title mb-2"><router-link :to="{name: 'profile', params: {account: account.account}}">{{ account.account }}</router-link></h1>
<router-link v-if="isActiveUserAccount" :to="{name: 'account-image', params: {account: account.account}}">
<v-avatar color="#c35219" size="56" class="mr-4 mb-2">
<img v-if="accountMedia" :src="accountMedia" :alt="account.account" />
<span v-else class="white--text headline">{{ account.account[0].toUpperCase() }}</span>
</v-avatar>
</router-link>
<template v-else>
<v-avatar color="#c35219" size="56" class="mr-4 mb-2">
<img v-if="accountMedia" :src="accountMedia" :alt="account.account" />
<span v-else class="white--text headline">{{ account.account[0].toUpperCase() }}</span>
</v-avatar>
</template>
<div class="caption my-0" ref="bio">
<nl2br v-if="account.about" tag="p" :text="account.about"></nl2br>
</div>
</div>
<button v-if="showButton" type="button" style="font-size:small; margin: auto; margin-right: 5%" #click="toggleHeight">
{{showMoreTextLabel}}
</button>
</v-card-title>
JS
mounted() {
// elements have been created, so the `ref` will return an element.
// but the elements have not necessarily been inserted into the DOM yet.
// you can use $nextTick() to wait for that to have happened.
// this is espeically necessary if you want to to get dimensions or position of that element.
this.$nextTick(() => {
console.log("refs", this.$refs); // logs correct this.$refs
console.log("$refs.title-data", this.$refs["title-data"]); //undefined
let el = this.$refs["title-data"];
if (el.offsetHeight < el.scrollHeight || el.offsetWidth < el.scrollWidth) {
this.isElementOverflown = true;
this.showButton = true;
}
})
},
toggleHeight() {
if (this.$refs && 'title-data' in this.$refs) {
this.$refs['title-data'].classList.toggle('collapsed');
this.$refs['title-data'].classList.contains('collapsed')
? this.showMoreTextLabel = "...show more"
: this.showMoreTextLabel = "...show less";
}
},
In mounted I’m getting an error that
this.$refs[“title-data”] is undefined but
This.$refs is there and it shows the correct refs. I’m not sure why.
Thank you for any help!
You can create a computed property that checks if the length of your text exceeds a given number.
computed: {
isTextLengthLongEnough() {
if(el.offsetHeight > 150) {
this.showButton = true;
}
}
Then you can check in your template with a v-if if that computed property is true or false and then display the button or not.
Unfortunately, I couldn't get either of the above answers to work. el was coming up as undefined, So if someone could explain to me based on the code I have in the question how to get el, that would be great.
I did a work around not ideal, but it works where I have code in updated, So I'm going with that for now. Thanks very much for everyone's help
Here is the code I used
updated() {
if ('title-data' in this.$refs) {
const el = this.$refs['title-data']
const heightDiff = Boolean(el.scrollHeight - el.offsetHeight > ALLOWED_HEIGHT_VARIANCE)
if (heightDiff) {
this.showButton = heightDiff
el.className += ' read-more'
}
}
},

Using Vue mouseover event to set styles on two elements is very laggy. Is there a better way to do this?

I'm relatively new to Vue so I may not be doing this in the most performant way. I have a table of rows created with a v-for loop. I also have a corresponding list of row titles also created with a v-for loop. They need to be separate DOM elements to enable fixed header scrolling. When I hover over the row I want to change its color AND the color of the corresponding row title.
So I thought you would be able to do the following with Vue:
On mouseover set a data property to the index of the element being hovered over.
Bind a class to the elements of interest to change their styles when the index is equal to their index from the v-for loop.
This does work but it results in very slow / laggy code. I have quite a few rows (40) and columns (60) but it isn't an enormous amount.
An abridged version of the code is below:
Template html
<div class="rowTitle"
v-for="(rowName, i) in yAxis.values" :key="i"
#mouseover="hover(i)"
:class="hoverActive(i)" >{{rowName}}</div>
<div class="row"
v-for="(row, i) in rows" :key="i"
#mouseover="hover(i)"
:class="hoverActive(i)" >{{row}}</div>
Vue object
export default {
data:()=>(
{
rows: rows,
yAxis: yAxis,
rowHover: -1,
}
),
methods:{
hover(i) {
this.rowHover = i
},
hoverActive(i) {
return this.rowHover == i ? 'hover' : ''
}
},
}
The likely answer is that the hover event is firing very frequently, and each time it fires you are forcing vue to re-render. If you could add a debounce the event handler, or even better mark the event handler as passive, it would make a big difference in performance. In this runnable snippet, I'm telling vue to use a passive event modifier (see https://v2.vuejs.org/v2/guide/events.html#Event-Modifiers) to tell the browser not to prioritize that event handler over critical processes like rendering.
To be clear, if you can use CSS to solve the problem like a commenter mentioned (see https://developer.mozilla.org/en-US/docs/Web/CSS/:hover), I strongly advise that as the browser's performance should be excellent with that approach. But sometimes css does not solve everything, so a debounce or a passive event listener might be your next best option. You could also experiment with the "stop" (stop propagation) modifier if additional tuning is required.
const grid = [];
for (var i = 0; i < 40; i++) {
grid.push([]);
for (var j = 0; j < 60; j++) {
grid[i].push({
i,
j
});
}
}
Vue.component('aTable', {
props: ['grid'],
data() {
return {
rowHover: -1
}
},
template: `
<div class="table">
<div class="row" v-for="(row, x) in grid">
<span v-for="cell in row"
#mouseover.passive="hover(x)"
:class="hoverActive(x)"
class="row-cell"
>i:{{cell.i}},j:{{cell.j}}</span>
</div>
</div>
`,
methods: {
hover(i) {
this.rowHover = i
},
hoverActive(i) {
return this.rowHover == i ? 'hover' : ''
}
}
})
var app = new Vue({
el: '#app',
data() {
return {
grid
}
}
})
body {
font-size: 11px;
}
.row-cell {
min-width: 40px;
display: inline-block;
border: 1px solid gray;
}
.row-cell.hover {
color: blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
My table
<a-table :grid="grid" />
</div>

Create custom slider to control 'scrollLeft' of html element using Vue

I'm trying to create a custom slider to emulate horizontal scroll behavior of a specific element. the slider is implemented as a stand alone component and the selector for the element is passed to the slider component as a property:
<template>
<div class="slidercontainer">
<h1>{{contentWidth}}</h1>
<button class="right-scroll"></button>
<input
type="range"
min="1"
:max="contentWidth"
value="1"
class="slider"
v-on:input="handleScroll"
>
<button class="left-scroll"></button>
</div>
</template>
<script>
export default {
props: ["el"],
data() {
return {
scrollLeft: 0
};
},
methods: {
handleScroll($event) {
this.content.scrollLeft = this.contentWidth - $event.target.value;
}
},
computed: {
content() {
return document.querySelector(this.el);
},
contentWidth() {
return this.content.scrollWidth - this.content.clientWidth;
}
}
};
</script>
the problem though, is that content wont update and always return 0. when first loaded the app is waiting for some data from the server and thats why when mounted the contentWidth equals 0, but i thought returning content as a computed property should take care of that, but nothing happens after the content is injected with some new...well - content :)
any idea how to solve this?

The content not shown properly in function callback in Vue.js

I've got two problems here. The first is that I can't get the star rendered properly. I can do it if I change the value in the data() function but if I want to do it in a function callback way, it doesn't work (see comments below). What's going wrong here? Does it have something to do with Vue's lifecycle?
The second one is that I want to submit the star-rate and the content of the textarea and when I refresh the page, the content should be rendered on the page and replace the <textarea></textarea> what can I do?
I want to make a JSFiddle here but I don't know how to make it in Vue's single-file component, really appreciate your help.
<div class="order-comment">
<ul class="list-wrap">
<li>
<span class="comment-label">rateA</span>
<star-rating :data="dimensionA"></star-rating>
</li>
</ul>
<div>
<h4 class="title">comment</h4>
<textarea class="content" v-model="content">
</textarea>
</div>
<mt-button type="primary" class="mt-button">submit</mt-button>
</div>
</template>
<script>
import starRating from 'components/starRating'
import dataService from 'services/dataService'
export default {
data () {
return {
dimensionA: '' //if I changed the value here the star rendered just fine.
}
},
components: {
starRating
},
methods: {
getComment (id) {
return dataService.getOrderCommentList(id).then(data => {
this.dimensionA = 1
})
}
},
created () {
this.getComment(1) // not working
}
}
</script>
What it seems is scope of this is not correct in your getComment method, you need changes like following:
methods: {
getComment (id) {
var self = this;
dataService.getOrderCommentList(id).then(data => {
self.dimensionA = 1
})
}
},
As you want to replace the <textarea> and render the content if present, you can use v-if for this, if content if available- show content else show <textarea>
<div>
<h4 class="title">comment</h4>
<span v-if="content> {{content}} </span>
<textarea v-else class="content" v-model="content">
</textarea>
</div>
See working fiddle here.
one more problem I have observed in your code is you are using dynamic props, but you have assigned the prop initially to the data variable value in star-rating component, but you are not checking future changes in the prop. One way to solve this, assuming you have some other usage of value variable is putting following watch:
watch:{
data: function(newVal){
this.value = newVal
}
}
see updated fiddle.

Categories