I'm trying to append multiple vuejs components with jquery ajax, but it's not working.
It all works fine, until response returns more than one component, or component within component.
Here's the code:
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.14"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<div id="app">
</div>
<script type="text/x-template" id="template-1">
<div>
<h1>Template 1 {{param}}</h1>
</div>
</script>
<script type="text/x-template" id="template-2">
<div>
<h1>Template 2 {{param}}</h1>
</div>
</script>
<script>
Vue.component('template-1', {
template: '#template-1',
props: ['param']
});
Vue.component('template-2', {
template: '#template-2',
props: ['param']
});
const vm = new Vue({
el: '#app'
});
</script>
<script>
function loadMore(){
$.get('/home/test', function (response) {
var MyComponent = Vue.extend({
template: response
});
var compiled = new MyComponent().$mount();
$('#app').append(compiled.$el);
});
}
loadMore();
</script>
If the following response comes from ajax, it works, and renders one component:
<template-1 param="Test"></template-1>
If the following response returns from ajax, it renders only first component:
<template-1 param="Test"></template-1>
<template-2 param="Test"></template-2>
If the following response returns from ajax, it renders both components:
<div>
<template-1 param="Test"></template-1>
<template-2 param="Test"></template-2>
</div>
If the following response returns from ajax, it renders only parent component:
<template-1 param="Test">
<template-2 param="Test"></template-2>
</template-1>
Is there a way to make this work always, without knowing how many components will be returned from the server?
Since you are telling Vue that your response.data should be the value of the el property, it will look inside it and render all components that were registered and therefore known.
In your case you should always wrap your response inside another HTML element, which then should be the value of the el property.
Notice: Vue elements inside other Vue elements can only be rendered, if the parent component (here your template-1) has registered the second one itself. Means when template-1 got rendered, everything inside it will be removed, except it is using slots.
If you want to use slots, this could help you.
Otherwise you could use the render function and build your own dynamic render stuff.
I'm trying to setup a feature intro tutorial for my web app (like intro.js). I'm having trouble with intro.js with nothing happening (no error message or tour messages). I tried setting up the data attributes that intro.js uses and calling the tour start from the mounted function on App.vue, but no luck. I'm looking to see if anyone has experience with with libraries like this combined with VueJS.
Code from App.vue:
mounted: function() {
const introJS = require('intro.js')
introJS.introJs().start()
}
Inside of the same component in it's <template>:
<div class="card card-accent-info" v-if="!isLoading" data-intro="Test step here" data-step="1">
I also have the css loaded in App.vue:
#import "~intro.js/minified/introjs.min.css";
The problem might be the way you're importing the CSS from the <style> tag. To get the styles to apply properly, import the CSS in JavaScript:
<!-- MyComponent.vue -->
<script>
import "intro.js/minified/introjs.min.css";
export default {
mounted() {
const introJS = require("intro.js");
introJS.introJs().start();
}
};
</script>
demo
I use a an WYSIWYG article editor that generates some HTML for articles that I save in the database and later show to the user.
The problem is I need to insert Vue components into this auto generated HTML for showing dynamic products. I can make a custom block in the editor that adds in HTML but I want it to work as a Vue component that updates the product description directly from the database.
What Im thinking now is to add a button that adds a div with a data property of the products ID. I can then replace that div in the code with a Vue component with the same ID by injecting a component.
Another idea I had was to simply add in components like <product id="1031"/> as plain html and then try to compile the whole article HTML with Vue but I read that the v-html directive only compile code as plain HTML.
Is this possible? Or is there any better ideas?
If you are using the full build of Vue (not the runtime only build) you can initialize a new instance of Vue and mount it wherever you like, pass in data etc.
// Main app
new Vue({
el: '#app',
data: {
stuff: 'inserted message'
},
methods: {
clicked() {
// Add new
new Vue({
template: `<h1 style="color: red;">{{ message }}</h1>`,
parent: this,
data: {
message: 'new message'
}
}).$mount(document.getElementById('more'))
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.js"></script>
<div id="app">
There are some things here before like {{ stuff }}
but when you press <button #click="clicked()">add more</button>
you can add more things here:
<div id="more"></div>
</div>
I am using vue.js for a small web app. The whole content is wrapped in a div#app. How can I trigger a function outside of #app?
var app = new Vue({
el: '#app',
data: {
message: 'I am a working vue app'
},
methods: {
showAlert: function() {
alert("Hello world");
}
}
})
<html>
<head>
<script src="https://unpkg.com/vue"></script>
</head>
<body>
<div id="app">
{{ message }}
<button v-on:click="showAlert">Alert</button>
</div>
<button v-on:click="showAlert">Alert not working</button>
</body>
</html>
In your case, the alert is not going to work because Vue is only going to do anything with the code inside it's template. You've identified the template as #app, so the only HTML that Vue is concerned with is
<div id="app">
{{ message }}
<button v-on:click="showAlert">Alert</button>
</div>
That being the case, you've defined a Vue specific means of attaching an event handler to code elsewhere in your HTML.
<button v-on:click="showAlert">Alert not working</button>
The reason it doesn't work is because Vue doesn't even know that code exists. It is not inside #app.
If you wanted to trigger the alert from outside, you can use any plain javascript method for attaching an event handler to your button and call the method. For example,
<button onclick="app.showAlert()">Alert not working</button>
Example.
Note: You probably shouldn't set both the id and the variable that captures the result of new Vue() to the same name, app. In most browsers, the id of HTML elements are exposed in the global scope and you now have two variables named app. In this case, the Vue wins, but you can easily change it to something like
var myApp = new Vue({...})
to spare confusion. If you did that, the line above would be
<button onclick="myApp.showAlert()">Alert not working</button>
I'm trying to use the on click directive inside a component but it does not seem to work. When I click the component nothings happens when I should get a 'test clicked' in the console. I don't see any errors in the console, so I don't know what am I doing wrong.
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>vuetest</title>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
App.vue
<template>
<div id="app">
<test v-on:click="testFunction"></test>
</div>
</template>
<script>
import Test from './components/Test'
export default {
name: 'app',
methods: {
testFunction: function (event) {
console.log('test clicked')
}
},
components: {
Test
}
}
</script>
Test.vue (the component)
<template>
<div>
click here
</div>
</template>
<script>
export default {
name: 'test',
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
}
}
</script>
If you want to listen to a native event on the root element of a component, you have to use the .native modifier for v-on, like following:
<template>
<div id="app">
<test v-on:click.native="testFunction"></test>
</div>
</template>
or in shorthand, as suggested in comment, you can as well do:
<template>
<div id="app">
<test #click.native="testFunction"></test>
</div>
</template>
Reference to read more about native event
I think the $emit function works better for what I think you're asking for. It keeps your component separated from the Vue instance so that it is reusable in many contexts.
// Child component
<template>
<div id="app">
<test #click="$emit('test-click')"></test>
</div>
</template>
Use it in HTML
// Parent component
<test #test-click="testFunction">
It's the #Neps' answer but with details.
Note: #Saurabh's answer is more suitable if you don't want to modify your component or don't have access to it.
Why can't #click just work?
Components are complicated. One component can be a small fancy button wrapper, and another one can be an entire table with bunch of logic inside. Vue doesn't know what exactly you expect when bind v-model or use v-on so all of that should be processed by component's creator.
How to handle click event
According to Vue docs, $emit passes events to parent. Example from docs:
Main file
<blog-post
#enlarge-text="onEnlargeText"
/>
Component
<button #click="$emit('enlarge-text')">
Enlarge text
</button>
(# is the v-on shorthand)
Component handles native click event and emits parent's #enlarge-text="..."
enlarge-text can be replaced with click to make it look like we're handling a native click event:
<blog-post
#click="onEnlargeText"
></blog-post>
<button #click="$emit('click')">
Enlarge text
</button>
But that's not all. $emit allows to pass a specific value with an event. In the case of native click, the value is MouseEvent (JS event that has nothing to do with Vue).
Vue stores that event in a $event variable. So, it'd the best to emit $event with an event to create the impression of native event usage:
<button v-on:click="$emit('click', $event)">
Enlarge text
</button>
As mentioned by Chris Fritz (Vue.js Core Team Emeriti) in VueCONF US 2019
If we had Kia enter .native and then the root element of the base input changed from an input to a label suddenly this component is broken and it's not obvious and in fact, you might not even catch it right away unless you have a really good test. Instead by avoiding the use of the .native modifier which I currently consider an anti-pattern, and will be removed in Vue 3, you'll be able to explicitly define that the parent might care about which element listeners are added to...
With Vue 2
Using $listeners:
So, if you are using Vue 2, a better option to resolve this issue would be to use a fully transparent wrapper logic. For this, Vue provides a $listeners property containing an object of listeners being used on the component. For example:
{
focus: function (event) { /* ... */ }
input: function (value) { /* ... */ },
}
and then we just need to add v-on="$listeners" to the test component like:
Test.vue (child component)
<template>
<div v-on="$listeners">
click here
</div>
</template>
Now the <test> component is a fully transparent wrapper, meaning it can be used exactly like a normal <div> element: all the listeners will work, without the .native modifier.
Demo:
Vue.component('test', {
template: `
<div class="child" v-on="$listeners">
Click here
</div>`
})
new Vue({
el: "#myApp",
data: {},
methods: {
testFunction: function(event) {
console.log('test clicked')
}
}
})
div.child{border:5px dotted orange; padding:20px;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="myApp">
<test #click="testFunction"></test>
</div>
Using $emit method:
We can also use the $emit method for this purpose, which helps us to listen to a child component's events in the parent component. For this, we first need to emit a custom event from a child component, like:
Test.vue (child component)
<test #click="$emit('my-event')"></test>
Important: Always use kebab-case for event names. For more information and a demo regading this point please check out this answer: VueJS passing computed value from component to parent.
Now, we just need to listen to this emitted custom event in the parent component, like:
App.vue
<test #my-event="testFunction"></test>
So basically, instead of v-on:click or the shorthand #click we will simply use v-on:my-event or just #my-event.
Demo:
Vue.component('test', {
template: `
<div class="child" #click="$emit('my-event')">
Click here
</div>`
})
new Vue({
el: "#myApp",
data: {},
methods: {
testFunction: function(event) {
console.log('test clicked')
}
}
})
div.child{border:5px dotted orange; padding:20px;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="myApp">
<test #my-event="testFunction"></test>
</div>
With Vue 3
Using v-bind="$attrs":
Vue 3 is going to make our life much easier in many ways. One example is that it will help us create a simpler transparent wrapper with less config, by just using v-bind="$attrs". By using this on child components, not only will our listener work directly from the parent, but also any other attributes will also work just like they would with a normal <div>.
So, with respect to this question, we will not need to update anything in Vue 3 and your code will still work fine, as <div> is the root element here and it will automatically listen to all child events.
Demo #1:
const { createApp } = Vue;
const Test = {
template: `
<div class="child">
Click here
</div>`
};
const App = {
components: { Test },
setup() {
const testFunction = event => {
console.log("test clicked");
};
return { testFunction };
}
};
createApp(App).mount("#myApp");
div.child{border:5px dotted orange; padding:20px;}
<script src="//unpkg.com/vue#next"></script>
<div id="myApp">
<test v-on:click="testFunction"></test>
</div>
But, for complex components with nested elements where we need to apply attributes and events to the <input /> instead of the parent label we can simply use v-bind="$attrs"
Demo #2:
const { createApp } = Vue;
const BaseInput = {
props: ['label', 'value'],
template: `
<label>
{{ label }}
<input v-bind="$attrs">
</label>`
};
const App = {
components: { BaseInput },
setup() {
const search = event => {
console.clear();
console.log("Searching...", event.target.value);
};
return { search };
}
};
createApp(App).mount("#myApp");
input{padding:8px;}
<script src="//unpkg.com/vue#next"></script>
<div id="myApp">
<base-input
label="Search: "
placeholder="Search"
#keyup="search">
</base-input><br/>
</div>
A bit verbose but this is how I do it:
#click="$emit('click', $event)"
UPDATE: Example added by #sparkyspider
<div-container #click="doSomething"></div-container>
In div-container component...
<template>
<div #click="$emit('click', $event);">The inner div</div>
</template>
Native events of components aren't directly accessible from parent elements. Instead you should try v-on:click.native="testFunction", or you can emit an event from Test component as well. Like v-on:click="$emit('click')".
One use case of using #click.native is when you create a custom component and you want to listen to click event on the custom component. For example:
#CustomComponent.vue
<div>
<span>This is a custom component</span>
</div>
#App.vue
<custom-component #click.native="onClick"></custom-component>
#click.native always work for this situation.
App.vue
<div id="app">
<test #itemClicked="testFunction($event)"/>
</div>
Test.vue
<div #click="$emit('itemClicked', data)">
click here
</div>
From the documentation:
Due to limitations in JavaScript, Vue cannot detect the following changes to an array:
When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue
When you modify the length of the array, e.g. vm.items.length = newLength
In my case i stumbled on this problem when migrating from Angular to VUE. Fix was quite easy, but really difficult to find:
setValue(index) {
Vue.set(this.arr, index, !this.arr[index]);
this.$forceUpdate(); // Needed to force view rerendering
}