this.$el of Vue components always reference the same dom element - javascript

I have a component “MText”,the main code is as follows :
<template>
<vue-draggable-resizable #click="deleteFun">
</vue-draggable-resizable>
</template>
export default {
method:{
deleteFun () {
this.$el.remove();
}
}}
and in another file,I have a function like this
function createText(){
let MyComponent =Vue.extend({
template:"<MText></MText>",
components:{MText},
data () {
return {}
}})
return new MyComponent(); }
and I have a button,click event bind a function “addText”,like this
addText(){
let text = createText();
let panel = document.getElementById("palette");
let tp_dom = document.createElement("div");
tp_dom.setAttribute("id","id");
panel.appendChild(tp_dom);
text.$mount(tp_dom);
}
the quesition is that when I run “addText” twice, the dom “#palette”
have two “MText” elements,then,I click the second “MText” element,why
the first “MText” is deleted;“this.$el” always reference the first
“MText”

I have no idea what your problem is, but here's a working example:
https://jsfiddle.net/oddswe36/
let i = 0;
// Register your component globally
Vue.component('MText', {
template: `
<div #click="removeMe">click to remove me {{ counter }}</div>
`,
data() {
return {
counter: i++
}
},
methods: {
removeMe() {
this.$el.remove()
}
}
})
function createText() {
const MyComponent = Vue.extend({
template:"<MText></MText>",
})
return new MyComponent();
}
function addText() {
const text = createText();
const panel = document.getElementById("palette");
const tp_dom = document.createElement("div");
tp_dom.setAttribute("id","id");
panel.appendChild(tp_dom);
text.$mount(tp_dom);
};
addText();
addText();
addText();
addText();
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<div id="palette"></div>

Related

Adding custom HTML element inside template using functions Vue.js

Is there any way to render html element with custom properties inside template tag using javascript functions?
Example what I try to achieve:
<template>
<div class="test">
{{ testFunction(param1,param2,param3) }}
</div>
</template>
<script>
export default {
....
methods: {
testFunction(param1,param2,param3) {
return `<button #click="function()">Click</button>`;
}
}
};
</script>
Directly you will get interpolated html, like this
<button #click="function()">Click</button>
Even if you fix it using the v-html directive to output raw HTML the button will still not work.
The right way is to use Render Functions like this:
const myComponent3 = {
setup(props) {
return () => h('button',
{
onClick(event) {
alert('Click');
}
},
'Click Me!'
)
}
}
Here is the playground with samples:
const { createApp, h } = Vue;
const myComponent1 = {
template: '#my-component',
methods: {
testFunction(par1) {
return `<button #click="function()">Click</button>`;
}
}
}
const myComponent2 = {
template: '<div v-html="rawHTML()"></div>',
methods: {
myClick() {
alert('Click');
},
rawHTML(par1) {
return '<button #click="myClick()">Click</button>';
}
}
}
const myComponent3 = {
setup(props) {
return () => h('button',
{
onClick(event) {
alert('Click');
}
},
'Click Me!'
)
}
}
const App = {
components: {
myComponent1, myComponent2, myComponent3
}
}
const app = createApp(App)
app.mount('#app')
<div id="app">
My Component 1: <my-component1></my-component1><br/>
My Component 2: <my-component2></my-component2><br/>
My Component 3: <my-component3></my-component3><br/>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<script type="text/x-template" id="my-component">
<div class="test">
{{ testFunction( param1 ) }}
</div>
</script>

How to Watch changes inside a div in Vue 3

