How to find width on components in vue.js - vue.js

Html:
<ul class="nav nav-tabs nav-style">
<tabs
v-for="tabelement in tabelements" :name="tabelement":tabselected="tabelement == type ? 'active': ''" v-on:click="tabclick(tab)"
></tabs>
</ul>
JS:
Vue.component('tabs', {
template:'<li :class="tabselected">{{name}}</li>',
props:['name','tabselected']
});
I want to find the sum of width of all li in this example.

Add watch block to your script.
script
watch: {
'tabelements': function(val) {
var lis = this.$refs.ul.getElementsByTagName("li");
for (var i = 0, len = lis.length; i < len; i++) {
console.log(lis[i].clientWidth); // do something
}
console.log(this.$refs.ul.clientWidth, this.$refs.ul.scrollWidth);
}
}
if scrollWidth > clientWidth, u can show your arrows.
Updated. Explain Fiddle
template
<tabs ref="ul">
Put ref on component otherwise instance doesnot know about it
script
this.$nextTick
This function run method when dom is updated

Related

Vue.js : Range slider with two handles

I want to create a vue js components where it contains a range slider of hours with two handles.
I use vue3 + vite.js
I tried this code to implement the components but when I drag one of handles I have an error
Code :
this is the template :
<template>
<div>
<input type="range" ref="rangeInput" v-model="rangeValue" #input="updateRange"/>
<div class="range-slider">
<div class="handle" :style="{left: leftHandle + '%'}" #mousedown="startHandleDrag(1)">
{{ formatHour(rangeValue[0]) }}
</div>
<div class="handle" :style="{left: rightHandle + '%'}" #mousedown="startHandleDrag(2)">
{{ formatHour(rangeValue[1]) }}
</div>
</div>
</div>
</template>
and this is the script :
<script>
export default {
data() {
return {
rangeValue: [8, 18],
handleDragging: 0
};
},
computed: {
leftHandle() {
return this.rangeValue[0];
},
rightHandle() {
return this.rangeValue[1];
}
},
methods: {
updateRange(event) {
const value = event.target.value;
const range = this.rangeValue;
if (this.handleDragging === 1) {
range[0] = value[0];
} else if (this.handleDragging === 2) {
range[1] = value[1];
} else {
range[0] = value[0];
range[1] = value[1];
}
this.rangeValue = range;
},
startHandleDrag(handle) {
this.handleDragging = handle;
document.addEventListener("mouseup", this.stopHandleDrag);
document.addEventListener("mousemove", this.updateRange);
},
stopHandleDrag() {
this.handleDragging = 0;
document.removeEventListener("mouseup", this.stopHandleDrag);
document.removeEventListener("mousemove", this.updateRange);
},
formatHour(value) {
return value + ":00";
}
}
};
</script>
Error :
any ideas to solve it !!!
In your startHandleDrag() and stopHandleDrag(), you bind updateRange() to the mousemove event:
document.addEventListener("mousemove", this.updateRange);
There are two issues with that:
The target of the mousemove event is the element under the cursor. This can be any element, and unless it happens to be an input, it will not have a value attribute (and if it does, it will not hold an array). If you really want to use the "mousemove" event, use the cursor coordinates like pageX or pageX.
You bind it as a function pointer (addEventListener("mousemove", this.updateRange)), and when called from the listener, this will refer to element.target. To avoid this, either use an arrow function (addEventListener("mousemove", (e) => this.updateRange(e))) or bind this (addEventListener("mousemove", this.updateRange.bind(this))).
I don't fully understand what you want to do with the handles, but my guess is that adding and removing listeners is a workaround, and you actually want to make them draggable? If so, have a look at the drag event. Hope that helps!

vue component compilation issue

