VueJS Nested Component
I was recently working with the Quickbooks API. The task was to import the chart of accounts and show them in nested check-boxes.
Quickbook charts of accounts on the UI has a structure like this.
We wanted to create a similar structure but with checkboxes and allow users select only those accounts that they wanted to import.
Goal:
Understanding the API response
The API response was like this:
{ "Name": "Job Expenses", "Id": "58", }, { "Name": "Cost of Labor", "ParentRef": { "value": "58" }, "Id": "59", }, { "Name": "Installation", "ParentRef": { "value": "59" }, "Id": "60", }, { "Name": "Maintenance and Repairs", "ParentRef": { "value": "59" }, "Id": "61", }, { "Name": "Equipment Rental", "ParentRef": { "value": "58" }, "Id": "62", }, { "Name": "Job Materials", "ParentRef": { "value": "58" }, "Id": "63", }, // ...
Each account is its own object with a ParentRef field telling which Id is the parent account. Would have been really great if we would have got this information nested already from the API, but since it is not, we have to do it on our own.
Building a Tree Structure
We now wanted to get the structure into form of a tree:
getTree(allCategories, subCategories, attached) { var tree = []; var counter = 0; subCategories.forEach(ex => { if (!attached.includes(ex.Id)) { attached.push(ex.Id); tree[counter] = ex; var children = this.getChildren(ex.Id, allCategories); if (!children || !children.length) { tree[counter]["children"] = children; } else { tree[counter]["children"] = this.getTree( allCategories, children, attached ); } counter++; } }); return tree; }, getChildren(id, categoryData) { return _.filter(categoryData, function(o) { return o.ParentRef == id; }); },
We call the “Accounts” coming in from QB, “Categories” here.
In the getTree
method the parameters allCategories
holds the original data structure we had received from the API and subCategories
, stores the data structure containing only the children of the current object that is being processed. We use our friend “recursion” and build a tree structure.
Building the Nested Checkboxes
Once we got the tree structure, now we had to create the template for it. We created a new component NestedCheckboxes.vue, with the template:
<template> <div class="row p-0" :class="depth ? 'ml-3' : ''"> <template v-for="(qbCategory) in categoryData"> <div class="pl-2 p-1" :class="depth ? 'col-12' : 'col-4 border border-light'" :key="depth + qbCategory.Id" > <div class="custom-control custom-checkbox checkbox-primary form-check"> <input type="checkbox" class="custom-control-input" :id="'qbCategory' + qbCategory.Id" :value="qbCategory.Id" @input="updatedValue" v-model="selectedData" > <label class="custom-control-label" :for="'qbCategory' + qbCategory.Id" >{{ qbCategory.Name}}</label> </div> <nested-checkboxes :key="'child' + qbCategory.Id + depth" :category-data="qbCategory.children" :depth="depth+1" :form-data="formData" :root-data="rootData" @checkbox-updated="handleUpdatedEvent" ></nested-checkboxes> </div> </template> </div> </template>
See how the template calls the component itself. Clicking on an input button emits an event which lets the parent know of updates. The parent then takes care of updating all the nested components. This is how we use it in the parent.
<nested-checkboxes :key="'parent' + categoryGroup" :category-data="getTree(categoryData, categoryData, [])" :root-data="categoryData" @checkbox-updated="handleUpdatedEvent" :form-data="formData.categories" ></nested-checkboxes>
Finally, with help of lodash and some more code which manages updates, we reach our goal. You can check the full code here.
Really enjoyed building this.