How can I Watch for any changes like classes or text change inside a div in Vue 3.
from
<p class="font-bold">My text</p>
to:
<p class="font-bold color-red">My updated text.</p>
I have tried the Vue 3 Watch method but the Watch method is not looking for changes inside a div.
watch(myDiv, (newValue, oldValue) => {
// not working for changes inside a myDiv.
})
To watch some properties you need to bind it first, like <p :class="classes">{{ text }}</p>, also, you can use mutationObserver:
const {ref, onMounted, onBeforeUnmount} = Vue
const app = Vue.createApp({
data() {
return {
text: 'My text',
classes: 'font-bold'
};
},
watch: {
text(newValue, oldValue) {
console.log(newValue)
},
classes(newValue, oldValue) {
console.log(newValue)
}
},
methods: {
addClass() {
this.classes = 'font-bold color-red'
}
},
setup() {
let observer = null
let target = ref(null)
let options = {subtree: true, childList: true, attributes: true}
const callback = (mutationList, observer) => {
for (const mutation of mutationList) {
if (mutation.type === 'childList') {
console.log('A child node has been added or removed.');
} else if (mutation.type === 'attributes') {
console.log(`The ${mutation.attributeName} attribute was modified.`);
}
}
};
onMounted(() => {
target = document.querySelector(".mydiv")
observer = new MutationObserver(callback);
observer.observe(target, options);
});
onBeforeUnmount(() => observer.disconnect())
},
})
app.mount('#demo')
.color-red {
color: red;
}
.font-bold {
font-weight: 700;
}
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<p class="mydiv" :class="classes">{{ text }}</p>
<input v-model="text" />
<button #click="addClass">class</button>
</div>
In Vue3 you can use refs in template. Docs
<p ref="myDiv" class="test">{{text}}</p>
Now you can define them in template. myDiv.value will hold DOM element
const myDiv = ref(null)
const text= computed(() => {
if(myDiv.value.classList ... rest of your logic for checking classes)
return "Text"
else return "Updated Text"
})
you can create a SFC component with your paragraph and catch every change with a hook onUpdated
onUpdated(() => {
console.log('updated');
});
example: https://stackblitz.com/edit/vue-5qtyjg

VueJS child to parent boolean value for hide component

Hi all and thank for helping :)
Context :
In my child i want pass a boolean to the parent for hidden a component in the parent if user clicked
in my child component ( name : connexionDesktop ) :
<button v-on:click="$emit(childMessage)"> Start </button>
data () {
return {
childMessage: true
}
}
in the parent i try :
<connexionDesktop v-if="fromChild == false " v-on:childToParent="childMessage"> </connexionDesktop>
data () {
fromChild:false
}
methods: {
childMessage (value) {
alert('from child' + value );
this.fromChild = value
}
}
but that's not working ... i think i am a noob :D i can't send a message from child to parent ^^""
can you help me ? thank a lot
The first argument for the $emit method should be the event name. So your code should look better like this:
// child component
<template>
<button v-on:click="handleClick"> Start </button>
</template>
<script>
export default {
data () {
return {
childMessage: true
}
},
methods: {
handleClick() {
this.$emit('update:parent', this.childMessage)
}
}
}
</script>
Then in the parent component, you listen for the event and pass in the value the child emitted to a local value of the parent like so:
<template>
<connexionDesktop v-if="fromChild == false" #update:parent="fromChild = $event">
</connexionDesktop>
</template>
<script>
export default {
data () {
return {
fromChild: false
}
},
}
</script>
As it's stated in the docs on $emit here, the first parameter is the custom event name.
You can do something like this:
<button v-on:click="$parent.$emit('childToParent', childMessage)"> Start </button>
data () {
return {
childMessage: true
}
}
and in the parent:
<connexionDesktop v-if="fromChild == false " v-on:child-to-parent="childMessage"> </connexionDesktop>
...
data () {
fromChild:false
}
methods: {
childMessage (value) {
alert('from child' + value );
this.fromChild = value
}
}
...

VueJs watching deep changes in object

