Get Element Count from within a Slot - javascript

Consider the following Vue component:
<template>
<!-- Carousel -->
<div class="carousel-container">
<div ref="carousel" class="carousel">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
data() {
return {
items: this.$refs.carousel.querySelector('*')
}
},
computed: {
count: function () {
return this.items.length;
}
},
created () {
console.log(this.count);
}
}
</script>
The above does not work, and I think that is because I am attempting to reference refs in the data object.
I am trying to get the count of DOM elements within the .carousel element. How should I go about achieving this?
Update
After doing some further research, I have found that it is possible to achieve like so:
<script>
export default {
data() {
return {
items: []
}
},
computed: {
count: function () {
return this.items.length;
}
},
mounted () {
this.items = this.$refs.carousel.children;
console.log(this.count);
}
}
</script>
However, I am not confident that this is the best way to achieve this. I appreciate that 'best' is subjective, but is anyone aware of a 'better' way to achieve this?

I think a simpler, more straightforward way of counting the elements in tag would be more like this:
<script>
export default {
data() {
return {
count: 0
}
},
mounted () {
this.count = this.$refs.carousel.children.length;
console.log(this.count);
}
}
</script>

We can get all the elements inside the .carousel class using below
var matched = $(".carousel *");
var total elements = matched.length;
if you need only slot count, you can use below
var matched = $(".carousel slot");
var total elements = matched.length;

Related

Click Button in Parent, get data from Child to Parent and use it in a method (Vue 3)

