Expand all Bootstrap-Vue b-table rows - vue.js

I have a b-table which looks something like this:
<b-table :fields="fields" :items="tableRows">
<template #head(expand_all+)="data">
//would like to make this a button to expand all row-details
<div class="text-right">{{data.label}}</div>
</template>
...
<template #cell(expand_all+)="data">
<div class="expand-arrow-container":class="{ 'expanded': data.detailsShowing }" #click="data.toggleDetails">
<font-awesome-icon icon="chevron-right" />
</div>
</template>
...
<template #row-details="row">
//display data here
</template>
</b-table>
Now, all of this works fine and and my row details expand/contract as I would expect (see b-table row-details for more information).
The problem arises in that I want to be able to click one button and expand/contract all the row-details. My plan is to change template #head(expand_all+)="data" into a button which can be clicked to accomplish this. However, I do not know how to do this. I have searched and read the b-table documentation and have not found any way of accomplishing what I want. I've even taken a look at the table object to see if it had any references to its rows and I didn't see anything.
Do any of you know a way to accomplish this? Getting a reference to the rows would make this a simple problem, but as I mentioned I didn't see anything. I am also not looking to swap out the b-table with a bunch of b-collapse elements since I have put a lot of work into the table.

As described on the docs, you can set a _showDetails property on your items passed to the table. Which if set to true will expand that row's details. This means you could loop through all your items and set that property to true to expand all rows. And do the same with false to collapse them.
If the record has its _showDetails property set to true, and a row-details scoped slot exists, a new row will be shown just below the item, with the rendered contents of the row-details scoped slot.
Here's two example methods you could use.
We're using $set because we're adding a new property to an already existing object. You can read more about why here
expandAll() {
for(const item of this.tableRows) {
this.$set(item, '_showDetails', true)
}
},
collapseAll() {
for(const item of this.tableRows) {
this.$set(item, '_showDetails', false)
}
}

Related

Watching a dynamically rendered field in Laravel Nova Vue component

In Laravel Nova, action modals are rendered in Vue by retrieving a list of fields to display through a dynamic component. I have replaced the action modal with own custom component, but am struggling to achieve the effect I want without also extending the entire set of components for rendering form fields.
I have my CustomResourceIndex.vue, containing a conditionally loaded (via v-if) ActionModal.vue, in which the form fields are rendered like so:
<div class="action" v-for="field in action.fields" :key="field.attribute">
<component
:is="'form-' + field.component"
:resource-name="resourceName"
:field="field"
/>
</div>
where the actual form field component is chosen based on the field.component value.
Those form fields (which I ideally do not want to have to extend and edit) are rendered like so:
<template>
<default-field :field="field" :errors="errors">
<template slot="field">
<input
class="w-full form-control form-input form-input-bordered"
:id="field.attribute"
:dusk="field.attribute"
v-model="value"
v-bind="extraAttributes"
:disabled="isReadonly"
/>
</template>
</default-field>
</template>
I would like to watch the value of specific fields and run methods when they change. Unfortunately due to a lack of ref attribute on the input elements or access to the value that the form element is bound to, I'm not sure how I can accomplish that from within ActionModal.vue.
I am hoping that because I have access to the ids still, there is some potential way for me to emulate this behavior.
Many resources I've found on my own have told me that anything with an ID is accessible via this.$refs but that does not seem to be true. I can only see elements that have an explicitly declared ref attribute in this.$refs, so I am not sure if I've misunderstood something there.
I would recommend looking into VueJS watch property.
You can listen to function calls, value changes etc.
watch: {
'field.component': function(newVal, oldVal) {
console.log('value changed from ' + oldVal + ' to ' + newVal);
},
},
Are those components triggering events? Try looking into the events tab of the Vue DevTools to see if some events are triggered from the default-field component when you update the value.
My guess is that you could write something like:
<div class="action" v-for="field in action.fields" :key="field.attribute">
<component
:is="'form-' + field.component"
:resource-name="resourceName"
:field="field"
#input="doSomething($event)"
/>
</div>
The $event value being the new value of the field.
Hit me on the comments if you have more info on the behavior of the default form fields (Are their complete code accessible somewhere?).

Add row styling when data populated, remove on mouse hover

I'm currently using Bootstrap Vue's b-table to display my data from the database. I have SignalR implemented, where it will automatically update/refresh the table with the new data received from the server.
I would like to add some sort of highlight css styling or row variant
when new data gets populated into the table, and then remove the styling from the row on mouseover/hover. Currently, I can receive events in the console when a row gets hovered.
How am I able to achieve this functionality using Vue.js?
b-table
<b-table
...
:items="items"
:fields="fields"
#row-hovered="myRowHoverHandler"
...
> ... </b-table>
script tag
props: {
...
items: Array,
...
}
methods: {
myRowHoverHandler(record, index) {
console.log(this.items[index].id);
},
...
}
When the item gets updated, you can simply set the _rowVariant on the item that got updated, and then on hover remove it from the item again as shown in the codepen below.
Remember to use $set and $delete to keep Vue's Reactivity happy
https://codepen.io/Hiws/pen/WqKqdG

bootstrap-vue editable table revert value if error