I have this 3 components in VueJS. The problem i want to solve is: When i click at vehicle component, it needs to be selected (selected = true) and other vehicles unselected.
What i need to do for two-way data binding? Because i'm changing this selected property in VehiclesList.vue component and it also need to be changed in Monit.vue (which is a parent) and 'Vehicle.vue' need to watch this property for change class.
Also problem is with updating vehicles. In Monit.vue i do not update full object like this.vehicles = response.vehicles, but i do each by each one, and changing only monit property.
Maybe easier would be use a store for this. But i want to do this in components.
EDITED:Data sctructure
{
"m":[
{
"id":"v19",
"regno":"ATECH DOBLO",
"dt":"2017-10-09 13:19:01",
"lon":17.96442604,
"lat":50.66988373,
"v":0,
"th":0,
"r":0,
"g":28,
"s":"3",
"pow":1
},
{
"id":"v20",
"regno":"ATECH DUCATO_2",
"dt":"2017-10-10 01:00:03",
"lon":17.96442604,
"lat":50.6698494,
"v":0,
"th":0,
"r":0,
"g":20,
"s":"3"
},
]
}
Monit.vue
<template>
<div class="module-container">
<div class="module-container-widgets">
<vehicles-list :vehicles="vehicles"></vehicles-list>
</div>
</div>
</template>
<script>
import VehiclesList from '#/components/modules/monit/VehiclesList.vue';
export default {
name: "Monit",
data (){
return {
vehicles: null
}
},
components: {
VehiclesList
},
methods: {
getMonitData(opt){
let self = this;
if (this.getMonitDataTimer) clearTimeout(this.getMonitDataTimer);
this.axios({
url:'/monit',
})
.then(res => {
let data = res.data;
console.log(data);
if (!data.err){
self.updateVehicles(data.m);
}
self.getMonitDataTimer = setTimeout(()=>{
self.getMonitData();
}, self.getMonitDataDelay);
})
.catch(error => {
})
},
updateVehicles(data){
let self = this;
if (!this.vehicles){
this.vehicles = {};
data.forEach((v,id) => {
self.vehicles[v.id] = {
monit: v,
no: Object.keys(self.vehicles).length + 1
}
});
} else {
data.forEach((v,id) => {
if (self.vehicles[v.id]) {
self.vehicles[v.id].monit = v;
} else {
self.vehicles[v.id] = {
monit: v,
no: Object.keys(self.vehicles).length + 1
}
}
});
}
},
},
mounted: function(){
this.getMonitData();
}
};
</script>
VehiclesList.vue
<template>
<div class="vehicles-list" :class="{'vehicles-list--short': isShort}">
<ul>
<vehicle
v-for="v in vehicles"
:key="v.id"
:data="v"
#click.native="select(v)"
></vehicle>
</ul>
</div>
</template>
<script>
import Vehicle from '#/components/modules/monit/VehiclesListItem.vue';
export default {
data: function(){
return {
isShort: true
}
},
props:{
vehicles: {}
},
methods:{
select(vehicle){
let id = vehicle.monit.id;
console.log("Select vehicle: " + id);
_.forEach((v, id) => {
v.selected = false;
});
this.vehicles[id].selected = true;
}
},
components:{
Vehicle
}
}
</script>
Vehicle.vue
<template>
<li class="vehicle" :id="data.id" :class="classes">
<div class="vehicle-info">
<div class="vehicle-info--regno font-weight-bold"><span class="vehicle-info--no">{{data.no}}.</span> {{ data.monit.regno }}</div>
</div>
<div class="vehicle-stats">
<div v-if="data.monit.v !== 'undefined'" class="vehicle-stat--speed" data-name="speed"><i class="mdi mdi-speedometer"></i>{{ data.monit.v }} km/h</div>
</div>
</li>
</template>
<script>
export default {
props:{
data: Object
},
computed:{
classes (){
return {
'vehicle--selected': this.data.selected
}
}
}
}
</script>
Two-way component data binding was deprecated in VueJS 2.0 for a more event-driven model: https://v2.vuejs.org/v2/guide/components.html#One-Way-Data-Flow
This means, that changes made in the parent are still propagated to the child component (one-way). Changes you make inside the child component need to be explicitly send back to the parent via custom events: https://v2.vuejs.org/v2/guide/components.html#Custom-Events or in 2.3.0+ the sync keyword: https://v2.vuejs.org/v2/guide/components.html#sync-Modifier
EDIT Alternative (maybe better) approach:
Monit.vue:
<template>
<div class="module-container">
<div class="module-container-widgets">
<vehicles-list :vehicles="vehicles" v-on:vehicleSelected="onVehicleSelected"></vehicles-list>
</div>
</div>
</template>
<script>
import VehiclesList from '#/components/modules/monit/VehiclesList.vue';
export default {
name: "Monit",
data (){
return {
vehicles: null
}
},
components: {
VehiclesList
},
methods: {
onVehicleSelected: function (id) {
_.forEach((v, id) => {
v.selected = false;
});
this.vehicles[id].selected = true;
}
...other methods
},
mounted: function(){
this.getMonitData();
}
};
</script>
VehicleList.vue:
methods:{
select(vehicle){
this.$emit('vehicleSelected', vehicle.monit.id)
}
},
Original post:
For your example this would probably mean that you need to emit changes inside the select method and you need to use some sort of mutable object inside the VehicleList.vue:
export default {
data: function(){
return {
isShort: true,
mutableVehicles: {}
}
},
props:{
vehicles: {}
},
methods:{
select(vehicle){
let id = vehicle.monit.id;
console.log("Select vehicle: " + id);
_.forEach((v, id) => {
v.selected = false;
});
this.mutableVehicles[id].selected = true;
this.$emit('update:vehicles', this.mutableVehicles);
},
vehilcesLoaded () {
// Call this function from the parent once the data was loaded from the api.
// This ensures that we don't overwrite the child data with data from the parent when something changes.
// But still have the up-to-date data from the api
this.mutableVehilces = this.vehicles
}
},
components:{
Vehicle
}
}
Monit.vue
<template>
<div class="module-container">
<div class="module-container-widgets">
<vehicles-list :vehicles.sync="vehicles"></vehicles-list>
</div>
</div>
</template>
<script>
You still should maybe think more about responsibilities. Shouldn't the VehicleList.vue component be responsible for loading and managing the vehicles? This probably would make thinks a bit easier.
EDIT 2:
Try to $set the inner object and see if this helps:
self.$set(self.vehicles, v.id, {
monit: v,
no: Object.keys(self.vehicles).length + 1,
selected: false
});

