VueJS 2 - Re-useable parent component that can accept different children - vuejs2

I have a parent component, SearchComponent:
<template>
<div>
<div class="relative pl-8 pr-10 rounded bg-white border focus-within:bg-white focus-within:ring-1">
<input v-focus #keyup.escape="clearSearch" #keyup="doSearch" v-model="searchTerm"
class="w-full ml-4 h-12 pl-1 text-gray-700 text-lg rounded-full border-0 bg-transparent focus:outline-none placeholder-gray-400"
placeholder="Search..." autocomplete="off">
</div>
<ul class="bg-white mt-4">
<quick-search-item v-for="item in searchResults" :key="item.id" :item-data="item.itemData">
</quick-search-item>
</ul>
</div>
</template>
This is responsible for receiving user input and getting results from an ajax call, handling errors etc. and generating the result list.
What I'd like to do is to make this generic so that instead of having a quick-search-item child component I can pass in different types of child component (like car-search-item, person-search-item etc.) depending on the context of where the user is in the app and what they're searching for
I've read a number of tutorials and I couldn't find quite what I'm trying to do. This may mean I'm approaching this in the wrong way - but if anyone could point me in the right direction, or has a better approach, I'd be very grateful.
Thanks,
Lenny.

I would try to make use of the <slot> element. Check out the documentation here
<parent-component>
<slot></slot>
</parent-component>
Hope this can put you on the right path.

Schalk Pretorius was quite right: slots are the answer to this, specifically scoped slots. I found the Vue docs a little confusing as it refers to getting data from the child component and I wanted to do it the other way around, so as an aide memoire for myself and to help anyone else here's what I did:
In my parent component I defined the slot like this:
<slot name="results" v-bind:items="searchResults">
</slot>
The v-bind binds searchResults (a data item in the parent component) to the value 'items'. 'items' then becomes available in the slot.
In my child component I have a simple property setup called items:
props: {
items: {type: Array},
},
Then to hook it all together in my Blade file I did this:
<search-component endpoint="{{ route('quick_search.index') }}">
<template v-slot:results="props">
<food-results :items="props.items">
</food-results>
</template>
</search-component>
This creates the search-component. Inside that -as I'm using named slots - we need a template and use v-slot to tell it which slot to use (results), then the ="props" exposes all the properties we've defined on that slot (in this case just one, 'items').
Inside the template we put our child component and then we can bind items to props.items which will be the searchResults in our parent component.
I'm happy to have this working and I can now create lots of different results components while reusing the search component - and at least I learnt something today!
Cheers,
Lenny.

Related

Vue get children of a component passed in a parent component

How do I get access the children that were passed into the children component in the parent component?
I understand the explanation may be confusing, what I want is this, similar to React "children" prop.
<div class="layout">
<item-component v-for="i in 10" :key="i">{{ i }}</item-component>
</div>
And in the item-component I want to access the i variable. Is this possible?
If I understand your question correctly, you want to use Slots.
In item-component you need to put in:
<slot></slot>
exactly where you want the value of i to be.
If you want to use i in scripts, just add property in item-component, for example named no and put it in for:
<div class="layout">
<item-component v-for="i in 10" :key="i" :no="i" ></item-component>
</div>

Passing slots through from Parent to Child Components

I have built a user-defined component (async-select) on top of another component (vue mutliselect) like this:
https://jsfiddle.net/2x7n4rL6/4/
Since the original vue-multiselect component offers a couple of slots, I don't want to loose the chance to use them. So my goal is to make these slots available from inside my custom component. In other words, I want to something like this:
https://jsfiddle.net/2x7n4rL6/3/
But that code oes not work.
However, if I add the slot to the child component itself, it works just fine (which you can see from the fact that options become red-colored).
https://jsfiddle.net/2x7n4rL6/1/
After surfing the web, I have come across this article, but it does not seem to work
Is there any way in VueJS to accomplish this ?
Slots can be confusing!
First, you need a template element to define the slot content:
<async-select :value="value" :options="options">
<template v-slot:option-tmpl="{ props }">
<div class="ui grid">
<div style="color: red">{{ props.option.name }}</div>
</div>
</template>
</async-select>
Then, in the parent component, you need a slot element. That slot element itself can be inside of another template element, so its contents can be put in a slot of its own parent.
<multiselect
label="name"
ref="multiselect"
v-model="localValue"
placeholder="My component"
:options="options"
:multiple="false"
:taggable="false">
<template slot="option" slot-scope="props">
<slot name="option-tmpl" :props="props"></slot>
</template>
</multiselect>
Working Fiddle: https://jsfiddle.net/thebluenile/ph0s1jda/

Pass a button with click handler via slot to recursive child component