I'm pretty new to Vue.js so bear with me. I'm working on a project where I created two new vue components, one is a tab/toggle element, the other is a cookie banner. However, when both are added to the page the cookie banner does not compile. The HTML is rendered but it still contains all the vue syntax in its uncompiled form. Does anyone see where the conflict is occurring between these two components? I don't see any errors in the console so I'm at a loss on how to begin debugging.
Component 1:
(function () {
var _instance = new Vue({
el: "#multiTrackSwiper",
data: {
tabs: {}
},
methods: {
checkActiveTab: function (index) {
if (this.tabs['active']) {
return this.tabs['active'] === index;
} else {
return index === "0";
}
},
handlerActiveTab: function (index) {
Vue.set(this.tabs, 'active', index);
}
}
});
})();
#using Sitecore.Feature.Media.Models.Components
#model List<ITrackWithCarousel>
#if (Model != null && Model.Count > 0)
{
if (Model.Count == 1)
{
<div class="c-product-details__track">
#Html.Partial("TrackWithCarousel", Model[0])
</div>
}
else
{
var index = 0;
<div id="multiTrackSwiper" class="multi-track-swiper" vue-instance v-cloak>
<ul class="nav nav-tabs">
#foreach (var track in Model)
{
<li class="nav-item">
<button id="tab_#track.Name.Replace(" ","_")" data-bs-toggle="tab" class="nav-link"
v-bind:class="{ 'active':checkActiveTab('#index') }"
v-on:click="handlerActiveTab('#index')">
#track.DisplayName
</button>
</li>
index++;
}
</ul>
#{ index = 0; }
#foreach (var track in Model)
{
<div class="c-product-details__track c-product-details__multitrack" aria-labelledby="tab_#track.Name.Replace(" ","_")"
v-bind:class="{ 'active':checkActiveTab('#index') }">
#Html.Partial("TrackWithCarousel", track)
</div>
index++;
}
</div>
}
}
Component 2:
(function () {
var _instance = new Vue({
el: "#cookie-banner",
data: {
cookieSaved: null
},
methods: {
saveSessionCookie: function () {
var expiry = (new Date(Date.now() + 600 * 1000)).toUTCString(); // 3 days 259200
document.cookie = "cookie-banner-closed=true; expires=" + expiry + ";path=/;"
this.cookieSaved = true;
}
},
mounted: function () {
if (document.cookie.includes('cookie-banner-closed')) {
this.cookieSaved = true;
} else {
this.cookieSaved = null;
}
}
});
})();
<div id="cookie-banner" vue-instance v-cloak>
<div class="cookie-disclaimer" v-if="!cookieSaved">
<div id="cookie-notice">
<div class="cookie-inner-module h-spacing">
This website uses cookies. We do this to better understand how visitors use our site and to offer you a more personal experience. We share information about your use of our site with social media and analytics partners in accordance with our Privacy Notice</a>.
<i class="fas fa-times" v-on:click="saveSessionCookie"></i>
</div>
</div>
</div>
</div>
I've tried switching both vue components into vue instances instead but that doesn't resolve the issue.
The HTML is rendered but it still contains all the vue syntax in its uncompiled form.
I don't think that you are using Vue format/syntax. So it will render what you are typed inside html.

Infinite scroll not working in old Nebular site

I have a website which is based on an old version of ngx-admin (Nebular) which i am assuming version 2.1.0.
the new nebular documentation does not seem to apply to my website, so i am trying to add an infinite loop functionality using ngx-infinite-scroll, but the scroll event is not fired.
my example which i try to apply is taken from (https://stackblitz.com/edit/ngx-infinite-scroll?file=src%2Fapp%2Fapp.module.ts)
my component:
//our root app component
import { Component } from "#angular/core";
#Component({
selector: "my-app",
styleUrls: ["./test.component.scss"],
templateUrl: "./test.component.html"
})
export class TestComponent {
array = [];
sum = 100;
throttle = 300;
scrollDistance = 1;
scrollUpDistance = 2;
direction = "";
modalOpen = false;
constructor() {
console.log("constructor!!");
this.appendItems(0, this.sum);
}
addItems(startIndex, endIndex, _method) {
for (let i = 0; i < this.sum; ++i) {
this.array[_method]([i, " ", this.generateWord()].join(""));
}
}
appendItems(startIndex, endIndex) {
this.addItems(startIndex, endIndex, "push");
}
prependItems(startIndex, endIndex) {
this.addItems(startIndex, endIndex, "unshift");
}
onScrollDown(ev) {
console.log("scrolled down!!", ev);
// add another 20 items
const start = this.sum;
this.sum += 20;
this.appendItems(start, this.sum);
this.direction = "down";
}
onUp(ev) {
console.log("scrolled up!", ev);
const start = this.sum;
this.sum += 20;
this.prependItems(start, this.sum);
this.direction = "up";
}
generateWord() {
return "Test Word";
}
toggleModal() {
this.modalOpen = !this.modalOpen;
}
}
my html:
<h1 class="title well">
ngx-infinite-scroll v-{{nisVersion}}
<section>
<small>items: {{sum}}, now triggering scroll: {{direction}}</small>
</section>
<section>
<button class="btn btn-info">Open Infinite Scroll in Modal</button>
</section>
</h1>
<div class="search-results"
infinite-scroll
[infiniteScrollDistance]="scrollDistance"
[infiniteScrollUpDistance]="scrollUpDistance"
[infiniteScrollThrottle]="throttle"
(scrolled)="onScrollDown()"
(scrolledUp)="onUp()" style="height:100%">
<p *ngFor="let i of array">
{{ i }}
</p>
</div>
Any hints how i can get the scroll event to fire?
Well, finally i got it.
the tricky part of ngx-admin was to findout that one needs to add withScroll="false" to nb-layout in the theme file.
then in the component file this is what worked for me:
#HostListener("window:scroll", ["$event"])
onWindowScroll() {
//In chrome and some browser scroll is given to body tag
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
console.log("scrolled");
}
}
How to detect scroll to bottom of html element
maybe this will help someone