Vue.js, toggle class on click doesn't work as expected

I'm learning Due and I'm trying something that should be easy but doesn't work and I'm sure there is something I don't understand.
I have to add a case to a modal...
I simplify the code: I have an array of products from my parent passed the a props and to chunk them in columns I use a computed variabile.
In the computed variable I also add to my object array an attribute active for every object, and I need to use that attribute to add the class.
I cannot change the value: when I click the button the product.active value is changed if I look the console but in my template no, it is false. Why
<template>
<div class="columns" v-for="products in processedProducts">
<div class="column" v-for="product in products">
<pre>{{product.active}}</pre>
<a v-on:click="activeteModal(product)">Pricy History</a>
<price-history :asin="product.asin" :active="product.active"></price-history>
</div>
</div>
</template>
<script>
import PriceHistory from '../components/PriceHistory'
export default {
props: ['results','search','maxprice','discount'],
name: 'product',
components: {
PriceHistory
},
methods: {
activeteModal: function(product){
console.log(product.active);
product.active = !product.active;
console.log(product.active);
}
},
computed: {
processedProducts() {
let products = this.results.map((obj) => {
obj.active = false;
return obj;
})
// Put Array into Chunks
let i, j, chunkedArray = [], chunk = 5;
for (i=0, j=0; i < products.length; i += chunk, j++) {
chunkedArray[j] = products.slice(i,i+chunk);
}
return chunkedArray;
}
}
}
</script>
Computed objects update lazy, set the array as a data property then it will update reactive.
Furthermore computed-objects are by default getter-only.
You better trigger a method that fills the product array when the component is mounted like this:
export default {
props: ['results','search','maxprice','discount'],
name: 'product',
components: {
PriceHistory
},
data: function () {
return {
products :[]
}
},
mounted: function(){
this.processedProducts();
},
methods: {
activeteModal: function(product){
console.log(product.active);
product.active = !product.active;
console.log(product.active);
},
processedProducts() {
let products = this.results.map((obj) => {
obj.active = false;
return obj;
})
// Put Array into Chunks
let i, j, chunk = 5;
for (i=0, j=0; i < products.length; i += chunk, j++) {
this.products[j] = products.slice(i,i+chunk);
}
}
}
}

Categories