I've got a page template with the following code part:
<nested-draggable v-bind:list="list" v-bind:selected="selected" v-bind:group="dragGroup">
<slot>
<v-icon v-on:click="$root.$emit('click', el)" small v-if="allowcreate" style="float: right">mdi-plus</v-icon>
</slot>
</nested-draggable>
the sub component ("nested-draggable.vue") for the recursion looks like this:
<template>
<ul class="tree">
<draggable
class="dragArea"
tag="li"
v-for="el in list"
v-bind:elementdata="el"
v-bind:key="el._id"
v-bind:list="list_empty"
v-bind:selected="selected"
v-bind:group="group"
v-on:add="add"
>
<span v-bind:class="{'selected' : el._id === selected._id}" v-on:click="elemClicked(el)">{{ el.title }}</span>
<slot></slot>
<!-- render children of the current iterated element -->
<nested-draggable
v-bind:list="el.children" v-bind:selected="selected" v-bind:group="group">
<!--<slot></slot>-->
</nested-draggable>
</draggable>
</ul>
</template>
so I'd like to have the click event from the button within the passed slot emited with the current iteration's var "el" when the "plus" button is clicked, but within the slot the "el" var that is used within the iteration at the nested-draggable component can not be accessed. Vue tells that there is no "el" reference when trying to emit. (Throwing this error: https://pastebin.com/8bNwMcDr)
So how can I access the recursive data within the passed slot? How do I have to define my slot when passing it?
The only solution I found is putting the button/event-link directly into the nested-draggable component (not as slot) but I think to be clean and write a nice separated component, this would not belong into the nested draggable component, but in its parent.
You don't need to pass your event from the template because you can get in your method anyways. This should help you out.

Aurelia component slotting in markup for components model

I am building an autocomplete component. The plan is that I can slot in some markup for what I know the component is going to bind to.
The idea is this could be any object rather than a simple display value and identifier.
I have this working using templates but I am wondering if there is a better approach.
So far it looks like this (options is hard coded for now within the components model):
// Usage:
<autocomplete>
<template replace-part="item">
//this is the content for each option within the component
<b>${option.lastName}<b/>, ${option.firstName}
</template>
</autocomplete>
//autocomplete
<template>
<input type="text" placeholder="Type 3 characters ...">
<ul>
<li repeat.for="option of options">
<template replaceable part="item"></template>
</li>
</ul>
</template>
I don't really like the templating boilerplate, slots are much nicer, is there any way to make slots work like this?
<autocomplete>
<li repeat.for="option of options">
${option.lastName}<b/>, ${option.firstName}
<li/>
</autocomplete>
//autocomplete
<template>
<input type="text" placeholder="Type 3 characters ...">
<ul>
<slot></slot>
</ul>
</template>
Slot in Aurelia is the emulation based on standard spec, which mean it doesn't work with repeat situation. repaceable was introduced to handle this scenario and I don't think we have any other options. Sometimes it feels weird but with a little documentation, probably you and your team will be fine. What you can do is for each replacement, what properties it can look for to get the item.

Attach multiple v-models to the loop of dynamic child components in vuejs 2

Recently I ran into a problem, where I had to display many different components inside a loop, but: each of them should share it's state with parent (kinda knockout.js style). I was digging thru the docs where clearly was pointed out, that Vue.js pass properties one way down to childs, and those can eventually speak with some events. Also, docs says that there can be only one v-model per component, so finally I came up with something like this:
<li :is="field.type" v-for="(field, i) in fields" :key="i" :title="field.title" v-on:title-change="title = $event" :somevalue="field.somevalue" v-on:somevalue-change="somevalue = $event"></li>
And so on... Yet, after fifth parameter I quickly realized that the code is basically messy. Is there some less messy way to attach multiple two-way data bindings to child components?
The solution happened to be a .sync method and proper naming of events. While sync has been deprecated and removed in vue.js 2, since 2.3 version has been rewritten and added again in some similar form. As in fact it's only a syntatic sugar, my component now looks more decent I believe.
<ol>
<li :is="field.type"
v-for="(field, i) in fields"
:key="field.id"
v-bind.sync="field"
v-on:remove="fields.splice(i, 1)"></li>
</ol>
Vue.component('bool', {
template: '\
<li>\
<input type="text" v-bind:value="title" #input="$emit(\'update:title\', $event.target.value)">\
<button v-bind:value="value" #click="$emit(\'update:value\', !$event.target.value)" :class="{active: value}">{{value}}</button>\
<input type="checkbox" value="1" v-bind:checked="istrue" #change="$emit(\'update:istrue\', $event.target.checked)">\
<button #click="$emit(\'remove\')">Remove</button>\
</li>\
',
data () {
return {}
},
props: ['title', 'value', 'availablevalues']
})