Send template to grandchild component (nested template) - vue.js

There is this TablePackage component that we use in our project. I saw that to customize how values in a column render, one can pass template named with the specific column name (in this example status) and use slot prop row.
<!--Code Snippet 1-->
<!--In each row of status columns, we will see status buttons instead of plain status text-->
<TablePackage>
<template #status="{ row }">
<button>{{ row.status }}</button>
</template>
</TablePackage>
Now there are two more components, Parent and Dialogue.
Parent uses Dialogue, Dialogue uses TablePackage.
Parent component
<Dialogue title="dialogueTitle">
</Dialogue>
Dialogue component
{{ title }}
<TablePackage rows="rowList" columns="columnObj">
</TablePackage>
Now I want to be able to custom render values in a column as in 'Code Snippet 1' which means I want to render a button not a text in 'status' column. How can I achieve this from Parent component level? I tried nested template but it was not allowed. With the constraint that I can edit Parent and Dialogue components but not TablePackage, what's the recommended away to achieve this?

Related

Prevent list display filter from resetting v-list selection

Version info: Vuetify 2.6.3, Vue 2.6.14, Nuxt 2.15.8
I'm making a custom component that is supposed to be somewhat similar to v-autocomplete, except that it's rendered as bottom sheet. If user enters a display filter into v-text-field, the option list (v-list) is supposed to display only those options that match the filter.
In overall it works fine except one use case: let say the list has 5 items (aa, bb, cc, dd, ee) and user selected bb and cc from the list. Now, v-list-item-group's model selectedItems contains the 2 selected items bb and cc, perfect! However, when user enters b into display filter, the already selected item cc will be auto deleted from selectedItems. I can't tell if selectedItems change is caused by filter or by user selection. Is there a way to maintain the selection in model?
I'm considering a hack - if an item is selected, keep it in filteredChoices even if it does not match the filter. This behaviour is bearable but UX wise not as intuitive as the filter of v-autocomplete.
The simplified structure looks like the below:
<template>
<v-bottom-sheet scrollable>
<v-card>
<v-card-text>
<v-list>
<v-list-item-group
v-model="selectedItems"
:mandatory="!optional"
:multiple="multiple"
>
<v-list-item
v-for="item in filteredChoices"
:key="item.value"
:value="item"
>
</v-list-item>
</v-list-item-group>
</v-list>
</v-card-text>
<v-text-field
v-model="filterInput"
placeholder="filter choices..."
hide-details
></v-text-field>
</v-card>
</v-bottom-sheet>
</template>
<script>
...
filteredChoices() {
if (this.filterInput == null) {
return this.allItems
}
return this.allItems.filter((item) => {
return item.label
.toLocaleLowerCase()
.includes(String(this.filterInput).toLocaleLowerCase())
})
},
...
</script>
How I reach the solution:
I was quite new to front-end stuff. Previously, when I was learning how to implement v-model support for custom component, all web resources I came across say the same thing - bind inner component's value props to custom component's value props. However I just discovered that this is merely one of the ways rather than a must. With that new learning, more possibilities pop up in my mind and one of them lead me to below solution.
Solution:
Decouple the custom component value from bottomsheet's list
Bind the model of inner v-autocomplete to an array data, say internalValue. (this inner component is not included in question's template for simplification)
Bind the model of inner v-list-item-group (in bottomsheet) to a separate data, say bottomSheetSelections.
Update the custom component value based on user actions in bottomsheet
Add a watcher to bottomSheetSelections array:
if the array grows, it means the user has selected more item. We should push the additional item to internalValue.
if the array shrinks:
if the missing item is still there in filteredChoices, the removal is triggered by user de-selection. We should remove this item from internalValue.
else, we consider the removal is triggered by list filter. No action is needed.
Restore user selection in bottomsheet on clearing filter
Add a watcher for filteredChoices. Whenever the array grows, if the additional choice exist in internalValue, we should push it to bottomSheetSelections.
Summary
Strictly speaking, this doesn't solve the ask of question's title - the list selection in bottomsheet (bound to v-list-item-group) is still getting reset. However, at least we're able to restore it in bottomsheet on clearing filter.
More importantly, this solution achieved the objective mentioned in question details - retain the user selection value. We kept it in a separate data internalValue.
I haven't test this solution with long list of data. My guts feeling is that there could be more efficient solution. Please share it if you have a better solution.

Bootstrap Table row-clicked event

I'm using a <b-table> component in a Vue project and I need to define a function to be triggered when a row is clicked. I heard that there is a #row-clicked property in b-table so I tried to use it and I can put simple functions inside of it, I mean I can get console prints etc but nothing more complicated...
I want two functions to be executed when a row is clicked. The functions are now bound to a button inside of every row, but I dont want use it like this, I just need to click the related row to make them work. Here is the current version of the b-table component;
<b-table
:items="displayByType"
:fields="displayColumn"
responsive="sm"
:per-page="perPage"
:current-page="currentPage"
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc"
:filter="filter"
:filter-included-fields="filterOn"
#filtered="onFiltered"
hover
#row-clicked="test"
>
And here is the two functions that I want to be triggered when a row is clicked...
toggleRightBar(){
document.body.classList.toggle("right-bar-enabled");
}
changeRightBarContent(row){
this.$store.dispatch("rightbar/changeRightBarInfo", tableData[row]);
},
There is a JavaScript folder that I export column names and row data. I also a have Rightbar, when a row is clicked, I want to enable the rightbar so that the user can see detailed information about that specific row.
At last, here is the button that I use to make these two functions work;
<template #cell(detail)="row">
<button #click="toggleRightBar(); changeRightBarContent(row.index);" class="btn btn-outline-primary toggle-right">
<i class="bx bx-detail toggle-right"></i>
</button>
</template>
Basically, I dont want to use this button, instead, I want to click the row itself. Every row's rightbar information will be uniqe, my function does that. I just dont know how to use these 2 functions inside #row-clicked
Thanks in advance.
The row-clicked event will pass 3 parameters to your function.
The first being the item specific to the row that was clicked. The second will be the index of the row, and the third is a native click event.
This means you can use the arguments keyword to get the index and pass it to your function.
#row-clicked="toggleRightBar(); changeRightBarContent(arguments[1])".
Alternatively you can create a third method which will call the other two.
<b-table #row-clicked="onRowClicked" />
{
onRowClicked(item, index, event) {
this.toggleRightBar();
this.changeRightBarContent(index);
}
}
You can read more about the #row-clicked event on the docs component reference section.
https://bootstrap-vue.org/docs/components/table#comp-ref-b-table-events

Why some Vue custom component can do `<Radio v-model="today" value="monday" />`?

I was confused by this boostrap-vue's <Radio /> component:
It can do this
<b-form-radio v-model="selected" value="A">Option A</b-form-radio>
Why is this happening?
v-model is using value prop already, why can it still specify value prop?
Radio buttons differs a little bit from some other input elements such as text. Radio button values are static (not changing after set) while values on some other field types such as text are dynamic (able to change.
So without a value on a radio button the v-model would not know what data to set.
Read more on: https://v2.vuejs.org/v2/guide/forms.html#Value-Bindings

Using inner component in a loop in Shopware 6 does not persist the values uniquely for each looped component

I am using a v-for over a custom component and passing the item as a prop. But the issue is that each component instance in the loop takes the same item prop. For e.g in the 1st loop a component field has text "abc", then the second looped component also will have the same "abc" text. If I change the text in the 2nd one, it changes in the 1st component too. Is there a way to make the prop unique for each loop ?
For e.g this is the code which calls the inner component:
<template v-for="(businesscase, index) in businessCase.fields">
<custom-case-freetext-field #field-changed="updateFields"
:key="index"
#field-removed="removeFields"
:fields="businessCase.fields"
:index="index">
</custom-case-freetext-field>
</template>
and inside this component I have a basic form
<sw-field :label="$tc('rma.modules.case.freetext.nameLabel')"
:placeholder="$tc('rma.modules.case.freetext.nameLabel')"
required
v-model="fields[index].name">
</sw-field>
<sw-single-select
labelValue="label"
valueProperty="label"
:options="fieldTypes"
:label="$tc('rma.modules.case.freetext.fieldType')"
:placeholder="$tc('rma.modules.case.freetext.fieldType')"
v-model="fields[index].type"
#input="changeType"
required>
</sw-single-select>
If I do :value instead of v-model, the entered value disappears as soon as the element loses focus.
If I use v-model, the data stays there, but then both (or as many are there in the loop) component instances, have data binding between them, so it defeats the purpose of having a loop for multiple components. As seen in the screenshot, I am typing in the 2nd component, but it changes the text for the first one too.
In the above example I am sending the whole array as prop, but I have also tried with individual field element instead of fields
Your are not using the businesscase variable inside your components. And since every component always works on the upper scope property, they will all change the same. Use the innerscope property. If you have problems with reactivity, because you try to mutate props directly work with events emitting the key and the changed value to the upperscope component.

Vue remove child component with it state

I want delete child component. I use this.rows.splice(index, 1)
When i call this.rows.splice(index, 1) VueJs always remove last component from this.$children and save internal state in component.$data ;
Example is here
`https://jsfiddle.net/abratko/gc7h1r34/3/`
How fix it?
Vue associates each data item with each vnode according to the item's index by default. This results in existing Vue components being reused, but bound to different items, when re-rendering the list after an item was removed from the array.
This is why you should always bind key to a value which uniquely identifies that particular item. In your example, since each item is a unique string you can just bind to that:
<row-component v-for="(row, index) in rows" :key="row">
^^^^^^^^^^