How to define Vue event modifiers with a render function? - vue.js

I'm trying to create a Vue app with a render() function and withModifiers. But somehow a keyup.enter event does not work as expected: It fires for every key, not only the enter or return key.
Here is a minimum example:
<html>
<body>
<div id="app"></div>
<script src="https://cdn.jsdelivr.net/npm/vue#3/dist/vue.global.prod.js"></script>
<script>
const app = Vue.createApp({
render() {
return Vue.h("input", { onKeydown: Vue.withModifiers((e) => console.log(e), ["enter"]) });
},
}).mount("#app");
</script>
</body>
</html>
What did I miss?

Related

Composition API template not updating reactive object in non SFC mode

I have a simple Vue app that creates a reactive object with a field. Then upon button click, the field on the object is updated. However the template is not updating the updated field. Any hints on what I'm doing wrong? I'm using the esm-browser mode. When I switch it to Options API, then the reactivity works.
<!-- index.html -->
<html>
<head></head>
<body>
<div id="app"></div>
<script src="https://unpkg.com/vue#3.2.23"></script>
<script src="index.js" type="module"></script>
</body>
</html>
//index.js
import { reactive } from "https://cdn.jsdelivr.net/npm/vue#3.2.23/dist/vue.esm-browser.prod.js";
Vue.createApp(
{
template: `<div>{{ model.firstName }} <p><button #click="buttonClicked">click me</button></p></div>`,
setup() {
let model = reactive({ firstName: "test" });
let buttonClicked = () => {
model.firstName = "something new";
console.log(model); // This shows "model" as a Proxy object with the firstName updated. However {{ model.firstName }} is not updated.
};
return { model, buttonClicked, };
}
}
).mount("#app");

How can I use passed data using props in child component v-bind:style?

