Cannot refresh vue bootstrap table when emitting data from child to parent - vue.js

I am using vue-bootstrap https://bootstrap-vue.js.org/docs/components/table/ and have implemented a slot for the header so I can have a show and hide button in each column header that contains a search box. The header is in a slot. In the fields data I have added a showFilter property that is boolean. So the idea was that it toggled between true and false. Now this all works when in the child you emit the data change to the parent it updates. However the table itself does not update until you trigger another action like try and sort a header, then it updates. I have tried the Force refreshing of table data mentioned in the above url but that does not seem to work, or I can't figure out how to use it as there is not an example.
Parent
<template>
<b-table
ref="table"
:items="filtered"
:fields="fields"
sort-icon-left
responsive="sm"
>
<template v-slot:head()="data">
<TableHeader :data="data" :filters="filters" />
</template>
</b-table>
</template>
data() {
return {
fields: [
{
key: "name",
sortable: true,
showFilters: true,
},
{ key: "age", sortable: true, showFilters: true },
],
}
}
Child
<template>
<div>
<div class="text-info">{{ data.label.toUpperCase() }}</div>
<div>
<button #click="setUpdate(data.field)">filters</button>
</div>
<input
v-show="data.field.showFilters"
v-model="filters[data.field.key]"
:placeholder="data.label"
#keyup="$emit('update:filters[data.field.key]')"
/>
</div>
</template>
methods: {
setUpdate(field) {
this._originalField = Object.assign({}, field);
field.showFilters = !field.showFilters;
this.$root.$emit("update:field.showFilters", "bv::refresh::table");
console.log(field.showFilters);
}
}

I solved this by using a bootstrap collapse button and dealing with it all in the child which makes more sense I think!
<b-collapse :id="'collapse' + data.field.key">
<b-card>
<!-- content -->
</b-card>
</b-collapse>

Related

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>

How to get the value of one input field in a "v-for" of multiple inputs

I have multiple text inputs generated in v-for directive which i have attached to one v-model variable as show below. I have a button by the respective inputs which prints the value of the current working input. Ultimately I want to extract value of the selected input without affecting the other inputs.
But apparent any change make in one input affect all the input. I super confused as to how I will achieve this. Any help will be much appreciated.
My attempted code is shown below.
<template>
<div id="app">
<div v-for="i in 5" :key="i">
<input v-model="text" type="text" :key="i" />
<button #click="printText">print</button> <span>{{ text }}</span>
</div>
</div>
</template>
<script>
export default {
data() {
return {
text: "",
};
},
methods: {
printText() {
console.log(this.text);
},
},
};
</script>
Take an array instead of simple variable when you use v-model in v-for
And on click pass the index with function call
Try to use
<template>
<div id="app">
<div v-for="i in 5" :key="i">
<input v-model="text[i]" type="text"/>
<button #click="printText(i)">print</button> <span>{{ text[i] }}</span>
</div>
</div>
</template>
<script>
export default {
data() {
return {
text: [],
};
},
methods: {
printText(index) {
console.log(this.text[index]);
},
},
};
</script>

How do I fit the value of one v-model into another v-model?

In this example, I'm trying to fit the value from div id="message" into textarea using the Vue v-model construct, but this not work
<template>
<div>
<textarea v-model="text"></textarea>
</div>
<div>
<div id="message" v-model="text2">{{ comment.message }}</div>
<button #click="update(text2);">
Edit
</button>
</div>
</template>
<script>
export default {
data() {
return {
text: [],
text2: null
}
},
methods: {
/* not work */
update(text2) {
this.text = text2;
}
}
<script>
How do I make sure that when I click on the "edit" button, the value of v-model="text2" insert into v-model="text" ?
You cannot use v-model on a <div> because it isn't an input element.
It seems what you want to do is set text to the comment message when you click the edit button so that it can be edited by the textarea. All you have to do is pass comment.message as the argument:
<button #click="update(comment.message)">
A couple of other things:
You cannot have multiple root elements in your template (you have two root <div> elements). You can just wrap everything in a single <div>.
text has initial value [] which isn't compatible with a textarea's v-model; did you mean ''?

Update component data from the template

Not too sure what is wrong here, it seems fine to me! I'm simply trying to update the data property display to true when I click the input within my component.
I have passed the data to the slot scope, so can't see that being the issue. It just simply won't update, using a function to toggle it works, however not what I really want to do, seems pointless.
<time-select>
<div slot-scope="{ time }" class="is-inline-block">
<label for="businessHoursTimeFrom"><i class="fas fa-clock"></i> From</label>
<input type="text" name="businessHoursTimeFrom[]" v-model="time" v-on:click="display = true">
</div>
</time-select>
The code behind:
<template>
<div>
<p>{{ display }}</p>
<slot :time="time" :display="display"></slot>
<div class="picker" v-if="display">
<p>Test</p>
</div>
</div>
</template>
<script>
export default {
props: [],
data: function () {
return {
time: '',
display: false
}
},
mounted() {
},
methods: {
}
}
</script>

Custom events in vue.js components

The scenario
Since I have a more complex checkbox I encapsulated it inside a separate component. Inside the template of the parent component I prefer using v-model to bind the value to a variable.
My approach is based on this description (https://v2.vuejs.org/v2/guide/components-custom-events.html#Customizing-Component-v-model) taken from the official documentation.
The problem
When I have two custom-checkbox-elements and I select the last one, the first one inside the DOM will be selected. So it seems, that the first one is consuming the event.
The code
The following snippet illustrates the checkbox component.
<template>
<div class="checkbox-part">
<input class="checkbox-part-input" type="checkbox" name="cb" id="cb"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
<label class="checkbox-part-label" for="cb"
:class="{ 'checkbox-part-label--checked': checked }"
>
<slot name="label"></slot>
</label>
<!-- removed for brevetiy -->
</div>
</template>
<script>
export default {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: {
type: Boolean,
}
}
}
</script>
How can I achieve, that the selected checkbox is updated?
You have name of input hardcoded in component. So you probably render two input with same name ("cb" in this case)
I think that you can pass input name and id as props.
This should solve your problem.
As already mentioned in the comments the problem was caused by hard-coded values for id and name inside the CheckboxPart component.
I've added two properties for name and value and inject them into the component as well.
The snippet
<!-- checkbox -->
<template>
<div class="checkbox-part">
<input class="checkbox-part-input" type="checkbox"
:name="name"
:id="id"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
<label class="checkbox-part-label"
:for="name"
:class="{ 'checkbox-part-label--checked': checked }"
>
<slot name="label"></slot>
</label>
<!-- removed for brevity -->
</div>
</template>
<script>
export default {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: {
type: Boolean,
},
name: {
type: String,
required: true
},
id: {
type: String,
required: true
}
}
}
</script>
<!--- parent component using the one -->
<template>
<!-- removed for brevity -->
<!-- ... -->
<div class="expandable-category-part-social-section">
<checkbox-part
v-model="isSocialIntegrationEnabled"
:id="title + 'social-media'"
:name="title + 'social-media'"
>
<template slot="label">
<div class="checkbox-part-label-text">Final text comes here...</div>
</template>
</checkbox-part>
</div>
<!-- ... -->
</template>