How to push an object to another Vue component - vue.js

I'm using Element UI library with Vuejs framework (configured using Vue CLI).
I currently have a nested input form that is taking information and pushing it to an object array inside of a parent component, but I'm having a lot of trouble sharing this information to a child component.
I believe using props is the correct approach, but I'm not sure I'm declaring the props correctly. I also have the parent / child set up with vue-router.
Form in parent component...
<div>
<el-dialog title="Create a new space to huddle" :visible.sync="createHuddleVisible" width="1000px" height="200%">
<el-form :huddle="huddle" :index="index">
<el-form-item label="Huddle Headline" :label-width="formLabelWidth">
<el-input v-model="newHuddle" autocomplete="off" placeholder="What should we call this meeting?"></el-input>
</el-form-item>
<el-form-item label="Goal" :label-width="formLabelWidth" :autosize="{ minRows: 1, maxRows: 2 }">
<el-input type="textarea" v-model="newGoal" placeholder="Use this text box to quickly establish the goal of this huddle.">
</el-input>
</el-form-item>
<el-form-item label="Body" :label-width="formLabelWidth">
<el-input style="white-space: pre-line;" type="textarea" :autosize="{ minRows: 10, maxRows: 15 }" v-model="newBody"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button #click="createHuddleVisible = false">Cancel</el-button>
<el-button type="primary" #click="addHuddle">Confirm</el-button>
</span>
</el-dialog>
</div>
The 'addHuddle' method successfully takes all strings and creates new objects.
I would like to use the data captured here and displayed in a child component.
For now, I've just been trying to print one of the object values onto a card as a test with no luck. Blank card.
<template>
<el-card class="huddle-box-card" shadow="always">
{{ huddle[0].goal }}
</el-card>
</template>
<script>
export default {
name: 'HuddleSpaceOne',
props: {
huddle: {
type: Object
},
index: {
type: Number
},
}
}
</script>

Related

How to access slot props from the component used inside the slot?

so everything i can find about scoped slots and passing props dont work for my specific situation:
i have following component order:
Home/List/ListItem
now i desided to replace the ListItem with a slot and because i use the List in a other Component too, but in there i need the ListOptionsItem.
in my home component i did this:
<list
class="mapkeyList"
:content="displayList"
:filterbar="true"
#handleSelection="addSelection"
#delete="deleteElement"
#editItem="editItem"
header="Mapkeys"
:key="mapkeyListKey"
>
<list-item>
</list-item>
</list>
in my List component i have this:
<template>
<div>
<h2 v-if="header">{{header}}</h2>
<div class="listContainer" v-if="showedContent.length > 0">
<div v-for=" (item, index) in showedContent" :key="index">
<slot
:item="item"
:index="index"
:dragable="dragableItems"
#auswahl="auswahlHandle"
#deleteElement="deleteElement"
#editItem="editItem"
:dontShowButtons="dontShowButtons"
#dragStart="handleOverDragStart"
:dragItem="dragItem"
#position="$emit('emitPosition',item)"
:deaktivierbar="deaktivierbar"
>
</slot >
</div>
finaly the listItem and the listOptionsItem need to access this props in the slot:
listItem:
<template>
<div class= "flexSpaceBetween" #click="$emit('auswahl',item)">
<div class="textFett">
{{item[0]}}
</div>
<div>
{{item[1]}}
</div>
</div>
i dont want to write all the neccessarry code in the home component because the listOptionsItem does need more informations and more space to write code.
my goal was it to define in the Home component that i want the list to use the listItem component and in the Options component the list should use the listItemOptions component. in the future there could be added new listItem versions.
Any component used inside scoped slot has no implicit access to the slot props. To make them available inside the component, you must pass it down to that component as props explicitly...
<list
class="mapkeyList"
:content="displayList"
:key="mapkeyListKey">
<template v-slot:default="{ item }">
<list-item :item="item">
</list-item>
</template>
</list>
If you have a lot of props/events you want to pass along, the ability of both v-bind and v-on to take an object as an argument is very useful because you can pass all the data and event handlers at the same time:
// List component
<template>
<div>
<h2 v-if="header">{{header}}</h2>
<div class="listContainer" v-if="showedContent.length > 0">
<div v-for=" (item, index) in showedContent" :key="index">
<slot :props="slotProps" :on="slotEventsHandlers"
</slot >
</div>
</div>
</div>
</template>
<script>
export default {
computed: {
slotProps() {
return {
item: this.item,
dragable: this.dragableItems
}
},
slotEventsHandlers() {
return {
deleteElement: this.deleteElement,
dragStart: this.handleOverDragStart
}
}
}
}
</script>
And use it in parent:
<list
class="mapkeyList"
:content="displayList"
:key="mapkeyListKey">
<template v-slot:default="{ props, on }">
<list-item v-bind="props" v-on="on">
</list-item>
</template>
</list>