How to Add javascript paggination code in Vue js component

I'm trying to add pagination code in the Vue component and I've tried to add the code in the mounted hook to call the function but it doesn't work. I want to load the code after component loaded completely.Also, jQuery code doesn't load in Vue component. Do I need to change my code to pure javascript for that. Can you guide me how to fix the issue?
// Create a root instance for each block
var vueElements = document.getElementsByClassName('search-bento-block');
var count = vueElements.length;
const store = new Vuex.Store({
state: {
query: drupalSettings.bento.query ? drupalSettings.bento.query : '',
bentoComponents: []
},
mutations: {
add (state, payload) {
state.bentoComponents.push(payload)
}
},
getters: {
getComponents: state => {
return state.bentoComponents
}
}
})
// Loop through each block
for (var i = 0; i < count; i++) {
Vue.component('results', {
template: `
<div v-if="results && results.length > 0">
<div v-for="result in results">
<div class="search-result-item">
<div class="image-holder">
<img src="https://images.unsplash.com/photo-1517836477839-7072aaa8b121?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=750&q=80">
</div>
<div class="container-content">
<a v-bind:href="result.url">
<h3 v-html="result.title"></h3>
</a>
<p>Subjects: <span v-html="result.subjects"></span></p>
</div>
</div>
</div>
</div>
<div v-else>
<p>No results found.</p>
</div>
`,
props: ['results'],
})
new Vue({
el: vueElements[i],
store,
data: {
message: 'Hello There!',
results: [],
total: 0,
bentoSettings: [],
},
methods: {
addComponentToStore: function (type) {
this.$store.commit('add', type);
console.log("test");
console.log(this.results.length);
}
},
mounted: function() {
// console.log(this.$route.query.bentoq);
const id = this.$el.id;
this.bentoSettings = drupalSettings.pdb.configuration[id];
var bentoConfig = drupalSettings.pdb.configuration[id].clients[this.bentoSettings.bento_type] ? drupalSettings.pdb.configuration[id].clients[this.bentoSettings.bento_type].settings : [];
axios
.get('/api/search/' + this.bentoSettings.bento_type, {
params: {
query: this.$store.state.query,
plugin_id: this.bentoSettings.bento_type,
bento_limit: this.bentoSettings.bento_limit,
bento_config: bentoConfig,
}
})
.then(response => {
console.log(response);
this.results = response.data.records;
this.total = response.data.total;
this.addComponentToStore({
title: this.bentoSettings.example_field,
count: this.total
});
})
.catch(error => {
console.log(error.response);
})
}
});
}
// I'm trying to call following function in Vue component.
function baseThemePagination1() {
//Pagination
pageSize = 3;
var pageCount = $('.line-content').length / pageSize;
for (var i = 0; i < pageCount; i++) {
$('#pagin').append('<li><a href=\'#\'>' + (i + 1) + '</a></li> ');
}
$('#pagin li').first().find('a').addClass('current')
showPage = function(page) {
$('.line-content').hide();
$('.line-content').each(function(n) {
if (n >= pageSize * (page - 1) && n < pageSize * page)
$(this).show();
});
}
showPage(1);
$('#pagin li a').click(function() {
$('#pagin li a').removeClass('current');
$(this).addClass('current');
showPage(parseInt($(this).text()))
});
}
What you are trying to do is not the recommended way to use vue, direct DOM manipulation is one of the things that vue is made to avoid (although can be done). The Vue way would be to bind the value you want to a variable with v-model assuming it is an input and then create your pagination based on that.
If you insist on DOM manipulation then try ref="line-content" and then call it like so:
this.refs.line-content.
In terms of reacting to a page change click simply use a method in your methods section there is no reason to use jQuery for that.
See here for a simple explanation:
https://medium.com/#denny.headrick/pagination-in-vue-js-4bfce47e573b

How to make Vue reactive content update quickly?

