Vue: bind dynamic and static classes - vue.js

A bit new to Vue so I am looking for a better way(if there's one) to set a default class without using the class attribute twice.
Component.vue
<template>
<input class="form__control" :class="class" v-bind="$attrs"/>
<template>
<script>
export default {
props: ['class']
}
<script>
Usage
<component class="big-red" data-id="1" type="text"/>

You can bind dynamic and static classes within a single property, using the array declaration:
<template>
<input :class="['form__control', class]" v-bind="$attrs"/>
<template>

Related

My dynamic component (layout) doesn't work with named slots in vuejs

I have problems to combine dynamic generated layouts with named slots.
To define my layouts I'm using "component :is"
//app.vue
<template>
<component :is="layout">
<router-view />
</component>
</template>
<script>
computed: {
layout() {
const layout = this.$route.meta.layout || 'default'
return () => import(`#/app/layouts/${layout}.vue`)
}
},
</script>
//layouts/default.vue
<template>
<div>
<div>
<slot name="header" />
</div>
<div>
<div>
<slot name="sidebar" />
</div>
<div>
<slot name="default"/>
</div>
</div>
</div>
</template>
// views/page.vue
<template>
<div>
<template #header>
<h1>Primitives</h1>
</template>
<template #sidebar>
<ul>
<li v-for="primitive in sections.sections" :key="primitive">
<router-link :to="`/primitives/${primitive}`">{{primitive}}</router-link>
</li>
</ul>
</template>
<template #default>
<router-view :key="$router.path" />
</template>
</div>
</template>
But now I get this error inside my code
'v-slot' directive must be owned by a custom element, but 'div' is not.
and console displays this error
<\template v-slot> can only appear at the root level inside the receiving component
If I remove the main div I get the error
The template root requires exactly one element.
What I'm doing wrong?
This is not easy to explain so please cope with me...
I really understand what you are trying to do but unfortunately it is not possible in Vue.
Reason for that is slots are more template compiler feature than runtime feature of Vue. What I mean by that ? When Vue template compiler sees something like <template #header>, it will take the inner content and compile it into a function returning virtual DOM elements. This function must be passed to some component which can call it and include the result in it's own virtual DOM it is generating. To do that template compiler needs to know to what component it should pass the function (that is the real meaning of 'v-slot' directive must be owned by a custom element, but 'div' is not. error message...ie compiler is "looking" for a component to pass the slot content to...)
But you are trying to use the slots as if they were "discoverable" at runtime. For your code to work the dynamic layout component must at runtime somehow discover that it's child (also dynamic thanks to <router-view />) has some slot content it can use. And this is not how slots work in Vue. You can pass the slot content your component receives from parent to a child components but do not expect that parent component (layout in this case) can "discover" slot content defined in it's child components...
Unfortunately only solution for your problem is to import the layout component in every "page" and use it as a root element in the template. You can use mixins to reduce code duplication (to define layout computed)
#/mixins/withLayout.js
export default = {
computed: {
layout() {
const layout = this.$route.meta.layout || 'default'
return () => import(`#/app/layouts/${layout}.vue`)
}
}
}
views/page.vue
<template>
<component :is="layout">
<template #header>
<h1>Primitives</h1>
</template>
<template #sidebar>
<ul>
<li v-for="primitive in sections.sections" :key="primitive">
<router-link :to="`/primitives/${primitive}`">{{primitive}}</router-link>
</li>
</ul>
</template>
<template #default>
<router-view :key="$router.path" />
</template>
</component>
</template>
<script>
import withLayout from '#/mixins/withLayout'
export default {
mixins: [withLayout]
}
</script>

How expose child element v-model as the vue component v-model

I Was using a simple text area in my vue component.:
<input v-model="someRootProperty"/>
I would like to encapsulate this input inside another component, for instance
<template>
<div>
<div>...</div>
<input v-model="???"/>
</div>
</template>
I would like to use
<my-component v-model="someRootProperty" />
instead and them bypass this to the input inside the component.
What should I do inside the component to expose its input v-model as the component v-model?
<input v-model="someRootProperty"/>
Is the same as (syntactic sugar):
<input :value="someRootProperty" #input="someRootProperty = $event.target.value"/>
That means that you could accept value as a prop in the component and emit input to achieve the same thing.
MyComponent.vue
<template>
<input :value="value" #input="$emit('input', $event.target.value)>
</template>
<script>
export default {
props: ['value']
}
And then use it like this.
<MyComponent v-model="someRootProperty"/>

Cannot pass data to slot from owning component and parent context in Vue.JS

In the code below I have parent/child components where some data (I'm just using literals) is passed from the root context (data1) whilst other data is applied to the slot inside the parent component (data2). The output shows that data1 is available but data2 is not.
Why is the data2 literal not passed to the child component? I've google'd and seen references to using template with a scope but I don't understand why that would work when I'm not using v-bind in this example.
I suspect I've misunderstood something but would certainly appreciate some help!
Vue.component(
'child', {
props: ['data1', 'data2'],
template: `
<div>
<p>The child component - data1: "{{data1}}", data2: "{{data2}}"</p>
</div>`
}
)
Vue.component(
'parent', {
template: `
<div>
<p>The parent component</p>
<slot data2="data from parent component"/>
</div>`
}
)
new Vue({
el: '#el',
template: `
<div>
<parent>
<child data1="data from root"></child>
</parent>
</div>`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="el" />
Update 04/JAN
I have an alternative use case where I'm trying to encapsulate Bootstrap syntax within a component. Given this is a more concrete example it may be easier to consider. Here I want all the Bootstrap specifics inside the form-group component but I can't seem to define class="form-control" on the slot instead of the template being passed in.
I have to admit that given what #collapsar has said I think this may be a different problem but I'm still in the process of getting my head around this!
Vue.component('formGroup', {
props: ['label'],
template: `
<div class="form-group">
<label for="inputEmail3" class="col-sm-2 control-label">{{label}}</label>
<div class="col-sm-10">
<!-- I'd like to see this the class defined here merged with the template as described
in https://v2.vuejs.org/v2/guide/class-and-style.html#With-Components although admittedly
that's for components and not slots -->
<slot class="form-control"/>
</div>
</div>`
})
new Vue({
el: '#el',
template: `
<div class="container">
<form class="form-horizontal">
<form-group label="Email">
<!-- I don't want the class defined on this control but added by the form-group instead -->
<input type="email" class="form-control" id="inputEmail3" placeholder="Email">
</form-group>
</form>
</div>
`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="el">
</div>
You appear to conceptualize the slot mechanism in a slightly wrong way:
Data transfer from parent to child happens through properties which are declared in the child and defined in the parent.
Parts of the child component's template may be defined in the parent. The child component integrates these parts by means of the slot mechanism.
The template snippet may contain data placeholders. These placeholders are resolved by the child component (ie. the user of the snippet), which is where the slot element comes into play: its attributes instantiate the parameters
The rationale for why these placeholders exist in the first place might be that they are to be computed by the child component.
Finally a syntactic hook in the template snippet is needed as a placeholder mark, since the placeholder will be instantiated by the child component. This is the name of a JS object which will serve as a container for actual parameter values. As these values are specified by the child there is no way to know this container object's structure beforehand without abandoning the (child) component's data encapsulation which would would defeat the very purpose of component use. The 'slot-scope' attribute in the snippet definition contains the name of the said JS object (the necessary declaration takes place automagically in the background).
See it in code:
Vue.component(
'child', {
props: ['data1', 'data2'],
template: `
<div>
<p>The child component</p>
<p>
<span>Passed through parent: {{data1}}</span><br/>
<span>Passed from parent: {{data2}}</span><br/>
<slot data3="Data from the child template funnelled into a parent-defined slot"/>
</p>
</div>`
}
)
Vue.component(
'parent', {
props: ['data1'],
template: `
<div>
<p>The parent component</p>
<child
:data1="data1"
:data2="'data from parent'"
>
<template slot-scope="mixme">
<span>{{mixme.data3}}</span><br/>
</template>
</child>
</div>`
}
)
//
// Vue instance
// The template references the component 'parent' only.
// As 'child' is embedded into 'parent', this component will be referenced in _parent_'s template.
//
// Also note that 'data1' is passed down as a genuine property instead of a DOM attribute.
//
new Vue({
el: '#el',
template: `
<div>
<parent
:data1 = "'data from root'"
/>
</div>`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="el" />

Are custom attribute bindings possible with Vue templates?

I'm trying to bind a custom attribute value in my Vue template. How can I do this?
(EDIT: The following code actually binds correctly. A third party library (Foundation) was interfering with the binding. Leaving the question up as it may be useful to others.
<template>
<span v-bind="{ 'aria-controls': inputControlId }"></span>
<input v-bind="{ 'id': inputControlId }">
</template>
<script lang="ts">
import Vue from 'vue';
import Component from 'vue-class-component';
#Component
export default class Slider extends Vue {
inputControlId = "TheBourneId";
}
}
</script>
The common syntax for binding attributes is
<template>
<span v-bind:aria-controls="inputControlId"></span>
<input v-bind:id="inputControlId">
</template>
There is also a shorthand.
<template>
<span :aria-controls="inputControlId"></span>
<input :id="inputControlId">
</template>
You can bind multiple properties at once using the syntax in your question, it's just not commonly used outside class or style, especially for single attributes.
It sounds like the real issue was your CSS framework.

Passing mutilple HTML fragments / other components to a component

Consider a simple component in Aurelia:
export class FrameComponent
{
#bindable str1;
#bindable str2;
}
And the corresponding HTML template:
<template>
${str1}
${str2}
</template>
As simple as it gets. Now I can use this component in a parent like this:
<template>
<require from="./frame-component"></require>
<frame-component str1="Hello" str2="World"></frame-component>
</template>
Question 1
What if I don't want to provide the child component just with simple strings, but instead I want to set them with HTML fragments?
Question 2
What if the I want to pass whole complete components as in str1 and str2?
Here's a gist demo: https://gist.run/?id=0bf83980935015217cfb83250643c13f
Content Projection
It applies for Question 1 and Question 2.
[Documentation]
By using slots, you can define the content of your custom component declaratively. It can contain other custom components as well.
frame-slot.html
<template>
<div>
<slot name="str1">Default str1</slot>
</div>
<div>
<slot name="str2">Default str2</slot>
</div>
</template>
usage in app.html
<require from="./frame-slot"></require>
<frame-slot>
<div slot="str1"><h3>${slotStr1}</h3></div>
<div slot="str2">
<h3>${slotStr2}</h3>
<div>
<frame-slot></frame-slot>
</div>
</div>
</frame-slot>
Inline ViewStrategy
It applies for Question 1 and Question 2.
[Documentation]
By using InlineViewStrategy, you can define templates as plain string variables and display it with the help of <compose> element [Composition docs].
frame-inline.js
import { bindable, InlineViewStrategy } from 'aurelia-framework';
export class FrameInline {
#bindable template;
#bindable model;
viewStrategy;
attached() {
this.viewStrategy = new InlineViewStrategy(`<template>${this.template}</template>`);
}
}
frame-inline.html
<template>
<compose view.bind="viewStrategy" model.bind="model"></compose>
</template>
app.js
export class App {
inlineModel = {
name: "inline-template",
description: "This is an inline template",
slot: "Frame-slot Str1 content within frame-inline"
};
inlineTemplate = '<require from="./frame-slot"></require>' +
'<div>${model.name}: ${model.description}</div>' +
'<br>' +
'<frame-slot>' +
'<div slot="str1">${model.slot}</div>' +
'</frame-slot>';
}
usage in app.html
<require from="./frame-inline"></require>
<frame-inline template.bind="inlineTemplate" model.bind="inlineModel"></frame-inline>
InnerHTML binding
It applies for Question 1 only.
You can bind content to an element's innerHTML property. I'm mentioning that, but you should use it with caution or not at all.
[Documentation]
Binding using the innerhtml attribute simply sets the element's innerHTML property. The markup does not pass through Aurelia's templating system. Binding expressions and require elements will not be evaluated.
<div innerHTML.bind="content | sanitizeHTML"></div>