Vue2 custom event with native dom element - javascript

I have just started learning vue2. While playing with components and custom-event, i am facing a problem. I am expecting an alert with text caught but nothing happens. Below is the simple code:
<div class="container" id="app" >
<div v-on:newmessage="handleNewMessage">
<message></message>
</div>
</div>
<script>
Vue.component('message',{
template: '<input type="text" #keyup.enter="handleInput">',
methods: {
handleInput: function(event)
{
this.$emit('newmessage');
},
}
});
var demo = new Vue({
el: '#app',
methods: {
handleNewMessage: function(message)
{
alert('caught');
}
},
});
</script>
Please note that if i move the newmessage event listener from the div element to the message element, this works fine and the alert is generated:
//this works
<div >
<message v-on:newmessage="handleNewMessage"></message>
</div>
I might as well be missing something very basic here. Do custom events not propagate up to native dom parent elements? I spent whole evening searching for a reference but no luck.
Thanks in advance.

Yes, it works as you explained since $emit is only visible at <message> tag (as I know it) and NOT at the <div> enclosing it. You can try directly emitting to the root component (#app) and wire the handler there too.
this.$root.$emit('newmessage')
and,
<div class="container" id="app" v-on:newmessage="handleNewMessage">
(EDIT)and,
const component = Vue.component('message' ...)
new Vue({
el: '#app',
components: component
...)
Basically, you are not doing anything wrong here.
PS: I haven't run EDIT. But, the idea is along those lines. Let me know if that works.

Unlike components and props, event names will never be used as variable or property names in JavaScript, so there’s no reason to use camelCase or PascalCase. Additionally, v-on event listeners inside DOM templates will be automatically transformed to lowercase (due to HTML’s case-insensitivity), so v-on:myEvent would become v-on:myevent – making myEvent impossible to listen to.
For these reasons, we recommend you always use kebab-case for event names.

Related

LitElement appending custom elements using button

I have a custom LitElement and inside it, I need to have a button that will dynamically append new custom elements (preferably using TemplateResult objects generated by html function) inside a container:
import { LitElement, html, render } from "lit";
import { customElement } from "lit/decorators.js";
#customElement('example-custom-component')
class CustomComponent extends LitElement {
render() {
return html`
<div id="custom-el-container">
</div>
<button #click=${this.appendNewCustomEl}>click me!</button>
`;
}
appendNewCustomEl() {
const templateToAppend = html`
<another-custom-component>
some other things added here
</another-custom-component>
`;
render(templateToAppend, this.shadowRoot?.querySelector('#custom-el-container'));
}
}
As you can see above I tried to achieve it by using the render function, but instead of appending it at the end of the container, I'm simply overwriting the content of the div. What am I doing wrong? What's the best approach to achieve those results? Please help.
EDIT:
New example for my question from comments about click events:
appendNewCustomEl() {
this.shadowRoot!.querySelector('#custom-el-container').insertAdjacentHTML("beforeend", `
<another-custom-component>
<button #click=${this.functionFromCustomComponent}>click me!</button>
</another-custom-component>
`)
}
If you really want to do it with lit-html and your container's content is purely what you are dynamically rendering on each button click, (i.e. not server side rendered content) or you are using lit-html v2 then you could have a list and track what you have rendered. something like:
items=[];
appendNewCustomEl() {
this.items.push(null);
const templatesToAppend = this.items.map(() => html`
<another-custom-component>
some other things added here
</another-custom-component>
`);
render(templatesToAppend, this.shadowRoot?.querySelector('#custom-el-container'));
}
in general what lit-html is good at and tries to achieve is an optimal re-render of markup when only parts are changed. not necessary a template engine alternative to handlebars, mustache and co.
In your example, you don't need it and could do it simply without lit-html:
appendNewCustomEl() {
this.shadowRoot!.querySelector('#custom-el-container').insertAdjacentHTML("beforeend", `
<another-custom-component>
some other things added here
</another-custom-component>
`)
}
I've slightly modified your example code to be more idiomatic Lit. Instead of using a querySelector and insertAdjacentHTML, it is preferred to render the array of templates declaratively. This has the added benefit that your functionFromCustomComponent event bindings "just work".
I've written the full example in a Lit playground.
The list of templates are stored in a reactive property, this.templates that schedules an efficient update whenever the list is updated.
Then the this.appendNewCustomEl method can push new templates to the list, and an automatic efficient render happens automatically.
this.templates is rendered into the #custom-el-container div using an expression:
<div id="custom-el-container">
${this.templates}
</div>
See Lit expressions documentation for other examples.
In my linked example, the list of templates turn blue when clicked to demonstrate the event working.
Not necessary but related, Lit.dev has a tutorial all about Working with lists that goes much more in depth if you're curious.

Instantiate Svelte Component in HTML5 Custom Tag (without Shadow DOM)

I'm building a Svelte component library to be consumed using JavaScript only. At a later stage also by other Svelte applications as an additional option.
I want to avoid Svelte's custom element feature, since there are limitations with true Web Components and its Shadow DOM.
Currently the component is instantiated like this:
<div id="my-component"></div>
<script>
document.addEventListener("DOMContentLoaded", function (event) {
new MySvelteComponent({
target: document.getElementById("my-component"),
props: {
firstProp: true,
secondProp: "some value"
}
});
});
</script>
Now I would like to provide a more elegant way by defining a HTLM5 custom tag like:
<my-component firstProp="true" secondProp="some value">
What is a good way to implement this?
found it: github/svelte-tag npm/svelte-tag
see regarding issue on github near to the bottom is the answer from chrisward.
It makes a custom element but without shadow DOM. Found it today and workes for me.

Adding HTML element to a vue template

I'm using a chart component (Chartist) that requires a HTML element to use as a parent when rendering the SVG.
The elements that the chart can use is generated during a v-for loop, which means that they are not added to the DOM at the time of the chart rendering.
The code looks something like this (in the vue):
<div v-for="chart in charts">
<h1>{{chart.Title}}</h1>
<div class="ct-chart" :id="'chart-' + chart.name"></div>
{{generateChart('#chart-' + chart.name, chart.Data}}
<div>
<span v-for="l in legend" :class="chart.ClassName">{{l.DisplayName}}</span>
</div>
In the component:
generateChart(chartName: string, data: IChartData) {
/* More code here */
//doesn't get added (can't find html node in dom)
new Chartist.Line(chartName, data, options);
// this worked
//var element = document.createElement("DIV");
//element.className = "ct-chart";
//document.body.appendChild(element);
//new Chartist.Line(element, data, options);
}
Which results in that Chartist fails on querySelectorAll.
If I on the other hand generate an element using document.createElement and attach it to the html body the generation works fine.
Summary:
I want to render a chart which requires a DOM element for the rendering. But since Vue renders everything in it's virtual DOM, there is no element available when Vue renders the view (including the chart).
Question:
How can I tell vue to add a pre-created HTML element? Or how can I defer the chartist generation until vue have added everything to the real DOM?
Workaround:
I'm using a timeout (setTimeout) to defer the chart rendering so that Vue can complete before I invoke the chart rendering function.
First and foremost, don't use function in a template like this. Template is converted into render function and that function is executed whenever something changes - in this case that means you will be creating new chartist objects every time the template is rendered ....which is something you don't want.
What you want is to render DOM nodes (without any charts) first and use mounted hook to initialize each chart after the DOM is ready:
mounted: function () {
this.$nextTick(function () {
// Code that will run only after the
// entire view has been rendered
})
}
If you need DOM elements references, you can use refs
Also be aware that most libs which create DOM element dynamically (like Chartist) almost always need some cleanup code to avoid memory leaks
Or just use some Vue wrapper, for example vue-chartist
As far as I understand the question you don't want to load the div having the class of
".ct-chart" untill the DOM has completely loaded.
What you can do is that add a v-if to your vue component and when the DOM is fully loaded you can call your generateChart() function.
new Vue({
el: '#app',
data: {
domloaded: false
},
created() {
window.addEventListener('load', function () {
domloaded = true;
});
}
}
You can then add a v-if like this or maybe use lifecycle hooks like mounted() or created()
<div v-for="chart in charts">
<h1>{{chart.Title}}</h1>
<div v-if="domloaded" class="ct-chart" :id="'chart-' + chart.name"></div>
{{generateChart('#chart-' + chart.name, chart.Data)}}
<div>
<span v-for="l in legend" :class="chart.ClassName">{{l.DisplayName}}</span>
</div>

Identify multiple containers for vuejs components

I'm working on a website where I have classic multiple pages powered by blade templates, but I want to use vuejs2 components for the most dynamic needs.
It's working fine, but I couldn't find a convenient way to declare containers parsed by VueJs for components.
I want to use many components in many places, but I can't declare a big main container as VueJs container because of conflicts with <script> tags.
If I do that I get errors like : - Templates should only be responsible for mapping the state to the UI. Avoid placing tags with side-effects in your templates, such as <script>, as they will not be parsed.
I currently do something like that in my app.js file :
new Vue({
el: '#need-components-1'
});
new Vue({
el: '#need-components-2'
});
new Vue({
el: '#need-components-3'
});
I really don't like it, I'd like to be able to declare either the whole content as VueJs-able, or at least use a common class or attribute for containers. Here I need to add a new id each time I want to use a new place for components. Moreover, VueJs posts console errors for every time an element is not found because obviously they are not all always loaded.
Is there any elegant / convenient way to do that ?
Thanks
Use some identifier that an element is a root Vue and then, when the page loads, iterate over the elements you find on the page and create Vue's for them.
Here is an example.
console.clear()
const vues = document.querySelectorAll("[data-vue]")
for (let v of vues)
new Vue({el: v, data: {message: "Hello from Vue"}})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div data-vue="true">
<h2>{{message}}</h2>
</div>
<h4>Some other page content</h4>
<div data-vue="true">
<h2>{{message}}</h2>
</div>
<h4>Some other page content</h4>
<div data-vue="true">
<h2>{{message}}</h2>
</div>
Obviously you'll need to figure out a way to marry the appropriate data with the correct Vue if it should be different from Vue to Vue.

Adding Events to Dynamically rendered HTML in Vue.js Component

I'm trying to dynamically render some HTML in a Vue.js component. I'm successfully rendering the HTML. However, I don't know how to wire-up the events for the dynamically rendered elements. I've created a small example. This example probably looks like I've over complicated things. However, it's just a small part of the real example. The example can be seen in this JSFiddle, and the code looks like this:
new Vue({
el: '#app',
data: {
items: [
{
name:'Item 1',
isHtml:true,
mold: function() {
return '<button #click="onButtonOneClick">click</button>';
}
},
{
name: 'Item 2',
isHtml: false
},
{
name:'Item 3',
isHtml: true,
mold: function() {
return '<button #click="onButtonThreeClick">click</button>';
}
}
]
},
methods: {
getHtml: function(i) {
return i.mold();
},
onButtonOneClick: function() {
alert('First Item Clicked');
},
onButtonThreeClick: function() {
alert('Third Item Clicked')
}
}
})
If you run this fiddle, you'll notice that my two buttons look fine on the screen. However, the related click events don't get fired when you actually click the buttons. From what I can see, it looks like the HTML doesn't get fully compiled. I may be wrong. But, it's what it looks like based on what i see in the Chrome Dev Tools.
How do I wire up events for dynamically generated HTML in a Vue.js component?
You're trying to render 'functional html' which is basically the essence of what Vue does for you. Just render your list as you do, and add in your data in an attribute like 'is_button', and output the html for the button in the v-for and add its events. e.g.
<div v-for="(item, index) in items">
<button v-if="item.is_button" #click="onButtonClick(index)">
<div>item.content</div>
</div>
Good luck
Your example goes against everything that Vue is trying to accomplish. I suggest reading up on Vue in their docs or following some tutorials. I am not saying it can't be done because you could certainly bind events later but it is not wise.
Certainly something like this is not going to work:
mold: function() {
return '<button #click="onButtonThreeClick">click</button>';
}
That is because Vue has already rendered the markup when you inject this.
It is difficult to tell what you are trying to achieve exactly but perhaps something like this can help you out:
https://jsfiddle.net/ozf8kq1z/2/
(Open your console)
I'm sorry to be a nuisance, but why have you got vue markup in your data :? This is never going to work. Can you explain what led you down this path? Why can't your markup stay in a template :?
Vue does have a v-html directive for popping little bits of markup out of javascript into templates, but Vue tags in this markup are not processed, and it's one of those features you should use with a bad conscience.
Event listeners outside of Vue, attached after Vue has rendered, do function, but then you've really got to look at yourself in the mirror and ask "oh what have I done"?

Categories