I have a problem with Vue being sluggish on updating reactive content.
I want users to select items from a list. When items are selected they should be marked. For test I am just setting a border around the selected items. The problem is when I have multiple items, I feel that Vue takes very long time to update (react) the class on the selected items.
So I have a simple reactive store which looks as follows:
export default new Vuex.Store({
state: {
selections: []
},
mutations: {
set_selections (state, sel) {
state.selections = sel;
}
}
})
I pass this store to my component which renders a simple list which will contain many items.
<p v-for="item in items">
<span v-bind:class="{ 'is-selected': isSelected(item) }" v-on:click="onSelect(item)">
{{ item.name }}
</span>
</p>
So each item will have a unique id which I add/remove from my Vuex store state, selections:
onSelect: function(item, event){
let itemId = item._id;
let sel = this.selections;
if (sel.indexOf(itemId) !== -1) {
var index = sel.indexOf(itemId);
sel.splice(index, 1);
} else {
sel.push(itemId);
}
this.$store.commit("set_selections", sel);
},
where,
selections: function() {
return this.$store.state.selections;
}
is a computed property which fetches the current selections.
The method which checks if the item is selected, and thus adds the "is-selected" class to the DOM element, looks as follows:
isSelected: function(item){
let itemId = item._id;
let sel = this.selections;
if (sel.indexOf(itemId) !== -1) {
return true;
}
return false;
},
Problem
When I have many items in my list I feel that the reactive content is very sluggish. When I click on an item it takes about 500ms to 1 sec before the item is marked. (Note I have very many item). Am, I perhaps doing something wrong? Since I loop using , v-for, I understand that Vue must recalculate the isSelected method for every item which might be time consuming.
Ofcourse I could add/remove the class directly on the onClick event but then I loose the whole point of using Vue. How would you deal with this problem?
I think your list updating is slow because iterating over your selection array can be expensive if you're doing this in thousand components/items.
Also as mentioned in the comments key binding can improve the speed as well (speed difference not tested).
But in my demo the best result was after creating an selection object and just check the property in selected.
And it better to do selected check as computed property - it's faster.
For computed property it takes around 200ms to select the item with a list of 1000 items. With a selection method it would take ~450ms - not exactly sure why it is so much slower.
Here is a screenshot of the performance for one selection event 200ms (Tested in Chrome 59.0.3071.115 (64bit) - i5-6200 / 8GB RAM / Win10):
At the moment, this is my fastest version. I've also started with 0,5 to 1 sec to display the selection.
Please have a look at the demo below or at this fiddle.
const listItem = {
props: ['item'],
template: `
<li :class="{ 'is-selected': selected }" #click="$emit('selected', item)">
{{ item.name }}
</li>
`,
computed: {
...Vuex.mapState(['selections']),
selected () {
// 200ms time to mark item for click with 1000 list items - clicked Test 326
return this.selections[this.item.id] !== undefined; // object property check is fast
}
},
methods: {
selected () {
// 450ms to mark selection
//console.log('selected', this.selections, !!this.selections[this.item.id]);
// slightly slower than computed property
return this.selections[this.item.id] !== undefined; // object property check is fast
// array --> slow because another iteration for each component required
//this.selections.indexOf(this.item) !== -1
}
}
};
const list = {
props: ['items'],
template: `
<ul>
<list-item
v-for="item in items"
:item="item"
#selected="select"
:key="item.id"></list-item>
</ul>
`,
components: {
listItem
},
methods: {
select(item) {
this.$store.commit('set_selection', item)
}
}
};
const store = new Vuex.Store({
state: {
selections: []
},
mutations: {
set_selection (state, item) {
//state.selections = sel;
console.log('clicked', item, state.selections[item.id]);
if (state.selections[item.id]) {
// in object --> remove from selection
//let {[item.id]: deleted, ...newState} = state;
state.selections[item.id] = undefined;
}
else {
// not in object --> add item
state.selections = {
...state.selections,
[item.id]: item
}
}
// console.log(state.selections, !!state.selections[item.id]);
/*
--> array approach is slow
if (state.selections.indexOf(item) === -1)
{
// not in list --> push to list
state.selections.push(item);
}
else {
// in list --> remove selection
state.selections.pop(item);
}*/
}
}
})
function createItems(count) {
let items = [];
for(let i=0; i< count; i++) {
items.push({
id: i,
name: 'Test ' + i
});
}
return items;
}
new Vue({
el: '#app',
store,
data () {
let items = createItems(1000);
return {
items
};
},
components: {
list
}
})
.is-selected {
background-color: red;
border: 1px solid red;
}
ul {
list-style-type: none;
}
li {
cursor: pointer;
}
li:hover {
border: 1px solid gray;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.3.1/vuex.js"></script>
<div id="app">
<list :items="items"></list>
</div>