Executing js on slot

I'm a beginner in web development and I'm trying to help out friends restarting an old game. I'm in charge of the tooltip component but I hit a wall...
There are many Vue components and in a lot of them I want to call a child component named Tooltip, I'm using vue-tippy for easy configuration. This is the component:
<template>
<tippy class="tippy-tooltip">
<slot name='tooltip-trigger'></slot>
<template #content>
<slot name='tooltip-content'>
</slot>
</template>
</tippy>
</template>
<script>
import { formatText } from "#/utils/formatText";
export default {
name: "Tooltip",
methods:{
formatContent(value) {
if (! value) return '';
return formatText(value.toString());
}
},
}
</script>
In one of the other components I try to use the tooltip:
<template>
<a class="action-button" href="#">
<Tooltip>
<template #tooltip-trigger>
<span v-if="action.movementPointCost > 0">{{ action.movementPointCost }}<img src="#/assets/images/pm.png" alt="mp"></span>
<span v-else-if="action.actionPointCost > 0">{{ action.actionPointCost }}<img src="#/assets/images/pa.png" alt="ap"></span>
<span v-if="action.canExecute">{{ action.name }}</span>
<span v-else><s>{{ action.name }}</s></span>
<span v-if="action.successRate < 100" class="success-rate"> ({{ action.successRate }}%)</span>
</template>
<template #tooltip-content>
<h1>{{action.name}}</h1>
<p>{{action.description}}</p>
</template>
</Tooltip>
</a>
</template>
<script>
import Tooltip from "#/components/Utils/ToolTip";
export default {
props: {
action: Object
},
components: {Tooltip}
};
</script>
From here everything is fine, the tooltip is correctly displayed with the proper content.
The thing is, the text in the {{ named.description }} needs to be formatted with the formatContent content. I know I can use the props, the components would look like that:
Tooltip.vue:
<template>
<tippy class="tippy-tooltip">
<slot name='tooltip-trigger'></slot>
<template #content>
<h1 v-html="formatContent(title)" />
<p v-html="formatContent(content)"/>
</template>
</tippy>
</template>
<script>
import { formatText } from "#/utils/formatText";
export default {
name: "Tooltip",
methods:{
formatContent(value) {
if (! value) return '';
return formatText(value.toString());
}
},
props: {
title: {
type: String,
required: true
},
content: {
type: Array,
required: true
}
}
}
</script>
Parent.vue:
<template>
<a class="action-button" href="#">
<Tooltip :title="action.name" :content="action.description">
<template v-slot:tooltip-trigger>
<span v-if="action.movementPointCost > 0">{{ action.movementPointCost }}<img src="#/assets/images/pm.png" alt="mp"></span>
<span v-else-if="action.actionPointCost > 0">{{ action.actionPointCost }}<img src="#/assets/images/pa.png" alt="ap"></span>
<span v-if="action.canExecute">{{ action.name }}</span>
<span v-else><s>{{ action.name }}</s></span>
<span v-if="action.successRate < 100" class="success-rate"> ({{ action.successRate }}%)</span>
</template>
</Tooltip>
</a>
</template>
<script>
import Tooltip from "#/components/Utils/ToolTip";
export default {
props: {
action: Object
},
components: {Tooltip}
};
</script>
But I need to use a slot in the tooltip component because we'll have some "extensive" lists with v-for.
Is there a way to pass the data from a slot into a JS function?
If I understand you correctly, you're looking for scoped slots here.
These will allow you to pass information (including methods) from child components (the components with <slot> elements) back to the parents (the component(s) filling those slots), allowing parents to use chosen information directly in the slotted-in content.
In this case, we can give parents access to formatContent(), which will allow them to pass in content that uses it directly. This allows us to keep the flexibility of slots, with the data passing of props.
To add this to your example, we add some "scope" to your content slot in Tooltip.vue. This just means we one or more attributes to your <slot> element, in this case, formatContent:
<!-- Tooltip.vue -->
<template>
<tippy class="tippy-tooltip">
<slot name='tooltip-trigger'></slot>
<template #content>
<!-- Attributes we add or bind to this slot (eg. formatContent) -->
<!-- become available to components using the slot -->
<slot name='tooltip-content' :formatContent="formatContent"></slot>
</template>
</tippy>
</template>
<script>
import { formatText } from "#/utils/formatText";
export default {
name: "Tooltip",
methods: {
formatContent(value) {
// Rewrote as a ternary, but keep what you're comfortable with
return !value ? '' : formatText(value.toString());
}
},
}
</script>
Now that we've added some scope to the slot, parents filling the slot with content can use it by invoking a slot's "scope":
<!-- Parent.vue -->
<template>
<a class="action-button" href="#">
<Tooltip>
. . .
<template #tooltip-content="{ formatContent }">
<!-- Elements in this slot now have access to 'formatContent' -->
<h1>{{ formatContent(action.name) }}</h1>
<p>{{ formatContent(action.description) }}</p>
</template>
</Tooltip>
</a>
</template>
. . .
Sidenote: I prefer to use the destructured syntax for slot scope, because I feel it's clearer, and you only have to expose what you're actually using:
<template #tooltip-content="{ formatContent }">
But you can also use a variable name here if your prefer, which will become an object which has all your slot content as properties. Eg.:
<template #tooltip-content="slotProps">
<!-- 'formatContent' is now a property of 'slotProps' -->
<h1>{{ slotProps.formatContent(action.name) }}</h1>
<p>{{ slotProps.formatContent(action.description) }}</p>
</template>
If you still need the v-html rendering, you can still do that in the slot:
<template #tooltip-content="{ formatContent }">
<h1 v-html="formatContent(title)" />
<p v-html="formatContent(content)"/>
</template>

