EmberJS: Insert component dynamically - javascript

Got following components:
App.ParentComponent = Ember.Component.extend({
child: '',
yieldTemplateName: 'empty_by_default',
setup: function(){
//append here child compoment
}.on('didInsertElement')
});
App.ChildComponent = Ember.Component.extend({
templateName: 'some_location',
actions:{
//whatever
}
});
What am I wondering if is possible to add child component into parent's at any render status and based on a parameter?
So I call, for example:
{{parent child="child"}}
And it eventually renders:
<div !-- parent -- >
<div !-- child -- > </div>
</div>

App.ParentComponent = Ember.Component.extend({
child: null,
hasChild: Em.computed.notEmpty('child')
});
// parent template
{{#if hasChild}}
{{component child}}
{{/if}}
// another template
{{parent child="child"}} {{!-- displays ChildComponent --}}
Note: The component helper will be available in Ember 1.11. If you are using an earlier version, you can use the ember-dynamic-component addon.

Related

How to bind whole component in Vue.js?

I just want to ask how to bind whole component (for example div) in Vue.js. Is there anything like innerHTML? Here is example:
Parent.vue
<template>
<div id="parent">
//some elements
</div>
</template>
Child.vue
<template>
<div id="child">
//some different elements
</div>
</template>
Now how to innerHTML child in parent? I've tried something like v-html:component and then data(){ return{ component: and here I dont know how to pass whole vue Component like Child.vue div. Should I use refs or something?
Now I use visibility attribute from css and I change it but I don't think that is good way to do this.
If you want to switch between components, then check out VueJS dynamic components:
https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components
You can use the component element and the :is prop to send down what component to render.
I have a working demo here: https://codepen.io/bergur/pen/bPEJdB
Imagine the following simple Vue component
Vue.component('menuList', {
data() {
return {
list: ['Menu item A', 'Menu item B']
}
},
template: `
<ul>
<li v-for="item in list">{{ item}}</li>
</ul>
`
})
This is a simple component rendering a unordered list of menu items. Lets create another similiar component that renders ordered list of products. Note that just to make them a little different, the menuList that has ul and the productList has ol
Vue.component('productList', {
data() {
return {
list: ['Product item A', 'Product item B']
}
},
template: `
<ol>
<li v-for="item in list">{{ item}}</li>
</ol>
`
})
Now we can create a main VueJS that renders these components depending on which button I press. You can have what ever trigger/action you want to change the component.
new Vue({
name: 'main',
el: '#main',
data() {
return {
header: 'Component switching',
selectedComponent: 'menuList'
}
},
methods: {
setComponent(name) {
this.selectedComponent = name
}
},
template: `<div>
<button #click="setComponent('menuList')">Menu List</button>
<button #click="setComponent('productList')">Products</button>
<component :is="selectedComponent" />
</div>`
})
So here the magic begins.
We create a app with some data properties. The header property is just a string value, and selectedComponent tells us which component is beeing rendered.
In our template we use the <component :is="selectedComponent /> so initially the menuList component is the active one.
We create a method called setComponent that takes in a string value and sets that as a new value for selectedComponent. By pressing a button a new value for selectedComponent is set and the component is rendered. Voila

VueJS 2: Catch event of direct child component

I'm currently trying to get a simple Tabs/Tab component up and running.
It seems like something in the event handling mechanism has changed, therefore I can't get it to work.
Current implementation:
Tabs.vue
<template>
<div class="tabbed-pane">
<ul class="tab-list">
<li class="tab" v-for="tab in tabs" #click="activateTab(tab)">{{ tab.header }}</li>
</ul>
<slot></slot>
</div>
</template>
<script>
import hub from '../eventhub';
export default {
props: [],
data() {
return {
tabs: []
}
},
created() {
this.$on('tabcreated', this.registerTab)
},
methods: {
registerTab(tab) {
this.tabs.push(tab);
},
activateTab(tab) {
}
}
}
</script>
Tab.vue
<template>
<div class="tab-pane" v-show="active">
<slot></slot>
</div>
</template>
<script>
import hub from '../eventhub';
export default {
props: {
'header': String
},
data() {
return {
active: false
}
},
mounted() {
this.$emit('tabcreated', this);
}
}
</script>
eventhub.js
import Vue from 'vue';
export default new Vue();
View
<tabs>
<tab header="Test">
First Tab
</tab>
<tab header="Test2">
Second Tab
</tab>
<tab header="Test3">
Third Tab
</tab>
</tabs>
I've tried the following things:
use a Timeout for the $emit to test if it's a timing issue (it is
not)
use #tabcreated in the root element of the Tabs components
template
It works if...
... I use the suggested "eventhub" feature (replacing this.$on and
this.$emit with hub.$on and hub.$emit)
but this is not suitable for me, as I want to use the Tabs component multiple times on the same page, and doing it with the "eventhub" feature wouldn't allow that.
... I use this.$parent.$emit
but this just feels weird and wrong.
The documentation states that it IS possible to listen for events triggered by $emit on direct child components
https://v2.vuejs.org/v2/guide/migration.html#dispatch-and-broadcast-replaced
Does anyone have an Idea?
You're right, in vue 2, there is no more $dispatch. $emit could work for a single component but it will be scoped to himself (this). The recommended solution is to use a global event manager, the eventhub.
the eventhub can be stored in the window object to be used anywhere without import, I like to declare in my main.js file like this:
window.bus = new Vue()
and then in whatever component:
bus.$emit(...)
bus.$on(...)
It works just the same as this.$root.$emit / this.$root.$on. You said it works when you call this.$parent.$emit, but this code, simulate a scoped emit in the parent component but fired from the child, not good.
What I understand in your code is that you want to have an array of created tabs, but to do what with them ?
Instead of storing the tab instance in the parent and then activate from the parent, you should think about a more functional way.
The activateTab method should be declared on the tab component and manage the instanciation through the data, something like:
Tabs.vue
<template>
<div class="tabbed-pane">
<ul class="tab-list">
<tab v-for="tab in tabs" :header="tab.header"></tab>
</ul>
</div>
</template>
<script>
import hub from '../eventhub';
import Tab from 'path/to/Tab.vue';
export default {
components: [Tab],
props: [],
data() {
return {
tabs: ['First Tab', 'Second Tab', 'Third Tab']
}
}
}
</script>
Tab.vue
<template>
<div class="tab tab-pane" #click:activeTab()>
<span v-show="active">Activated</span>
<span>{{ header }}</span>
</div>
</template>
<script>
import hub from '../eventhub';
export default {
props: {
'header': String
},
data() {
return {
active: false
}
},
methods: {
activeTab () {
this.active = true
}
}
}
</script>
This way, your Tab is more independant. For parent/child communication keep this in mind :
parent to child > via props
child to parent > via $emit (global bus)
If you need a more complexe state management you definitely should take a look at vuex.
Edit
Tabs.vue
<template>
<div class="tabbed-pane">
<ul class="tab-list">
<tab v-for="tabData in tabs" :custom="tabData"></tab>
</ul>
</div>
</template>
<script>
import Tab from 'path/to/Tab.vue';
export default {
components: [Tab],
props: [],
data() {
return {
tabs: [
{foo: "foo 1"},
{foo: "foo 2"}
{foo: "foo 3"}
]
}
}
}
</script>
Tab.vue
<template>
<div class="tab tab-pane" #click:activeTab()>
<span v-show="active">Activated</span>
<span>{{ custom.foo }}</span>
</div>
</template>
<script>
export default {
props: ['custom'],
data() {
return {
active: false
}
},
methods: {
activeTab () {
this.active = true
}
}
}
</script>
This is what I don't like about VueJS (2), there is no convenient way of catching events emitted from child components to the parent component.
Anyways an alternative to this is if you do not want to use the eventhub approach, specially if you are only going to have an event communication between related components ( child and parent ) and not with non-related components, then you can do these steps.
reference your parent vue component on its data property (very important, you can't just pass this to the child component)
pass that parent vue component reference as an attribute to the child component ( make sure to bind it)
trigger the appropriate event of the parent component inside the child component whenever a desired event is emitted
Pseudo code
// Parent vue component
Vue.component( 'parent_component' , {
// various codes here ...
data : {
parent_component_ref : this // reference to the parent component
},
methods : {
custom_event_cb : function() {
// custom method to execute when child component emits 'custom_event'
}
}
// various codes here ...
} );
// Parent component template
<div id="parent_component">
<child_component :parent_component_ref="parent_component_ref"></child_component>
</div>
// Child component
Vue.component( 'child_component' , {
// various codes here ...
props : [ 'parent_component_ref' ],
mounted : function() {
this.$on( 'custom_event' , this.parent_component_ref.custom_event_cb );
this.$emit( 'custom_event' );
},
// You can also, of course, emit the event on events inside the child component, ex. button click, etc..
} );
Hope this helps anyone.
Use v-on="$listeners", which is available since Vue v2.4.0. You can then subscribe to any event you want on the parent, see fiddle.
Credit to BogdanL from Vue Support # Discord.

What is the proper way to mutate values from an imported sibling component in Vue.js 2.0?

I'm using the single-file component approach with Vue 2.0. I have 3 components App (parent), AppHeader, and FormModal. AppHeader and FormModal are immediate children of App and siblings of each other.
The goal is that when a button in AppHeader is clicked the FormModal visibility should be toggled. The problem I'm having is understanding Vue's uni-directional data flow. How can I pass an event back up to the parent (App) from AppHeader to let it know to mutate the form's visibility?
(AppHeader.vue)
<template>
<header>
<div class="app-header">
<h1 class="app-title">Sample Header</h1>
<a class="link-right" v-on:click="toggleModal()" href="#">
<i class="fa fa-pencil-square-o"></i>
</a>
</div>
</header>
</template>
<script>
import FormModal from "./FormModal.vue";
export default {
name : "AppHeader",
props : ['visible'],
methods : {
toggleModal () {
this.visible = !this.visible;
}
},
components : {
FormModal
}
}
</script>
(FormModal.vue)
<template>
<div class = "form-content" v-if="visible">
<!-- Some form markup -->
</div>
</template>
<script>
export default {
name : "FormModal",
props : ['visible']
//Also have data, methods, and computed here, but they aren't relevant to the example.
}
</script>
I've certainly botched the concept of props in this example. I'm very confused about the correct way to use props when importing a template.
Edit:
Forgive me, this is my first day working with Vue. I initially left out a very important piece of information, my App.vue file, which is the parent of all of my templates.
(App.vue)
<template>
<div class="app">
<AppHeader></AppHeader>
<FormModal></FormModal>
</div>
</template>
<script>
import AppHeader from "./AppHeader.vue";
import Compose from "./FormModal.vue";
export default {
data () {
return {
views : [AppHeader, FormModal]
}
},
components : {
AppHeader,
FormModal
}
}
</script>
In summary, App is the parent. There are 2 siblings, AppHeader and FormModal. When a button gets clicked in AppHeader, FormModal's visibility should be toggled.
I don't yet have a great grasp of Vue's uni-directional data flow and I'm not sure how to approach this scenario.
In your app header you need to bind "visible" data field to a "visible" attribute in the child (form-modal) component. This means that "visible "data field in AppHeader and "visible" property inside the child component will be "tight" together and any change that is done to the "visible" data field will be reflected inside form modal.
<form-modal :visible="visible"/>
...
<script>
import FormModal from "./FormModal.vue";
export default {
name : "AppHeader",
data() {
return {
visible: false
}
},
methods : {
toggleModal () {
this.visible = !this.visible;
}
},
components : {
FormModal
}
}
</script>
FormModal Template:
<template>
<div class="form-conten" v-if="visible">
<!-- Some form markup -->
</div>
</template>
<script>
export default {
name : "FormModal",
props : ['visible']
}
</script>
The idea is that your FormModal listens to any changes of "visible" prop that come from it's parent. Think about it as a "readonly" variable (in FormModal).

Vue.js routing no method handled

I get some trouble with Vue.js and his routing system. I made an example here.
Why the method is correctly attach when I use a template (see Foo) and why not when I use an el (see Bar) ?
Here his the code:
index.js
var Foo = Vue.extend({
template: '<p v-on:click.prevent="foo">This is foo!</p>',
methods: {
foo: function(){
alert('YEAH')
}
}
})
var Bar = Vue.extend({
el: function(){
return '#bar'
},
methods: {
bar: function(){
alert('YEAH')
}
}
})
var App = Vue.extend({})
var router = new VueRouter()
router.map({
'/foo': {
component: Foo
},
'/bar': {
component: Bar
}
})
router.start(App, '#app')
index.html
<div id="app">
<h1>Hello App!</h1>
<p>
<!-- use v-link directive for navigation. -->
<a v-link="{ path: '/foo' }">Go to Foo</a>
<a v-link="{ path: '/bar' }">Go to Bar</a>
</p>
<div id="bar" v-bind:class="$route.path == '/bar' ? '' : 'hidden'">
<p v-on:click.prevent="bar">This is bar!</p>
</div>
<!-- use router-view element as route outlet -->
<router-view></router-view>
</div>
You misunderstood the el purpose. When you pass el to component it tells Vue on which element to mount itself
Note that the provided element merely serves as a mounting point; it will be replaced if a template is also provided, unless replace is set to false. The resolved element will be accessible as vm.$el.
Actually you have no template inside #bar that Vue could compile and that's why you have no output. Also using el inside another Vue's el (in your case #app) is a bad idea. The v-on:click.prevent="bar" bit is compiled in parent's (App instance) scope, and since App has no bar method you get a warning.
Better solution:
http://codepen.io/anon/pen/zqWKrg
Notice that now each component has it's own template and you can clearly see scope of each component: #app is compiled in App scope, #foo is compiled in Foo scope and #bar is compiled in Bar scope.
you should use template: '#bar' instead of el: '#bar' the el option is not a selector for the template.
var Bar = Vue.extend({
template: '#bar',
methods: {
bar: function(){
alert('YEAH')
}
}
})
You (ab)used a regular HTML element in the Apps main template as a sub-template for a component - that's not something you should do.
Its also the reason the click event is not working: the content of that div, including the click event, is evaluated by the App component, not the Bar component. And the App component has no "bar" method.
Hence the error in the console:
[Vue warn]: v-on:click="bar" expects a function value, got undefined
The template should look like this:
<script type="x-template" id="bar">
<div>
<p v-on:click.prevent="bar">This is bar!</p>
</div>
</script>
<!-- or with HTML5 template tag: -->
<template id="bar">
<div>
<p v-on:click.prevent="bar">This is bar!</p>
</div>
</template>

How do i get the container name of a component within itself?

How do i get the container name of a component within itself?
this.componentNameStream used to kind of work in 1.11.
// in components/my-component.js
export default Component.extend({
layoutName: "components/my-component",
partialName: function() {
//return "my-component"; somehow....
}.property();
});
why? for subclassing reasons:
// in components/blah.js
export default MyComponent.extend({});
// in templates/components/my-component.hbs
<div class="someLayout">
// partialName is now "components/blah"
{{ partial partialName }}
</div>
Not sure I fully understand what you are trying to do but the usage of {{partial}} is not recommended (see https://github.com/dockyard/styleguides/blob/master/ember.md#templates).
Why not simply share a template between two components using layoutName?
// in components/my-foo.js
export default Component.extend({
showSomething: false
});
// in components/my-bar.js
export default MyFooComponent.extend({
layoutName: "components/my-foo",
showSomething: true
});
// in templates/components/my-foo.hbs
<div class="someLayout">
{{#if showSomething}}
I am the bar component!
{{/if}}
</div>
If you really need to use a partial, you could replace showSomething by a property which contains the name of the partial to be displayed as a string.

Categories