I'm trying to get data from parent component and use them in child component v-bind:style.
Here's my code.
<body>
<div id="parentComponent">
<child-component v-bind:propsdata="parentBackground" v-bind:propsdata2="parentFontstyle"></child-component>
</div>
<script>
Vue.component('child-component', {
props: ['propsdata', 'propsdata2'],
data: function() {
return {
childBackground: this.propsdata,
childFontStyle: this.propsdata2
},
template: '<p v-bind:style="childBackgroundColor, childFontStyle">Child componnent Area</p>'
});
new Vue ({
el: '#parentComponent',
data: function() {
return {
parentBackground: 'background-color:yellow;',
parentFontStyle: 'font-style: italic;'
},
})
</body>
When I run this code, only second style(childFontStyle) is applied to Child componnent template.
I also tried v-bind:style="[childBackgroundColor, childFontStyle]" and doesn't work.
Is there any way to apply both style?
You need to pass the props property as an object and can able to bind the props directly to the child. You have some typo mistakes that need to be fixed as well. Here is the working snippet.
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="parentComponent">
<child-component v-bind:propsdata="parentBackground" v-bind:propsdata2="parentFontStyle"></child-component>
</div>
<script>
Vue.component("child-component", {
template: `<p v-bind:style="[propsdata, propsdata2]">Child componnent Area</p>`,
props: ["propsdata", "propsdata2"]
});
new Vue({
el: "#parentComponent",
data: function() {
return {
parentBackground: {
"background-color": "yellow"
},
parentFontStyle: {
"font-style": "italic"
}
};
}
});
</script>
</body>
</html>

vuejs update component value on button click is not working

I have a header component. i cant able to update value of header component on button click
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
<body>
<header>
<my-component ref="foo"></my-component>
</header>
<div id="app">
<h1>Component Test</h1>
<button v-on:click="test">Button</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var MyComponent = Vue.extend({
template: '<div><p>Hello</p><div v-if="islogin">User</div></div>',
data: function () {
return {
islogin: false
};
}
});
var vm = new Vue({
el: '#app',
components: {
'my-component': MyComponent
},
methods: {
test: function () {
this.$refs.foo.islogin = true;
}
}
});
</script>
</body>
</html>
i want to update the islogin to true on button click. Now it shows "TypeError: Cannot set property 'islogin' of undefined" error.
Thanks
Your component 'my-component' is not in the scope of Vuejs. Vue is in the div with the id 'app'. So you can't add others components outside this main div
Put the component inside Vuejs #app scope :
<div id="app">
<header>
<my-component ref="foo"></my-component>
</header>
<h1>Component Test</h1>
<button v-on:click="test">Button</button>
</div>

Dynamic component event bus not work in vue?

I just used event bus in vuejs for the dynamic component, but when I emit an event from one component, I cannot listen to that event in another component. Below is the demo I created by codesandbox, what I want to do is output 'test in tes2' from test2component, but it outputs nothing. But when I uncomment the $bus listener in HomeCompnent, the two console.log are all executed.
main.js
Vue.propotype.$bus = new Vue();
HomeComponent
<template>
<div>
<component :is="currentComponent" #changeComponent="changeComponent"></component>
</div>
</template>
<script>
import Test1Component from "./Test1Component";
import Test2Component from "./Test2Component";
export default {
created() {
// this.$bus.$on("test", () => console.log("event bus test"));
},
components: {
Test1Component,
Test2Component
},
data() {
return {
currentComponent: "Test1Component"
};
},
methods: {
changeComponent() {
this.currentComponent = "Test2Component";
}
}
};
</script>
<style>
</style>
Test1Component
<template>
<div>
<h1>test1</h1>
<button #click="changeComponent">click me</button>
</div>
</template>
<script>
export default {
methods: {
changeComponent() {
this.$emit("changeComponent");
this.$bus.$emit("test");
}
}
};
</script>
<style>
</style>
Test2Component
<template>
<h1>test2</h1>
</template>
<script>
export default {
created() {
this.$bus.$on("test", () => console.log("test in test2"));
}
};
</script>
<style>
</style>
This problem is a good demonstration of the downsides of the event bus pattern:
You, the developer, have to carefully ensure that sender and receiver of all events actually exist at the same moment in time.
In your scenario, that's not the case:
When Test1Component emits the event, Test2Component doesn't exist yet.
After Test2Component has been created by HomeComponent just a moment later, the event is already "gone".
My usual disclaimer as a Vue core team member: Don't use the Event bus pattern.
Event Bus is just fine but problem is in implementation.
You are very correct in case of you un-comment code In home component, I tested on your snippet it emit only once event bus test [console.log]
With events you need to take care this
You need to define listener. [ make sure you define them before emitting event ]
Now you just emit event. [ to work we need listener to listen first]
In your case you wrote listener function $on in created event of theTest2Component
Now just think for now, You don't have any listeners at beginning as in home compo you just commented that listener code .[ its initial step ]
Now when you click on click me button you are changing component to new component[Test2Component], It is mounted and its created event will fire then this listener will start listening to event
but you missed this
this.$emit("changeComponent"); // this is first [ fires/emit ]
this.$bus.$emit("test"); // THEN THIS EMIT
So, When it just start changing component from compo1 tocompo2testfired/emitted directly without wait, its notsynchronousit isAsynchronousIt will not wait to finish all stuff ofchangeComponent. It will be emitted immediately [test`]
Guess what Now when test emits, at that time Dom operation to add component, ITS NOT DONE YET, and test is emitted
So, No listeners are there, so no console.log
But if you see if, you UNCOMMENT listener in home function listener is well defined before emit of test event so it output in console.
I hope you understand it, if not let me know point I will explain it in details
Another thins is that you added $bus in prototype so all components has its own bus. instead you can use GLOBAL event Bus
as in example you can see.
in es6 you can do
//bus.js
const bus = new Vue({});
export default bus;
to import it in other components
import bus from './bus.js';
// ... do bus.$on ..
// ... do bus.$emit ..
var $bus = new Vue({});
Vue.component('Test1', {
template: `
<div class="blog-post">
<h3>test1</h3>
<button #click="changeComponent">click me</button>
</div>
`,
methods: {
changeComponent() {
$bus.$emit("chng");
$bus.$emit("test");
}
}
});
Vue.component('Test2', {
template: `
<div class="blog-post">
<h3>test2</h3>
</div>
`,
created() {
$bus.$on("test", () => console.log("test in test2 will not fire as we are little late to listen it"));
}
});
new Vue({
el: '#app',
created: function(){
console.log('created');
$bus.$on("chng", () => this.changeComponent());
$bus.$on("test", () => console.log("test in test2 main/HOME"));
},
data() {
return {
currentComponent: "Test1"
};
},
methods: {
changeComponent() {
this.currentComponent = "Test2";
}
}
});
<!DOCTYPE html>
<html>
<head>
<script> console.info = function(){} </script>
<script src="https://code.jquery.com/jquery.min.js"></script>
<script src="https://vuejs.org/js/vue.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Stack Overflow - Hardik Satasiya</title>
<style>.half{width:50%;float:left;}</style>
</head>
<body>
<div id="app">
<component :is="currentComponent" #changeComponent="changeComponent"></component>
</div>
</body>
</html>
Your test is not correct.
Basically, Test2Component is not created in the time Test1Component emit this.$bus.$emit("test");
You saw 2 console.log because codesandbox use hot reload modules, so this.$bus.$on("test", () => console.log("test in test2")); is still registered if when you change your code.
If you unregister when component destroyed in Test2Component, test in test2 will never be logged
Test2Component.vue
<template>
<div>
<h1>test2</h1>
<button #click="changeComponent">click me</button>
</div>
</template>
<script>
export default {
created() {
console.log("Component 2 is created");
this.$bus.$on("test", this.testLog);
},
beforeDestroy() {
this.$bus.$off("test", this.testLog);
},
methods: {
testLog() {
console.log("test in test2");
},
changeComponent() {
this.$emit("changeComponent");
}
}
};
</script>
<style>
</style>
Demo: https://codesandbox.io/s/2485jw460y
If you comment
beforeDestroy() {
this.$bus.$off("test", this.testLog);
},
in component 2, you will see test in test2 is printed many times after few clicks
(if you change the code, please refresh to run from beginning)

How to re-mount a component in VueJS?

I have a component which is mounted as part of the DOM rendering. The skeleton of the application is
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>title</title>
</head>
<body>
<div id="app">
<my-component></my-component>
<button>press this button to reload the component</button>
</div>
</body>
</html>
<my-component> is functional (it displays a few form inputs) and $emit data to the parent.
Is there a way to re-mount it? The goal is to have a component content and setup as if it was just rendered for the first time (including a reset of the data() elements which hold its state).
There are some solutions to that but they all assume a rewrite of data(), which I would like to avoid.
My understanding is that a component is actuall HTML/CSS/JS code injected in the dom in the right place during the rendering so I fear that the concept of "re-mounting" it does not exist - I just wanted to make sure before going the data()-rewrite way.
The trick is to alter the key
When the key changes, vue regards it as a new component, so it will unmount the "old" component, and mount a "new" component.
See example, the created() hook will only run once, so if you see the value change, you're seeing a brand new object.
example:
Vue.component('my-component', {
template: `<div>{{ rand }}</div>`,
data() {
return {
rand: ''
}
},
created() {
this.rand = Math.round(Math.random() * 1000)
}
});
new Vue({
el: '#app',
data: {
componentKey:0
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.8/vue.min.js"></script>
<div id="app">
<my-component :key="componentKey"></my-component>
<button #click="componentKey=!componentKey">press this button to reload the component</button>
</div>
In your template you'll add the v-if directive:
<template>
<my-component v-if="renderComponent" />
</template>
In your script you'll add in this method that uses nextTick:
<script>
export default {
data() {
return {
renderComponent: true,
};
},
methods: {
forceRerender() {
// Remove my-component from the DOM
this.renderComponent = false;
this.$nextTick(() => {
// Add the component back in
this.renderComponent = true;
});
}
}
};
</script>
This is what's going on here:
Initially renderComponent is set to true, so my-component is rendered
When we call forceRerender we immediately set renderComponent to false
We stop rendering my-component because the v-if directive now evaluates to false
On the next tick renderComponent is set back to true
Now the v-if directive evaluates to true, so we start rendering my-component again