Collapsing vertical menu in element.io

I try to create a layout with element.io which has a collapsing sidebar menu.
It works quite well, the only problem is that I do not gain anything since the width of the menu and the content part are fixed.
My code looks like this:
<template>
<el-row>
<el-col :span="4">
<template>
<el-menu :router="true"
:default-active="$route.path"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
:collapse="isCollapse"
class="el-menu-vertical-demo"
>
<template v-for="rule in routes">
<el-menu-item :index="rule.path">
<i :class="rule.icon"></i>
<span slot="title">{{ rule.name }}</span>
</el-menu-item>
</template>
</el-menu>
</template>
</el-col>
<el-col :span="20">
<el-row>
<el-radio-group v-model="isCollapse" style="margin-bottom: 20px;">
<el-radio-button :label="true" :disabled="isCollapse" border>
<i class="fas fa-caret-left"></i>
</el-radio-button>
<el-radio-button :label="false" :disabled="!isCollapse" border>
<i class="fas fa-caret-right"></i>
</el-radio-button>
</el-radio-group>
</el-row>
<el-row>
<router-view></router-view>
</el-row>
</el-col>
</el-row>
</template>
<script>
export default {
data() {
return {
routes: this.$router.options.routes,
activeLink: null,
isCollapse: false
};
},
mounted: function () {
},
methods: {
}
};
</script>
How can I fix it so the content block will occupy 100% of the available width?
Ok, I found a simple solution.
First, I moved to element-io elements. The menu is now embedded inside el-aside tag, while the main part is embedded inside an el-container tag.
Second, I added a dynamic css class to the el-aside tag:
<el-aside v-bind:class="[isCollapse ? 'menu-collapsed' : 'menu-expanded']">
Now, if you don't want to mess around with transitions, simply add to the el-menu component :collapse-transition="false" and that's it!

v-model and scoped slots not working?