I'm working with Vue3 and Bootstrap 5.
MY PROBLEM: I want to click a button in my parent.vue. And after clicking this I want to have the data from my child.vue inside of the method in my parent.vue - method .
But my data is always empty, except I need another ```setTimeout"-function. But actually I don't want to use it.
I think there is a better solution for the props Boolean as well..
If there are any question left regarding my problem, please ask me!
Thanks for trying helping me out!
PARENT:
<template>
<Child :triggerFunc="triggerFunc" #childData="childData"/>
<button type="button" class="btn btn-success" #click="get_data()">Get Data</button>
</template>
<script>
export default {
data() {
return {
triggerFunc: false,
c_data: [],
}
},
methods: {
childData(data) {
this.c_data = data;
},
get_data() {
this.triggerFunc = true;
setTimeout(() => {
this.triggerFunc = false;
}, 50);
console.log(this.c_data);
//HERE I WANT TO USE "C_DATA" BUT OF COURSE IT's EMPTY. WITH ANOTHER SET_TIMEOUT IT WOULD WORK
//BUT I DON'T WANT TO USE IT. BUT WITHOUT IT'S EMPTY.
//LIKE THIS IT WOULD WORK BUT I DON'T WANT IT LIKE THAT
setTimeout(() => {
console.log(this.c_data);
}, 50);
}
},
}
</script>
CHILD:
<template>
<!-- SOME BUTTONS, INPUTS, ETC. IN HERE -->
</template>
<script>
export default {
data() {
return {
input1: "",
input2: "",
}
},
props: {
triggerFunc: Boolean,
},
triggerFunc(triggerFunc) {
if(triggerFunc == true) {
this.save_data()
}
}
methods: {
save_data() {
var data = [
{
Input1: this.input1,
Input2: this.input2
},
]
this.$emit("childData", data);
},
},
}
</script>
Parent can very well hold/own the data of it's children. In that case, the children only render/display the data. Children need to send events up to the parent to update that data. (Here parent is the Key component and child is a Helper for the parent.)
So, here parent always has the master copy of the child's data in its own data variables.
Also, you are using # for binding properties, which is wrong. # is for event binding. For data binding use ':' which is a shorthand for v-bind:
You can just say :childData=c_data
PS: You seem to be getting few of the basics wrong. Vue is reactive and automatically binds the data to the variables. So, you don't have to do this much work. Please look at some basic Vue examples.
Refer: https://sky790312.medium.com/about-vue-2-parent-to-child-props-af3b5bb59829
Edited code:
PARENT:
<template>
<Child #saveClick="saveChildData" :childData="c_data" />
</template>
<script>
export default {
data() {
return {
c_data: [{Input1:"", Input2:""}]
}
},
methods: {
saveChildData(incomingData) {
//Either set the new value, or copy all elements.
this.c_data = incomingData;
}
},
}
</script>
CHILD:
<template>
<!-- SOME BUTTONS, INPUTS, ETC. IN HERE -->
<!-- Vue will sync data to input1, input2. On button click we can send data to parent. -->
<button #click.prevent="sendData" />
</template>
<script>
export default {
props:['childData'],
data() {
return {
input1: "",
input2: "",
}
},
methods: {
sendData() {
var data = [
{
Input1: this.input1,
Input2: this.input2
},
]
this.$emit("saveClick", data); //Event is "saveClick" event.
},
},
beforeMount(){
//Make a local copy
this.input1 = this.childData[0].Input1;
this.input2 = this.childData[0].Input2;
}
}
</script>

check props value in child component if available

I'm working currently with BootstrapVue.
I have a b-dropdown in my parent.vue where I can select a object of a JSON-File and convert it into an array because I need the length of this json object. This works fine!!
My problem is that I need to check in my parent.vue if something was selected - so if this.arrayLength is higher than 0 (until this point it works all well!). If this is true, it should use and show addElementsNotClickable() in my child.vue where no elements can be added (count of the inputs are equal to length of array) - otherwise it should use and show my button addElement() where multiple elements can be added manually.
But I'm not able to check in my child.vue if arrayLenght > 0... AND i don't know what to use on second button e.g #change(??) How can I solve that?
Many thanks! I've tried to be as detailed as I can!
Additional Info: I get no error codes!!
my parent.vue:
methods: {
inputedValue(input, index) {
var array = [];
const item= this.json.find((i) => i.Number === input);
for (let key in item.ID) {
array.push(item.ID[key]);
}
if(array.length > 0) {
this.getIndex = index;
this.getDataArray = array;
this.getLengthArray = array.length;
}
}
}
my child.vue (template)
<div class="mt-4 mb-5 ml-3 mr-3">
<b-button v-if="!hide" #click="addElement" variant="block">Add Element</b-button>
<b-button v-if="hide" #???="addElementNotClickable" variant="block">Not clickable ! </b-button>
</div>
my child.vue (script)
methods: {
addElementsNotClickable() {
for(let i = 1; i < this.arrayLength; i++) {
this.inputs.push({})
}
},
addElement() {
this.inputs.push({})
},
}
data() {
return {
inputs: [{}]
arrayLength: this.getLengthArray,
arrayIndex: this.getIndex,
hide: false,
}
props: [
"getLengthArray",
"getIndex"
],
You are misunderstanding how components should work in Vue. In short you can understand them by:
parent send props down and child send events up
What you are looking for is that whenever your arrayLength updates, you send an event to the parent. Then, it is the responsibility of the parent to handle that event. In this case, the parent would receive the event and store the length of the array.
Parent.vue
<template>
<div>
<child #arrayLenght:update="onArrayLenghtUpdate"/>
</div>
</template>
<script>
export default {
data: () => {
arrayLength: 0,
},
methods: {
onArrayLenghtUpdate(length) {
this.arrayLength = length;
}
}
}
</script>
Child.vue
<template> ... </template>
<script>
export default {
data: () => ({
arrayLength: 0,
}),
watch: {
arrayLenghth: function (newLenght) {
this.$emit('arrayLenght:update', newLenght);
}
}
}
</script>
This is the standard way and extremely useful if your Parent and Child aren't highly coupled together. If they are dependent on each other (you won't use Child.vue anywhere else in the app. Just as direct child of Parent.vue) then you can use the inject/provide approach. This approach is beyond the scope of this answer, feel free to read an article on it

Vue.js : Passing Props Down to Child Components to Update Styles in the DOM

I'm a beginner trying to get my app to pass props that set CSS styles down a chain to child components. I have a listener that checks for view port size, and as the window gets resized, it checks past a certain point and then swaps the css class and passes it down the chain..
I think I may be doing something incorrectly because my child components don't seem to be receiving the new styles and aren't updating in the DOM as I drag the window.
Here is my code.. I removed irrelevant code to make it easier to read:
Page_Listings.vue
<template>
<main>
<section>
<ListingRack
:prp_classes="rackClass"
/>
</section>
</main>
</template>
<script>
import ListingRack from './Listing__Rack.vue';
export default {
name: 'Front_Page__Panel',
data() {
return {
viewportWidth: window.innerWidth
}
},
methods: {},
mounted() { window.onresize = () => this.viewportWidth = window.innerWidth },
components: {ListingRack},
},
computed: {
rackClass: function(){
let theValue;
console.log('>> viewport width is now: ',this.viewportWidth)
if(this.viewportWidth > 1200) {
theValue = "grid_view";
console.log('>> grid view')
}
else {
theValue = 'card_view';
console.log('>> card view')
}
return theValue
}
}
}
</script>
Listing__Rack.vue
<template>
<div class="listing_rack" :class="classes">
<ul>
<li v-for="item in listings" :key="item.postId">
// I removed irrelevant code for hte sake of simplicity in this example.
// listings is a GraphQL returned array of data that generates a list of "listings".
<Listing
:prp_classes=classes
/>
</li>
</ul>
</div>
</template>
<script>
import Listing from './Listing.vue'
export default {
name: 'listing__rack',
data() {
return {
posts: [], // what we get from the database.
listings: [], // what we copy from the database.
classes: this.prp_classes
}
},
props: {
prp_classes: String
},
components: {
Listing
},
watch: {
classes: function(){
//just to check if we're receiving anything...
console.log(">> [Listing_Rack](watch)(classes) there was a change to classes!");
}
}
}
</script>
Listing.vue
<template>
<div :id=id
:class=classes
class="listing"
:style="backgroundStyle"
>
</div>
</template>
<script>
export default {
name: 'Listing',
data() {
return {
classes: this.prp_classes,
backgroundStyle: String
}
},
props: {
prp_classes: String
},
methods: {
checkClasses: function(){
if(this.classes === 'grid_view') this.backgroundStyle = 'background: center / cover no-repeat url(background.jpg);';
}
},
mounted: function() {
this.checkClasses();
},
watch: {
classes: function(){
this.checkClasses();
}
}
}
</script>
My console.logs on rackClass so I know the class swapping part is working, but all my subsequent child components don't seem to be updating accordingly..
Can someone tell me what I'm doing wrong? Is there a better way to do this? How come my props aren't being passed when I drag the window, and how can I dynamically set styles in the DOM?
Your code does not work because of the one big mistake (don't worry, many people do it)
You are passing your classes using props to child components. But instead of using this prop (prp_classes) directly in the child's template, you create an absolutely unnecessary classes property in the data()
Problem with that is that data() is executed only once when the component is created. If the value of the prp_classes prop changes later, classes property from the data() just holds the old value.
To fix this, remove unnecessary classes from the data and use the prop directly in the template...
...bit more explanation by example what is going on:
let prp_classes = 'card_view'
let classes = prp_classes
prp_classes = 'grid_view'
// prp_classes === 'grid_view', classes === 'card_view', prp_classes !== classes
// strings/numbers/Date ...all work the same
let o1 = { a: 1 }
let o2 = o1
o1.a = 2
// o1.a === 2, o2.a === 2, o1 === o2
More to study

Prevent click on the html element according to the variable value

I have this clickable div in a component which fires a todo function when the div is clicked:
<div #click="todo()"></div>
Also, I have a global variable in the component which is called price.
I need to make the div above clickable, or the todo function firable only if the price value is greater than 100.
How can I achieve this behavior in Vue?
I guess you can simply write a wrapper function like so:
<template>
<div #click="onClick"></div>
</template>
<script>
export default {
name: 'MyComponent',
data() {
return {
price: 0
}
},
computed: {
isTodoCallAllowed() {
return this.price > 100;
}
},
methods: {
onClick() {
if(this.isTodoCallAllowed) {
this.todo();
}
},
todo() {
//
}
}
}
</script>
In general, making divs clickable is usually bad practice, since it can make your interface really unclear and confusing to users.
With this in mind, I think it's easier if you replace your div with a button element, since buttons allow you to use the disabled property to prevent clicks:
new Vue({
el: '#app',
data() {
return {
price: 100
}
},
computed: {
priceIsOver() {
return this.price > 100;
}
},
methods: {
todo() {
console.log('doing stuff');
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
<div>
Price: {{price}} <button #click="price++">+</button> <button #click="price--">-</button>
</div>
<br>
<button :disabled="!priceIsOver" #click="todo">TODO</button>
</div>

Is there a method to make a prop consisted of two linked string in Vue.js?

I meet a problem when creating vue template.Something like below:
<template>
<div :prop="Astr + 'prop'"></div> // here is the problem
</template>
<script>
export default {
data() {
return {
AProp: 10,
AStr: "A"
}
}
}
</script>
I want to transform the prop of div to AProp, a value from data.But I don't know how to resolve it.
Thanks a lot.
It will produce 10
<div :prop="getValue()"></div>
methods: {
getValue() {
return this[`${this.AStr}Prop`]
}
}
Add a method to return the value that you want to use for prop:
<template>
<div :prop="getProp(AStr)"></div>
</template>
<script>
export default {
data() {
return {
AProp: 10,
AStr: "A"
};
},
methods: {
getProp(str) {
return str + "prop";
}
}
};
</script>
the way you are doing is correct, you just have a typo for AStr. Alternatively, you can have a method to return the computed value.
There is error in your code. Div prop Astr is not the same as data property AStr.
But if you want to watch AStr changes better to use computed property.
<div :prop="getProp"></div>
computed: {
getProp() {
return `${this.Astr}prop`;
}
}

Categories

Resources