I am using the vue js and bootstrap-vue to make an editable table, the user is allowed to make changes on the table with v-on:change assisting to axios update the database. The restriction is that the Languages are Unique and cannot be an empty string, I cannot seem to be able to revert the value if the user makes a mistake. What is the recommended approach to this? Any help is greatly appreciated.
I have tried to "refresh" the table by doing:
this.languages = this.languages;
Does not seem to refresh the value in the table.
a section of vue component on the table:
<b-table striped hover :items="filtered" :fields="fields">
<template slot="name" slot-scope="data">
<b-form-input type="text" :value="data.item.name" v-on:change="updateLanguage($event,data.item.id)"></b-form-input>
</template>
</b-table>
in the methods of vue export default:
updateLanguage(e,id) {
if(e.trim()){
const find_langauge = this.languages.find(language=>{
return language.name.toLowerCase() === e.toLowerCase();
});
find_langauge ? this.languages = this.languages : console.log('no match');
} else {
console.log('cannot be empty');
this.languages = this.languages;
}
}
in computed:
filtered() {
return this.search ? this.languages.filter(language=>
language.name.toLowerCase().includes(this.search.toLowerCase())) : this.languages;
}
I cannot find any solution to refresh the :value, so I ended up with force re-render of the component. Not ideal, but at least the entire component got refreshed. Will appreciate any better way to approach this instead of re-rendering.
Vue doesn't retain "initial" values for inputs, as it relies on your data modal as the source of truth.
You would need to store the initial value in data, and when detecting a reset, copy that initial value back to the v-model associated with the input.
Also, if you have inputs in multiple rows, it is a good idea to set a unique key on the input, and or if you have a unique key field (where the value is unique per item row), set the primary-key prop on b-table to have it generate a unique Vue key per TR element.

Vue-Bootstrap: how to trigger sorting of one b-table to trigger sorting of the another b-table?

I'm using VueBoostrap <b-table> components, in a combination with sorting routine applied. In my project I have some more complex sorting routine, but for this dummy example, I will use the default one.
When a sorting is applied for a b-table, the table is simply sorted based on the clicked field in the table header. But what I need to achieve is to split the table header from the table content (since I want to place the content in a scrollable div later, while the header to remain static at the top - as user will be scrolling).
The complete code is given on this link (check componets/TableTest.vue), where I have three <b-table> components. First one is just a dummy example, and the next two are identical as the first one, but for one of them the header is hidden and for the other the body is hidden.
What I want to achieve is:
If you take a close look at the docs (https://bootstrap-vue.js.org/docs/components/table) you can see that there are certain events emitted by the <b-table> component.
One of them is sort-changed. So if you listen for that on your header only component and then set a sortBy property that you pass into the body only component you're all set.
//header only
<b-table ... #sort-changed="sortChanged">
// body only
<b-table :sort-by="sortBy" ...>
sortChanged(e) {
this.sortBy = e.sortBy
}
Full example here: https://codesandbox.io/s/y30x78oz81
As I understand it, the OP is asking :
"How do I force a <b-table> component to (re)sort itself without requiring the user to click on that <b-table> component?"
My answer (in their case):
Detect the click event on the "visible header" mentioned
In the function handler for that click event, emit a sort-changed event from the target table
// Assuming an SFC (single file component)
<template>
<div>
<b-button #click="handleClick">Sort the table!</b-button>
<b-table ref="mytable" #sort-changed="handleSortChange"></b-table>
</div>
</template>
<script>
export default {
// ...
methods: {
handleClick(evt) {
/// this is called when you click the button
this.$refs.mytable.$emit('sort-changed')
}
handleSortChange(context) {
// this is called when b-table with ref "mytable" hears the 'sort-changed' event
// that it has emitted
// sorting logic goes here
}
}
//...
}
</script>

Toggle in loop?

I wish to toggle (show/hide) a list when clicking on a title, but cant get the following to work
I have this:
<!-- Title -->
<div v-for="(subitem, index) in item" v-if="index === 0" #click="toggle(subitem)">
{{subitem.system_name}} - ({{item.length}})
</div>
<!-- All title items that should expand on click "Title" -->
<div v-if="subitem.clicked">
<p>{{subitem.system_name}}</p>
</div>
When clicking on the im triggering a toggle function called toggle, that sets a property on the item "clicked" to true or false (I should mention that this property does not already exist on the object, and I haven't got the possiblity add it, as we get the JSON from an API)
The toggle function is this:
toggle: function (data) {
data.clicked = !data.clicked;
},
Now, when I do this above, I get an error saying: "Property or method "subitem" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option"
Im guessing I get this because the "clicked" property doesnt exist in the object... So how do I work around this? Can't see any real solution ?
You initialize subitem in the v-for as a single item in the loop, but you are using it outside the element which has v-for loop on it. That's the reason you get that warning:
Property or method "subitem" is not defined on the instance but referenced during render.
Make sure to declare reactive data properties in the data option"
So move the div you want to toggle inside the div which has the v-for loop on it
<!-- Title -->
<div v-for="(subitem, index) in item" v-if="index === 0" #click="toggle(subitem)">
{{subitem.system_name}} - ({{item.length}})
<!-- All title items that should expand on click "Title" -->
<div v-if="subitem.clicked">
<p>{{subitem.system_name}}</p>
</div>
</div>
And coming yo the 2nd issue, as you mention the subitem obj does not have clicked property when you fetch the json from api.
You cannot add root level reactive properties after the vue instance is created.
Since you want to toggle the appeance of the div based on the property clicked which is not available at the time vue instance is created you should use vm.$set() to add reactive properties or Object.assign() to add properties to existing object. See Reactivity in depth
So in your case
toggle: function (data) {
if(data.hasOwnProperty('clicked')){
data.clicked = !data.clicked;
}else{
//since its the first time , set the value pf clicked to true to show the subitem
data = Object.assign({}, data, {clicked: true});
}
},