I want to call the dialog like this:
import demo from './demo.vue';
methods: {
open() {
const dialog = this.$dialog({
content: demo
});
}
}
dialog.js
import Vue from 'vue';
import QfDialog from './qf-dialog';
import ElementQfUI from 'element-qf-ui';
Vue.use(ElementQfUI);
let DialogConstructor = Vue.extend(QfDialog);
export const dialog = (params) => {
const instance = new DialogConstructor({
propsData: {
visible: true,
...params
}
});
instance.$mount();
document.body.appendChild(instance.$el);
return instance;
}
Vue.prototype.$dialog = dialog;
I tried to generate a VNode from a vue object to use in the template, but it gives me following error:
Error in render: "TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Object'
--- property '_renderProxy' closes the circle"
<template>
<el-dialog :visible.sync="visible" v-bind="$attrs" v-on="$listeners">
{{ contentTpl }}
</el-dialog>
</template>
<script>
export default {
name: 'qf-dialog',
props: {
visible: {
type: Boolean,
default: false
}
},
data() {
return {
contentTpl: null
};
},
created() {
// this.content is a vue obj
let content = JSON.parse(JSON.stringify(this.content));
let vnode = this.$createElement('demo-cc', content);
this.contentTpl = [vnode];
}
};
</script>
How do I make {{contentTpl}} work ?
You are passing a whole Vue component into your Dialog - not a VNodes
Just use Dynamic Components
<template>
<el-dialog :visible.sync="visible" v-bind="$attrs" v-on="$listeners">
<component :is="content" />
</el-dialog>
</template>
Note that using :visible.sync is problematic as Vue does not allow to modify props the component receives
Related
I have a functional component:
export default defineComponent({
name: 'MovieOverview',
components: {
ExpandedMovieInformation,
},
setup() {
let toggleValue = false;
const toggleExpandedMovieInformation = (moviex: Movie) => {
toggleValue = !toggleValue;
console.log(toggleValue)
};
return {
toggleValue,
toggleExpandedMovieInformation,
};
},
});
<template>
<div>
<button v-on:click='toggleExpandedMovieInformation'>click</button>
{{ toggleValue }}
</div>
</template>
When I click the button the console.log does log the change, but the toggleValue in the template stays the same value: false.
Right now the toggleValue variable has no reactivity. You should use ref() or reactive() in order to make it reactive so the view re-renders every time changes are made into that property.
So you should do something like this:
import { ref } from 'vue'
export default defineComponent({
name: 'MovieOverview',
components: {
ExpandedMovieInformation,
},
setup() {
let toggleValue = ref(false);
const toggleExpandedMovieInformation = (moviex: Movie) => {
// now you'll have to access its value through the `value` property
toggleValue.value = !toggleValue.value;
console.log(toggleValue.value)
};
return {
toggleValue,
toggleExpandedMovieInformation,
};
},
});
<template>
<div>
<button v-on:click='toggleExpandedMovieInformation'>click</button>
<!-- You DON'T need to change toggleValue to toggleValue.value in the template -->
{{ toggleValue }}
</div>
</template>
Check the docs for more info about ref and reactive.
I'm building web app with Vue, Nuxt, and Element UI.
I have a problem with the Element dialog component.
It can open for the first time, but it can't open for the second time.
This is the GIF about my problem.
https://gyazo.com/dfca3db76c75dceddccade632feb808f
This is my code.
index.vue
<template>
<div>
<el-button type="text" #click="handleDialogVisible">click to open the Dialog</el-button>
<modal-first :visible=visible></modal-first>
</div>
</template>
<script>
import ModalFirst from './../components/ModalFirst.vue'
export default {
components: {
'modal-first': ModalFirst
},
data() {
return {
visible: false,
};
},
methods: {
handleDialogVisible() {
this.visible = true;
}
}
}
</script>
ModalFirst.vue
<template>
<el-dialog
title="Tips"
:visible.sync="visible"
width="30%"
>
<span>This is a message</span>
<span slot="footer" class="dialog-footer">
<a>Hello</a>
</span>
</el-dialog>
</template>
<script>
export default {
props: [ 'visible' ]
}
</script>
And I can see a warning message on google chrome console after closing the dialog.
The warning message is below.
webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:620 [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "visible"
found in
---> <ModalFirst> at components/ModalFirst.vue
<Pages/index.vue> at pages/index.vue
<Nuxt>
<Layouts/default.vue> at layouts/default.vue
<Root>
This is the screenshot of the warning message.
https://gyazo.com/83c5f7c5a8e4d6816c35b3116c80db0d
In vue , using directly to prop value is not allowed . Especially when your child component will update that prop value , in my option if prop will be use
for display only using directly is not a problem .
In your code , .sync will update syncronously update data so I recommend to create local data.
ModalFirst.vue
<el-dialog
title="Tips"
:visible.sync="localVisible"
width="30%"
>
<script>
export default {
props: [ 'visible' ],
data: function () {
return {
localVisible: this.visible // create local data using prop value
}
}
}
</script>
If you need the parent visible property to be updated, you can create your component to leverage v-model:
ModalFirst.vue
<el-dialog
title="Tips"
:visible.sync="localVisible"
width="30%"
>
<script>
export default {
props: [ 'value' ],
data() {
return {
localVisible: null
}
},
created() {
this.localVisible = this.value;
this.$watch('localVisible', (value, oldValue) => {
if(value !== oldValue) { // Optional
this.$emit('input', value); // Required
}
});
}
}
</script>
index.vue
<template>
<div>
<el-button type="text" #click="handleDialogVisible">click to open the Dialog</el-button>
<modal-first v-model="visible"></modal-first>
</div>
</template>
<script>
import ModalFirst from './../components/ModalFirst.vue'
export default {
components: {
'modal-first': ModalFirst
},
data() {
return {
visible: false,
};
},
methods: {
handleDialogVisible() {
this.visible = true;
}
}
}
</script>
v-model is basically a shorthand for :value and #input
https://v2.vuejs.org/v2/guide/forms.html#Basic-Usage
Side-note:
You can also import your component like so:
components: { ModalFirst },
as ModalFirst will be interpreted as modal-first as well by Vue.js
I have a string (example, because it's an object with many key/values, want to loop and append to htmloutput) with a component name. Is it possible to render/build the component inside a method and display the html output?
Is that possible and how can i achieve that?
<template>
<div v-html="htmloutput"></div>
</template>
<script>
export default {
component: {
ComponentTest
},
data() {
return {
htmloutput: ''
}
},
methods:{
makeHtml(){
let string = 'component-test';//ComponentTest
//render the ComponentTest directly
this.htmloutput = ===>'HERE TO RENDER/BUILD THE COMPONENTTEST'<==
}
},
created(){
this.makeHtml();
}
</script>
You might be looking for dynamic components:
https://v2.vuejs.org/v2/guide/components-dynamic-async.html
Example:
<template>
<component :is="changeableComponent">
</component>
</template>
<script>
import FirstComponent from '#/views/first';
import SecondComponent from '#/views/second';
export default {
components: {
FirstComponent, SecondComponent
},
computed: {
changeableComponent() {
// Return 'first-component' or 'second-component' here which corresponds
// to one of the 2 included components.
return 'first-component';
}
}
}
</script>
Maybe this will help - https://forum.vuejs.org/t/how-can-i-get-rendered-html-code-of-vue-component/19421
StarRating is a sample Vue component. You can get it HTML code by run:
new Vue({
...StarRating,
parent: this,
propsData: { /* pass props here*/ }
}).$mount().$el.outerHTML
in Your method. Remember about import Vue from 'vue'; and of course import component.
What you're trying to do really isn't best practice for Vue.
It's better to use v-if and v-for to conditionally render your component in the <template> section.
Yes you can use the render function for that here is an example :
Vue.component('CompnentTest', {
data() {
return {
text: 'some text inside the header'
}
},
render(createElement) {
return createElement('h1', this.text)
}
})
new Vue({
el: '#app',
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<Compnent-test />
</div>
Or :
if you are using Vue-cli :
on your componentTest component :
export default {
render(createElement) {
return createElement('h1', 'sometext')
}
// Same as
// <template>
// <h1>sometext</h1>
// </template>
//
}
and on your root element (App.vue as default) :
export default {
....
component: {
ComponentTest
}
}
<template>
<div>
....
<Component-test />
</div>
</template>
example : codesandbox
you can read more about
Render Functions & JSX
I am use component of the dialog window dialog.vue from vue-mdl package
<template>
<div class="mdl-dialog-container" v-show="show">
<div class="mdl-dialog">
<div class="mdl-dialog__title">{{title}}</div>
<div class="mdl-dialog__content">
<slot></slot>
</div>
<div class="mdl-dialog__actions" :class="actionsClasses">
<slot name="actions">
<mdl-button class="mdl-js-ripple-effect" #click.native.stop="close">Close</mdl-button>
</slot>
</div>
</div>
</div>
</template>
<script>
import mdlButton from './button.vue'
import createFocusTrap from 'focus-trap'
export default {
components: {
mdlButton
},
computed: {
actionsClasses () {
return {
'mdl-dialog__actions--full-width': this.fullWidth
}
}
},
data () {
return {
show: false
}
},
props: {
title: {
type: String
},
fullWidth: Boolean
},
mounted () {
this._focusTrap = createFocusTrap(this.$el)
},
methods: {
open () {
this.show = true
this.$nextTick(() => this._focusTrap.activate())
this.$emit('open')
},
close () {
this.show = false
this._focusTrap.deactivate()
this.$emit('close')
}
}
}
</script>
I want to bring a dialog window to the other component
<mdl-dialog></mdl-dialog>
<button class="mdl-button mdl-js-button mdl-button--raised">Click me</button>
I found no information on how to call a method of one component within the other. All examples are mainly used props. Tell me how to do it?
How can I call a method open() in <mdl-dialog></mdl-dialog>?
Since they're not parent child you'd want to use an event bus. Since you're using .vue files you can create a file called bus.js like
import Vue from 'vue'
export default new Vue()
Then, import that wherever you need to emit and listen for centralized events. Here's a quick example:
// SomeComponent.vue
import bus from './bus.js'
export default {
methods: {
log (msg) {
console.log(msg)
}
},
created () {
bus.$on('someEvent', this.log)
}
}
Then in another component you can do like...
// AnotherComponent.vue
import bus from './bus.js'
export default {
methods: {
emitClick (msg) {
bus.$emit('Hello from AnotherComponent.vue')
},
},
}
You can read up a bit more about it here: https://v2.vuejs.org/v2/guide/components.html#Non-Parent-Child-Communication
You can create below helper method in methods in your parent component:
getChild(name) {
for(let child of this.$children) if (child.$options.name==name) return child;
},
And call child component method in this way:
this.getChild('mdl-dialog').open();
I don't test it for Vue>=2.0
//store
export default {
state: {
aboutModels: []
},
actions: {
findBy: ({commit}, about)=> {
//do getModels
var aboutModels = [{name: 'About'}] //Vue.resource('/abouts').get(about)
commit('setModels', aboutModels)
}
},
getters: {
getModels(state){
return state.aboutModels
}
},
mutations: {
setModels: (state, aboutModels)=> {
state.aboutModels = aboutModels
}
}
}
//component
import {mapActions, mapGetters} from "vuex";
export default {
name: 'About',
template: require('./about.template'),
style: require('./about.style'),
created () {
document.title = 'About'
this.findBy()
},
computed: mapGetters({
abouts: 'getModels'
}),
methods: mapActions({
findBy: 'findBy'
})
}
//view
<div class="about" v-for="about in abouts">{{about.name}}</div>
//error
vue.js:2532[Vue warn]: Cannot use v-for on stateful component root element because it renders multiple elements:
<div class="about" v-for="about in abouts">{{about.name}}</div>
vue.js:2532[Vue warn]: Multiple root nodes returned from render function. Render function should return a single root node. (found in component <About>)
You are mapping your Vuex state getters and action correctly. Your problem is something else as your error message states...
In your component template you can not use v-for directive on a root element. For example this is not allowed because your component can have multiple root elements:
<template>
<div class="about" v-for="about in abouts">{{about.name}}</div>
</template>
instead do it this way:
<template>
<div>
<div class="about" v-for="about in abouts">{{about.name}}</div>
</div>
</template>
** *fixed typo in template tag **