For the upcoming PiCockpit v2.0 release, I am preparing a GPIO application.

The GPIO entries are table rows – and to configure them, we want to be able to access the detail row by clicking on the configure button:

image

clicking either the chevron or the configure button will lead to the detail row opening:

image

Here is the corresponding Vue template:

<v-data-table
   :headers=“gpioInputTableHeaders“
   :items=“gpioInputTableData“
   :loading=“loading“
   item-key=“bcm_id“
   loading-text=“waiting for data … „
   fixed-header
  show-expand
   :expanded.sync=“gpioInputTableExpanded“

   class=“elevation-1″
>
   <template v-slot:item.value=“{ item }“>
     <span v-if=“!onlineStateBinary“><i class=“fa fa-warning“></i> </span>
     <span v-if=“item.state == 1″><v-icon x-small>mdi-arrow-up-bold</v-icon>{{item.high}}</span>
     <span v-else-if=“item.state == 0″><v-icon x-small>mdi-arrow-down-bold</v-icon>{{item.low}}</span>
     <span v-else-if=“item.state == 2″>N/A</span>
     <span v-else-if=“item.state == 4″>N/A</span>
     <span v-else>ERROR</span>
   </template>
   <template v-slot:item.action=“{ item }“>
     <v-btn
       class=“mr-2″
       color=“primary“
       small
       @click.ctrl.exact=“configGpioAll(‚input‘, item)“
       @click.exact=“configGpio(‚input‘, item)“

     >
       <v-icon left small class=“mr-2″>mdi-hammer-wrench</v-icon>
       Configure
     </v-btn>
     <v-btn
       class=“mr-2″
       color=“error“
       small
       @click=“remove_gpio(‚input‘, item.bcm_id)“
     >
       <v-icon left small class=“mr-2″>mdi-trash-can-outline</v-icon>
       Remove
     </v-btn>
   </template>
   <template v-slot:expanded-item=“{ headers, item }“>
     <!– v-on:pull-change=“pullChange“ –>
     <td :colspan=“headers.length“>
       <gpioInputDetailRow
         :rowData=“item“
         v-on:pull-change=“pullChange“
         v-on:debounce-change=“debounceChange“
       ></gpioInputDetailRow>
     </td>
   </template>       
</v-data-table>        

As you can see I wrap another component for the expanded-item slot. This component is irrelevant for our current discussion, therefore I do not provide code for it.

I have highlighted the important bits above.

These are:

  • :items=“gpioInputTableData“ – this links the data to the items key of the Vuetify data table
  • item-key=“bcm_id“ – this is important to give a unique key for every entry of your data (which is given as an array to items).
    • if you omit setting this up, all your detail rows will open at once and close at once.
    • the value should be unique as discussed; in case your data potentially has duplicate values across all keys you need to creat an additional unique key in it
  • show-expand – this will show the Chevrons image
  • :expanded.sync=“gpioInputTableExpanded“ – note the use of the sync modifier, to synchronize changes back! This is a variable to store the currently expanded items in.

then:

  •  @click.ctrl.exact=“configGpioAll(‚input‘, item)“ – as you can see, on Ctrl + Click, I am calling a utility function to toggle all expansion slots, depending on the state of the current item. I pass the item to it, which is to be expanded
  • @click.exact=“configGpio(‚input‘, item)“ – as you see, on an exact click, I call another utility function (we will get to these shortly), ALSO passing in the current item

finally:

  • <template v-slot:expanded-item=“{ headers, item }“> – this Slot holds the expanded item. Passing in the headers is important:
  • <td :colspan=“headers.length“> – … because it allows you to create a cell which spans all columns

Here is how my data() looks like (excerpt):

data () {
   return {
     loading: true,
     gpioInputTableData: [],
     gpioInputTableHeaders: [
       { text: ‚Name‘, value: ’name‘},
       { text: ‚Value‘, value: ‚value‘},
       { text: ‚Action‘, value: ‚action‘}
     ],
     gpioInputTableExpanded: []

  }
},

And here are the two utility functions:

image

configGpioAll: function (source, item){
         var ref = null;
         var refTable = null;
         if (source === ‚input‘){
             ref = ‚gpioInputTableExpanded‘;
             refTable = ‚gpioInputTableData‘;
         } else if (source === ‚output‘){
             ref = ‚gpioOutputTableExpanded‘;
             refTable = ‚gpioOutputTableData‘;
         } else if (source === ’softpwm‘){
             ref = ‚gpioSoftPWMTableExpanded‘;
             refTable = ‚gpioSoftPWMTableData‘;
         }
         // do we match for this item (is it opened?)
        if (this[ref].filter(e => e.bcm_id === item.bcm_id).length > 0){
           // remove all items
           console.log(„configGpioAll: removing“);
           this[ref] = [];
         }
         else {
           console.log(„configGpioAll: adding“);
           // add all items
           this[ref] = this[refTable];
         }
       },

in configGpioAll, I check whether I have the input, output or softpwm table (to avoid duplicating code), and set up references to the appropriate expanded variable (see data above), and the table data itself.

If the item is contained within the expanded data (I match by my unique key, bcm_id), I close all expanded items – for this I simply set the expanded variable to an empty array.

If the item is NOT contained within the expanded data, I open all items, by putting all the table data into it.

Note: it might be worthwhile to experiment whether an array of objects containing just the unique keys might be enough for this to work as well.

image

configGpio: function (source, item){
   var ref = null;
   if (source === ‚input‘){
       ref = ‚gpioInputTableExpanded‘;
   } else if (source === ‚output‘){
       ref = ‚gpioOutputTableExpanded‘;
   } else if (source === ’softpwm‘){
       ref = ‚gpioSoftPWMTableExpanded‘;
   }
   if (this[ref].filter(e => e.bcm_id === item.bcm_id).length > 0){
     // remove the item
     this[ref] = this[ref].filter(e => e.bcm_id !== item.bcm_id);
   }
   else {
     // add the item
     this[ref].push(item);
   }
},

in configGpio, I do similar things – but instead of removing all items, I filter and remove the item which matches the bcm_id I got passed in.

If the item is not present, I push it into the expanded variable.

This is the reason that I need the entire item, not just the bcm_id. I chose to have symmetry in my code, so configGpioAll which technically only needs the bcm_id also gets the entire item.