How to use template instead of compose - aurelia

I have a TreeView based on Aurelia binding and it works fine.
There is one component called TreeView with the usual view and viewmodel.
I then have another view TreeViewNode.html which the TreeView uses recursively.
<template>
<div content-id="treeview-root">
<compose view="./tree-view-node.html"></compose>
<compose repeat.for="item of root.items" view="./tree-view-node.html"></compose>
</div>
</template>
This all works. However, I would like to turn the TreeViewNode into a custom element instead of just using compose which inherits the parent view-model.
The issue with turning it into a custom element is that it loses the TreeView view-model which contains all the methods to process events such as drag and drop, and item selection.

You can use bindables to pass in the parts of the view-model needed by the custom element.
tree-view-node.html:
<template bindable="viewModelParts">
<div click.trigger="viewModelParts.itemSelected()">Click here</div>
</template>
consumer.html:
<template>
<require "./tree-view-node.html></require>
<div content-id="tree view-root">
<tree-view-node repeat.for="item of root.items" view-model-parts.one-time="theViewModelParts"></tree-view-node>
</div>
</template>

Related

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.

control over inherited attributes by vue component

Is there a way to have control over attributes provided through the component tag?
For example:
<my-component class="myClass" style="myStyle"></my-component>
My component:
<template>
<div>
<div>
</div>
<div>
</div>
</div>
</template>
At render Vue applies given attributes on the root:
<div class="myClass" style="myStyle">
<div>
</div>
<div>
</div>
</div>
I want to control where those attributes are applied like so:
<div>
<div>
</div>
<div class="myClass" style="myStyle">
</div>
</div>
#Boussadjra Brahim answer is definitely one way to handle it, however this will require you to pass in all of the class attributes you want everytime you define the component.
This question is answered in this SO post already as well.How to style a nested component from its parent component in Vuejs?
If you want a bit more flexibility I would suggested using interpolation and properties as below. This will let you define some default classes and pass in whatever else in addition.
<app-header :headerclass="parent-header-class"> </app-header>
Inside of your child component, you can use these properties and v-bind the class inside the HTML, as shown in the example below:
<template>
<div :class=`${headerClass} internal-class-example button`> </div>
</template>
Note: This does not allow you to use any scoped parent CSS to pass to the child. The classes you pass down must be global. Otherwise, the child component will not know what it is.

How to dynamically insert a template into another template

New to VueJS... I have a component that I want to pass other components into based on the selection made in a dropdown. I have a main template that will always be rendered on the screen, part of which has a dropdown. When I make a selection in that dropdown I want to have a div inside that main component with an ID (or some other identifying property) and push another template inside of it. I'm thinking that a slot does the opposite of what I want..
Original Template:
<div class="search-field-with-label-container">
<el-select v-model="serviceType">
<el-option
v-for="serviceType in serviceTypes"
:key="serviceType.id"
:value="serviceType"
>{{ serviceType }}</el-option>
</el-select>
<div id="thisIsWhereIWantMyOtherTemplateToRender"
</div>
Second template:
<template>
<h1>this is the other template</h1>
</template>
You can use dynamic components with the keep-alive tag.
<keep-alive>
<component v-bind:is="selectedComponent"></component>
</keep-alive>
Documentation is here.

ViewModel-less compose with model.bind in aurelia

I am having a structure where the main view composes a partial view that composes another partial view in a repeater.
Example:
main view
<template>
<h1>${factory.name}</h1>
<div class="column">
<compose view="./cars.html"></compose>
</div>
</template>
cars view
<template repeat.for="car of factory.cars">
<compose view="./specifications.html model.bind="{test: 'abc}"></compose>
</template>
specifications view
<template repeat.for="car of factory.cars">
<h1>${$parent.$parent.factory.name} - ${car.name}</h1>
${test}
</template>
The problem I am facing is that the model.bind in compose doesn't work. I tried it with the test above, but what I'd actually want to pass there is $parent.$parent.factory so I can output $parent.$parent.factory.name in the specifications view.
(I know I can print it like this, but the scenario gets way more complicated so
the binding is necessary)
Worth to mention that both specifications and cars view are viewmodel-less. Only themain view has a viewmodel where factory and cars are coming from.
According to this page, what I am trying to do is possible, but I can't wrap my head about what I'm doing wrong.
When composing with just an html file, the view-model for the referenced html file is the same as where the compose element is placed. In other words, it inherits the view-model of the parent. So you don't need to supply the model.
main view
<template>
<h1>${factory.name}</h1>
<div>
<compose view="./cars.html"></compose>
</div>
</template>
cars.html
<template>
<div repeat.for="car of factory.cars">
<compose view="./specifications.html"></compose>
</div>
</template>
specifications.html
<template>
<h1>${factory.name} - ${car.name}</h1>
</template>
Take a look at this GistRun example.
What #Jeff G said is correct, but for my specific use-case what solved my issue was creating a simple view-model that I could use for all compositions. It's looking like:
export class Main {
public parentFactory;
public activate(data) {
this.parentFactory= data;
}
}
And in the view
<compose
view="./car.html"
view-model="../view-models/main"
model.bind="$parent.$parent.factory">
</compose>