problematically generated tree of components in vue2 - javascript

I have a scenario which i need to build a nested menu and the menu can have an infinite level of nested layers (even thought this will not happen) and I'm wanting to know what is the best way of building the list of child components dynamically?
This is some test code that I put together to try some dynamic code from a function that would essentially give me the list of components in an array etc. What is the best way of problematically building up the child components tree?
<template>
<a-row>
<div v-html="getContent()"></div>
</a-row>
</template>
<script>
export default {
methods: {
getContent() {
return `<div #click="sayHello()">RYAN</div>`
},
sayHello() {
console.log('Hello there');
}
}
}
</script>

You can try with :is and pass component:
new Vue({
el: '#demo',
data() {
return {
component: '',
contents: ['RYAN', 'DJURO', 'SPIRO']
}
},
methods: {
getContent() {
this.component = 'comp'
},
}
})
Vue.component('comp', {
template: `<div #click="sayHello">{{ content }}</div>`,
props: ['content'],
methods: {
sayHello() {
console.log('Hello there ' + this.content);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<button #click="getContent">getContent</button>
<ul v-if="component">
<li v-for="(cont, i) in contents" :key="i">
<component :is="component" :content="cont"> </component>
</li>
</ul>
</div>

Related

Active events in vue instance encapsulated inside shadowDOM

I'm searching to trigger event from a vue instance which is encapsulated inside a shadowDOM.
For the moment, my code create a new Vue instance inside a shadowDOM and print the template without the style (because nuxt load the style inside the base vue instance).
I can call methods of each instance
But, when i'm trying to add an event / listener on my component, it doesn't work.
DOM.vue
<template>
<section ref='shadow'></section>
</template>
<script>
import bloc from '#/components/bloc.vue'
import Vue from 'vue'
export default {
data() {
return {
vm: {},
shadowRoot: {},
isShadowReady: false
}
},
watch: {
isShadowReady: function() {
let self = this
this.vm = new Vue({
el: self.shadowRoot,
components: { bloc },
template: "<bloc #click='listenClick'/>",
methods: {
listenClick() { console.log("I clicked !") },
callChild() { console.log("I'm the child !") }
},
created() {
this.callChild()
self.callParent()
}
})
}
},
mounted() {
const shadowDOM = this.$refs.shadow.attachShadow({ mode: 'open' })
this.shadowRoot = document.createElement('main')
shadowDOM.appendChild(this.shadowRoot)
this.isShadowReady = true
},
methods: {
callParent() {
console.log("I'am the parent")
},
}
}
</script>
bloc.vue
<template>
<div>
<p v-for='word in words' style='text-align: center; background: green;'>
{{ word }}
</p>
</div>
</template>
<script>
export default {
data() {
return {
words: [1,2,3,4,5,6]
}
}
}
</script>
Any idea ?
Thank you
Hi so after i watched at your code i think it's better to use the $emit to pass event from the child to the parent and i think it's more optimized then a #click.native
template: "<bloc #someevent='listenClick'/>",
<template>
<div #click="listenClickChild">
<p v-for='(word, idx) in words' :key="idx" style='text-align: center; background: green;'>
{{ word }}
</p>
</div>
</template>
<script>
export default {
data() {
return {
words: [1,2,3,4,5,6]
}
},
methods: {
listenClickChild() {
this.$emit('someevent', 'someValue')
}
}
}
</script>
bloc #click.native='listenClick'/>

Vue2Js: Is it possible to call a method with attributes from one component in another?

I have 3 Components: Component 1 is Top Menu, Component 2 is a kind of lib with functions, Component 3 is main code.
Ideally, I want to use in Component 1 a method from Component 2 with attributes from Component 3.
And then use Component 1 in Component 3.
// Component 1 Top Menu
Vue.component('component1', {
props: {
titleTest: {
type: String,
required: true
},
textTest: {
type: String,
required: true
}
},
template: `
<div>
<div :title="titleTest">{{ titleTest }}</div>
<div :data-test="textTest">{{ textTest }}</div>
<button id='test' type="submit" #click="bar">TestButton</button>
</div>
`,
created() {
console.log('Created1');
this.$root.$refs.component1 = this;
},
methods: {
//alertText should be passed from Component 3
bar: function(alertText) {
console.log(alertText);
this.$root.$refs.component2.foo(alertText);
}
}
});
// Component 2 Lib
Vue.component('component2', {
created() {
this.$root.$refs.component2 = this;
},
methods: {
foo: function(alertText) {
alert('this is component2.foo' + alertText);
}
}
});
// Component 3 Main Code
Vue.component('component3', {
template: `
<div>
<div>Some text</div>
<ul>
<li>List Item1</li>
<li>List Item2</li>
</ul>
<div>
<component1 ref="component1" :title-test="test1" :text-test="test2"></component1>
</div>
</div>
`,
data: function() {
return {
test1: 'testText1',
test2: 'testText2',
alertText: 'alertText'
};
},
created: function() {
}
});
new Vue({
el: '#plotlyExample',
});
I am not really sure is it possible, tried with "emits" but did not work.
When you call Vue.component('component2', {...}), it just tels Vue about named component, and that does not make component instance, for using methods of component2 it should be rendered/mounteded. But i think, you wrong using component2, for same, will be better using mixins or provide/reject or vuex.

Vue - how to append components?

I'm trying to create a button that, once is pushed, adds a child component to my component. My code works, except that the HTML is being parsed as a string, here is the parent component:
<template>
<div id="app">
<li v-for="item in items">
{{ item.component }}
</li>
</div>
</template>
<script>
export default {
props:{},
data(){
return {
items: []
}
},
mounted() {},
computed: {},
methods: {
addItem(){
this.items.push({component: '<testcomponent />'})
}
}
}
</script>
And the child component is very basic:
<template>
<div id="app">
<h1>Testing</h1>
</div>
</template>
So the problem with my code is that, every time i push the button, instead of loading every time a new instance of the child component, it will parse the HTML as a string: '<testcomponent />'. How can i fix this? Thanks in advance!
You can push the component name, and use <component :is="item.component"/> in the v-for as follows:
const testcomponent = Vue.component('testcomponent', {
template: '#testcomponent',
});
new Vue({
el:"#app",
components: { testcomponent },
data() { return { items: [] } },
methods: {
addItem() {
this.items.push({ component: 'testcomponent' });
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template id="testcomponent">
<div><h1>Testing</h1></div>
</template>
<div id="app">
<button #click="addItem">Add</button>
<li v-for="item in items">
<component :is="item.component"/>
</li>
</div>

Can you pass an external function to a Vue component as a prop?

I have a legacy codebase mostly built in PHP. I'm researching how to turn commonly used parts of the code into re-usable Vue components that can be plugged in as needed.
In one case, I have an onclick event in the html which will need to be individually passed to a child component. onclick="func()"
I want to be able to pass that func to the component from the markup, without defining this one-time use function as a property method either on the component or its parents.
I can't find anything in the Vue docs or elsewhere on how to do that. Every attempt I make gives an error:
Property or method "hi" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
Is there a way to pass externally-defined functions in the global scope to a Vue instance?
Vue tabs:
Vue.config.devtools = true;
Vue.component('tabs', {
template: `
<div class="qv-tabs">
<div class="tab">
<ul>
<li v-for="tab in tabs"
:class="{'is-active' : tab.isActive}"
#click="tab.callHandler"
>
<a :href="tab.href" #click="selectTab(tab)">{{tab.name}}</a>
</li>
</ul>
</div>
<div class="tab-content">
<slot></slot>
</div>
</div>
`,
data(){
return{
tabs: []
};
},
created(){
this.tabs = this.$children;
},
methods:{
selectTab(selectedTab){
this.tabs.forEach(tab => {
tab.isActive = (tab.name == selectedTab.name);
});
},
otherHi() {
alert('other hi');
}
}
});
Vue.component('tab', {
template: `
<div v-show="isActive">
<slot></slot>
</div>
`,
props: {
name: {required: true},
selected: {default: false},
callHandler: Function,
clickHandler: {
type: Function,
default: function() { console.log('default click handler') }
}
},
data(){
return{
isActive: false
}
},
methods: {
callHandler() {
console.log('call handler called');
this.clickHandler();
}
},
computed:{
href(){
return '#' + this.name.toLowerCase().replace(/ /g, '-');
}
},
mounted(){
this.isActive = this.selected;
}
});
new Vue({
el: '.app',
methods: {
vueHi() { alert('hi from vue'); }
}
});
function hi() {
alert('hi!');
}
Markup:
<div class="app">
<tabs>
<tab name="Tab 1" :selected="true" v-bind:call-handler="hi">
<p>Tab content</p>
</tab>
<tab name="Tab 2">
<p>Different content for Tab 2</p>
</tab>
</tabs>
</div>
You could define your methods like this in your component :
...
methods: {
hi
}
...
But you will have to define it in every component where you need this function. Maybe you can use a mixin that define the methods you want to access from yours components, and use this mixins in these components ? https://v2.vuejs.org/v2/guide/mixins.html
Anoter solution ( depending on what you try to achieve ) is to add your method to the Vue prototype like explained here :
https://v2.vuejs.org/v2/cookbook/adding-instance-properties.html
Vue.prototype.$reverseText = function(string) {
return string.split('')
.reverse()
.join('')
}
With this method defined in the Vue prototype, you can use the reverseText method like this in all of your components template :
...
<div> {{ $reverseText('hello') }} </div>
...
Or from yours script with this :
methods: {
sayReverseHello() {
this.$reverseText('hello')
}
}

vuejs prototype array not being watched

in my vuejs program i am trying to make a global instance of an alert/notification system. This would be at the rootmost instance of the app. and then my plan was to push to an array of objects and pass that through to the component.
This only half works.
in my app.vue i have
<template>
<div id="app">
<alert-queue :alerts="$alerts"></alert-queue>
<router-view></router-view>
</div>
</template>
in my main.js i have
exports.install = function (Vue, options) {
Vue.prototype.$alerts = []
}
and my alert_queue.vue is
<template>
<div id="alert-queue">
<div v-for="alert in alerts" >
<transition name="fade">
<div>
<div class="alert-card-close">
<span #click="dismissAlert(alert)"> × </span>
</div>
<div class="alert-card-message">
{{alert.message}}
</div>
</div>
</transition>
</div>
</div>
</template>
<script>
export default {
name: 'alert',
props: {
alerts: {
default: []
}
},
data () {
return {
}
},
methods: {
dismissAlert (alert) {
for (let i = 0; i < this.alerts.length; i++) {
if (this.alerts[i].message === alert.message) {
this.alerts.splice([i], 1)
}
}
}
}
}
</script>
I can add to this list now by using this.$alerts.push({}) and i can see they are added by console.logging the results.
The problem is that the component doesn't recognise them unless i manually go in and force it to refresh by changing something in code and having webpack reload the results. as far as i can see, there is no way to do this programatically.... Is there a way to make prototype components be watched like the rest of the application?
I have tried making the root most file have a $alerts object but when i use $root.$alerts.push({}) it doesn't work because $root is readonly.
Is there another way i can go about this ?
You could make $alerts a Vue instance and use it as an event bus:
exports.install = function (Vue, options) {
Vue.prototype.$alerts = new Vue({
data: {alerts: []},
events: { ... },
methods: { ... }
})
}
Then in your components you might call a method this.$alerts.addAlert() which in turn pushes to the array and broadcasts an event alert-added. In other places you could use this.$alerts.on('alert-added', (alert) => { ... }
Other than that, I think this is a good use case for Vuex, which is pretty much designed for this: https://github.com/vuejs/vuex
Properties defined on Vue.prototype are not reactive like a Vue instance's data properties.
I agree that, in most cases, Jeff's method or using Vuex is the way to go.
However, you could simply set this.$alerts as a Vue instance's data property and then updating that property (which would be reactive) would, by association, update the global $alerts array:
Vue.prototype.$alerts = ['Alert #1'];
Vue.component('child', {
template: `<div><div v-for="i in items">{{ i }}</div></div>`,
props: ['items'],
})
new Vue({
el: '#app',
data() {
return {
globalAlerts: this.$alerts,
}
},
methods: {
addToArray() {
this.globalAlerts.push('Alert #' + (this.globalAlerts.length + 1));
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.1/vue.min.js"></script>
<div id="app">
<child :items="$alerts"></child>
<button #click="addToArray">Add alert</button>
</div>

Categories

Resources