How to use slots inside of template components in HTML - html

I'm trying to use slots inside of a Vue component to display different titles more easily. However, when I try to replace the slot with data, regardless of the relative positioning in the markup, the slot only uses it's fallback option.
It's my understanding that the template to be used goes first, with a label, then slots are put in and given a "name," with fallback text between the opening and closing slot tags, like so:
<template id="somename-template>
<slot name="attrname>Some Fallback</slot>
</template>
Then data is stored as such:
<somename>
<span slot="attrname">Real text</slot>
</somename>
I have tried repositioning the both above and below the script, and above and below the , however no combination provides the expected results.
My actual code:
<body>
<template id="comp-dem-template">
<header-component></header-component>
</template>
<script>
customElements.define('comp-dem',
class extends HTMLElement {
constructor() {
super();
const template = document.getElementById('comp-dem-template').content;
const shadowRoot = this.attachShadow({mode: 'open'}).appendChild(template.cloneNode(true));
}
});
Vue.component('header-component', {
template: '<h1><slot name="pagetitle">Page Title Fallback</slot></h1>'
})
new Vue({ el: '#comp-dem-template' })
</script>
<comp-dem>
<span slot="pagetitle">
Images
</span>
</comp-dem>
</body>
The markup should look like:
<h1>Images</h1>
However, instead looks like:
<h1>Page Title Fallback</h1>
I can tell it's probably a super simple thing that I'm doing wrong (or it's the wrong tool for the job), but even looking at other working examples, I can't tell what that exactly is.

It's not quite clear to me what you're trying to accomplish. You're passing the <span slot="pagetitle">Images</span> to <comp-dem> but the <comp-dem> component doesn't have a slot - it's the <header-component> that has a slot. Why do you need to wrap a component in a component?
For the code to work, the slot needs to be passed like so:
<body>
<template id="comp-dem-template">
<header-component>
<span slot="pagetitle">
Images
</span>
</header-component>
</template>
<script>
Vue.component('header-component', {
template: '<h1><slot name="pagetitle">Page Title Fallback</slot></h1>'
})
new Vue({ el: '#comp-dem-template' })
</script>
</body>
Or, if you insist on using <comp-dem>, I think you might need to do the following:
<body>
<template id="comp-dem-template">
<header-component>
<span slot="pagetitle">
<slot name="pagetitle"><slot>
</span>
</header-component>
</template>
<script>
customElements.define('comp-dem',
class extends HTMLElement {
constructor() {
super();
const template = document.getElementById('comp-dem-template').content;
const shadowRoot = this.attachShadow({mode: 'open'}).appendChild(template.cloneNode(true));
}
});
Vue.component('header-component', {
template: '<h1><slot name="pagetitle">Page Title Fallback</slot></h1>'
})
new Vue({ el: '#comp-dem-template' })
</script>
<comp-dem>
<span slot="pagetitle">
Images
</span>
</comp-dem>
</body>

Related

Vue: Pass function into parent component

Okay, I'm new to Vue and trying to simplify my problem as much as I can do.
I have two components: NewsItem, NewsItemList
NewsItem:
<template>
<div>
<h1>A title</h1>
<a #click="aFunctionDeclaredInThisComponent">Link</a>
</div>
</template>
NewsItemList:
<template>
<NewsItem />
<div>A mystic box</div>
</template>
I want to show/hide the mystic box in NewsItemList, if the function aFunctionDeclaredInThisComponent is activated, which is in NewsItem. I would like to toggle show/hide with a active state in CSS.
My problem is, I don't know how to pass the function into the parent component. Please note, that I use <script setup>.
Thank you!
Not sure what you are trying to do, but I wrote you a minimal example of how to send data from child component to parent component. Check it out here.
The idea is to emit an event from the child and listen to it in the parent. Don't mind the naming I used, just try to understand how I'm interacting with the data in parent component via child one and try to implement it into your code:
Parent:
<template>
<Comp #my-var="callback" />
<p
v-text="`Clicked hide from child? ${test}`"
/>
<div v-show="! test">
I'm the parent div
</div>
</template>
<script setup>
import { ref } from 'vue'
import Comp from './Comp.vue'
const test = ref(false)
const callback = data => test.value = data
</script>
Child:
<template>
<button
v-text="'click to hide parent div'"
#click="doEmit()"
/>
</template>
<script setup>
const emits = defineEmits(['myVar'])
const doEmit = () => emits('myVar', true)
</script>

How can I get data from another Vue file?

As the code show below, A.vue file has element data return some number values
<template></template>
<script>
export default {
data(){
return{
element: [
{
number:'11'
}
{
number:'22'
}
]
}
}
}
</script>
Now I want to get element.length from A.vue to B.vue. Is there a way to do that? I saw a solution with button click but i dont want to use button to pass data.
B.vue file
<template>
<div>I want to get element.length here</div>
</template>
You can simply achieve it by passing prop (which contains the length of the element array) from A.vue component to B.vue component. Here is the live demo :
Vue.component('bcomponent', {
// declare the props
props: ['length'],
// just like data, the prop can be used inside templates
// and is also made available in the vm as this.message
template: '<div>Element length: {{ length }}</div>',
});
var app = new Vue({
el: '#app',
data: {
element: [{
number: '11'
}, {
number: '22'
}]
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<!-- Component A -->
<div id="app">
<BComponent :length="element.length">
</BComponent>
</div>
If it's possible, just pass the data as a prop from B to A, this way you can implement any logic on the data.
If it's not, you should use vuex for data storage, so any component can access it.

add Vue component in v-html [duplicate]

I am trying render a component using props. But only works when content props is not a component.
Here is an example: https://jsfiddle.net/eugenio4/onf54vt5/
// register modal component
Vue.component('component', {
template: '<span class="component-tag">This is component</span>',
})
// start app
new Vue({
el: '#app',
data: function (){
return {
test1 : '<label>this is label</label>',
test2 : '<component></component>' //this doest work
}
}
})
<!-- app -->
<div id="app">
<span v-html="test1"></span>
<span v-html="test2"></span>
</div>
Is this possible?
No, You can not do this using v-html, as documentation clearly points out:
Note that the contents are inserted as plain HTML - they will not be compiled as Vue templates.
The contents are inserted as plain HTML - data bindings/vue components are ignored. Note that you cannot use v-html to compose template partials, because Vue is not a string-based templating engine. Instead, components are preferred as the fundamental unit for UI reuse and composition.

What's the more efficient way to create dynamic tags on MeteorJS / Blaze?

I'm looking for a solution to manage a HTML tag type with a Reactive Var. I looked all the Blaze documentation but found nothing..
Simple example
I want to change a tag from div to form when a boolean ReactiveVar is updated.
Template.MyExample.onCreated(function() {
this.is_form = new ReactiveVar(false)
})
Template.MyExample.helpers({
getTag() {
return Template.instance().is_form.get() ? 'form' : 'div'
}
})
This obviously didn't work:
<Template name="MyExample">
<{{getTag}}>
</{{getTag}}>
</Template>
Nicer solution ?
The "best" way I found to get it was to create a tag template and list everycase a single time, but I didn't like that solution.
Template.MyExample.onCreated(function() {
this.is_form = new ReactiveVar(false)
})
Template.MyExample.helpers({
getTag() {
return Template.instance().is_form.get() ? 'form' : 'div'
}
})
Template.MyExample.events({
'click .switch'(e, instance) {
e.preventDefault()
instance.is_form.set(!instance.is_form.get())
}
})
Blaze Templates:
<Template name="MyExample">
<div>
Switch type
{{#MyTag tag=getTag}}
Parent tag is {{getTag}}
{{/MyTag}}
{{#MyTag tag="a" attributes=(object href="#" target="_blank")}}
Link
{{/MyTag}}
</div>
</Template>
<Template name="MyTag">
{{#if equals tag 'form'}}
<form {{attributes}}>
{{> Template.contentBlock }}
</form>
{{else if equals tag 'a'}}
<a {{attributes}}>
{{> Template.contentBlock }}
</a>
<!-- and more and more.... -->
{{else}}
<div {{attributes}}>
{{> Template.contentBlock }}
</div>
{{/if}}
</Template>
Helpers required:
Template.registerHelper('object', function({hash}) {
return hash;
})
Template.registerHelper('equals', function (a, b) {
return a === b
})
This is working but i'm wondering if it's to much for Meteor (and DOM updates). Does this solution works like an simple {{#if}}...{{/if}} or it's way heavier ?
The feature you request is basically not supported by Blaze. While static code generators can easily include dynamic tags, this is a very hard one at runtime where you have to deal with the DOM tree, whose element's tag-types are immutable by design.
I first thought of a workaround, that uses child swapping using jQuery in the onRendered of MyTag:
Template.MyTag.onRendered(function () {
const instance = this
instance.autorun(() => {
const data = Template.currentData()
const attributes = data.attributes || {}
const elementName = data.tag
const refTag = instance.$('.my-tag-ref')
const newTag = $(`<${elementName}>${refTag.html()}</${elementName}>`)
Object.keys(attributes).forEach(attKey => newTag.attr(attKey, attributes[ attKey ]))
newTag.addClass('my-tag-ref')
refTag.replaceWith(newTag)
})
})
But this is unfortunately not working, because the content bock looses it's reactivity and the jQuery instance of the current Template looses it's scope to the root element. I just add it here in case someone catches up on this and finds a solution that works.
Now there is still a solution that works using dynamic Templates:
<Template name="MyTag">
{{#Template.dynamic template=getTemplate data=getData}}
{{> Template.contentBlock }}
{{/Template.dynamic}}
</Template>
<template name="mytaga">
<a {{attributes}}>
{{> Template.contentBlock }}
</a>
</template>
<template name="mytagform">
<form {{attributes}}>
{{> Template.contentBlock }}
</form>
</template>
<template name="mytagdiv">
<div {{attributes}}>
{{> Template.contentBlock }}
</div>
</template>
As you can see the disadvantage is clearly that you have to define lots of new Templates. The advantage is, that you don't have to use so many if/else anymore and it pays out the more often you will have to include MyTag in your code.
The respective helpers look like the following:
Template.MyTag.helpers({
getTemplate() {
const instance = Template.instance()
console.log(instance.data)
return `mytag${instance.data.tag}`
},
getData () {
return Template.instance().data
}
})
This is working but i'm wondering if it's to much for Meteor (and DOM updates). Does this solution works like an simple {{#if}}...{{/if}} or it's way heavier ?
Blaze is overall slower than for example React or Vue. However, the rendering only updates if the reactive data updates, thus it is just as heavy as the amount of updates to be triggered.

Using the <content> tag in a shadow DOM

I'm trying to use the shadow DOM without any framework that abstracts it, mostly in order to learn its intricacies. I am using the webcomponents.js polyfills, so it works cross-browser.
I've managed to create a custom element, and set its shadow DOM, but for some reason the <content></content> tag isn't transcluding the expected content.
I expect this example to render as
Shadow DOM
Hello world
More Shadow
But it renders as
Shadow DOM
More Shadow
What am I missing here?
<script src="webcomponents-lite.min.js"></script>
<test-component>
<div>Hello world</div>
</test-component>
<template id="test-component">
<div>Shadow DOM</div>
<content></content>
<div>More shadow</div>
</template>
<script>
class TestComponent extends HTMLElement {
constructor() {
super();
var shadow = super.attachShadow({ mode: 'open' });
var template = document.getElementById('test-component');
var clone = document.importNode(template.content, true);
shadow.appendChild(clone);
}
}
customElements.define('test-component', TestComponent);
</script>
(The example is complete, and can be run as an entire HTML file, provided you have webcomponents-lite.min.js available, or can be run without the .js file in supported browsers)
As noted in a comment above, you need to use the slot element and slot attribute instead.
Here’s exactly the same code as above, but implemented using the slot element and attribute:
<test-component>
<div slot=middle>Hello world</div>
</test-component>
<template id="test-component">
<div>Shadow DOM</div>
<slot name=middle></slot>
<div>More shadow</div>
</template>
<script>
class TestComponent extends HTMLElement {
constructor() {
super();
var shadow = super.attachShadow({ mode: 'open' });
var template = document.getElementById('test-component');
var clone = document.importNode(template.content, true);
shadow.appendChild(clone);
}
}
customElements.define('test-component', TestComponent);
</script>