I have a component:
<slot name="test" :name="name">
<input type="text" v-model="name">
</slot>
The input is bound to name in data.
When I use the slot in the parent:
<div slot="test" slot-scope="props">
<input type="text" v-model="props.name">
</div>
Data does not update on the child. It's not linked - why?
What you are seeing is actually the parent's default <input>. So you understand what I mean, add some text to both, like:
<slot name="test" :name="name">
Default: <input type="text" v-model="name">
</slot>
<div slot="test" slot-scope="props">
Actual: <input type="text" v-model="props.name">
</div>
You'll see that what appears is the default.
Now, that happens because, it seems like a bug, when the slot prop has the same name as the parent's, the slot does not work.
Workaround: rename the slot prop.
In the example below, I renamed it from name to namex. Notice the v-model in the default remains the same name because anything in the template refers to the props of that template (in other words, slot props, e.g. namex, will never be available in the parent default slot).
<slot name="test" :namex="name">
Default: <input type="text" v-model="name">
</slot>
<div slot="test" slot-scope="props">
Actual: <input type="text" v-model="props.namex">
</div>
to use v-model in scoped slots, the value of v-model must be one level deeper:
Vue.component('render-props', {
data: () => ({message: 'hello', obj: {msg: 'obj_msg'}}),
template: `<div>
<slot name="a" :message="message">
default: {{message}}
<input v-model="message"/>
</slot>
<slot name="b" :obj="obj">
default: {{obj.msg}}
<input v-model="obj.msg"/>
</slot>
</div>`
});
new Vue({
el: "#root",
template: `<div>
<render-props>
<template v-slot:a="props">
actual: {{props.message}}
<input v-model="props.message"/>
</template>
<template v-slot:b="props">
actual: {{props.obj.msg}}
<input v-model="props.obj.msg"/>
</template>
</render-props>
<cus_2 />
</div>`
});
you are not supposed to modify the data you pass to a slot, pretty
much like a prop. You should pass a method instead to change tha
value. You can always pass an object and modify a property (like a
prop) but it's also not recommended
-from https://github.com/vuejs/vue/issues/9726
So I imagine something like
<template>
<slot v-bind:value="value"></slot>
</template>
<script>
export default {
name: 'FooBar',
data() {
value: '',
},
methods: {
updateValue(e) {
this.value = e.target.value;
}
},
};
</script>
Then when using the component <FooBar> instead of using v-model, you can use the passed scoped slot props, the method (updateValue), and actual prop that will be updated, value.
<FooBar v-slot="slotProps">
<input type="text" :value="slotProps.value" #input="slotProps.updateValue" />
</Foobar>

What is the best way to propagate event to few component instance in Vue Js?

I need to implement folowing behavior->
I have the list with simple elements (just text). In elements i put the button "edit", that open edit element form on same place.
I have "add" button on top of list, that open same form.
I'd like to: when user click on "add" or "edit" another opened forms close. Also forms closed after sending request to server..
Sorry for my English! )
It looks like that:
List and add element form:
<template>
<el-row>
<el-col :span="24">
<el-button type="text" #click="show_add_form = true">Добавить</el-button>
<edit-dict-element-form
v-bind:dict="dictName"
v-if="show_add_form"
v-on:closeAddForm="show_add_form = false">
</edit-dict-element-form>
</el-col>
<el-col :span="24"
v-for="(element, index) in dict"
v-bind:key="element.id">
<dict-element
v-bind:element="element"
v-bind:index="index"
v-bind:dict="dictName">
</dict-element>
</el-col>
</el-row>
Element component:
<template>
<div>
<el-card v-if="!show_edit_form">
{{element.name}}
<el-button class="edit-button" type="text" #click="show_edit_form = true">Изменить</el-button>
</el-card>
<edit-dict-element-form
v-bind:dict="dict"
v-bind:edit_element="element"
v-if="show_edit_form"
v-on:closeAddForm="show_edit_form = false">
</edit-dict-element-form>
</div>
</template>
Add\Edit element form:
<template>
<el-card>
<el-form :rules="rules" :model="element" label-position="top" ref="AddDictElement">
<el-form-item label="Введите значение" prop="name">
<el-input v-model="element.name"></el-input>
</el-form-item>
</el-form>
<el-button type="danger" #click="$emit('closeAddForm')">Закрыть</el-button>
<el-button type="success" #click="addDictElement">Сохранить</el-button>
</el-card>
</template>
I'd like to only one add\edit form was opened at same time.
You could use an 'event bus' system for communicating between components (non parent-child communication).
var bus = new Vue()
// in component A's method
bus.$emit('id-selected', 1)
// in component B's created hook
bus.$on('id-selected', function (id) {
// ...
})
Please see:
https://v2.vuejs.org/v2/guide/components.html#Non-Parent-Child-Communication