What are some strategies for navigating the constraint of having multiple root elements in Vue.js?

Seeking assistance from the experts here to solve this particular issue.

I have a dataset that looks like this:

[
  {
    title: 'Header',
    children: [
      {
        title: 'Paragraph',
        children: [],
      },
    ],
  },
  {
    title: 'Container',
    children: [
      {
        title: 'Paragraph',
        children: [],
      },
    ],
  },
]

The desired output is to render this data in a list of <div> elements as follows:

<div class="sortable-item" data-depth="1" data-index="0">Header</div> <!-- Parent -->
<div class="sortable-item" data-depth="2" data-index="0">Paragraph</div> <!-- Child-->
<div class="sortable-item" data-depth="1" data-index="1">Container</div> <!-- Parent -->
<div class="sortable-item" data-depth="2" data-index="0">Paragraph</div> <!-- Child-->

I've tried creating a recursive component so far, here's what I have:

<template>
  <template v-for="(item, index) in tree">
    <div
      class="sortable-item"
      :data-depth="getDepth()"
      :data-index="index"
      :key="getKey(index)"
    >
      {{ item.title }}
    </div>
    <Multi-Level-Sortable
      :tree="item.children"
      :parent-depth="getDepth()"
      :parent-index="index"
      :key="getKey(index + 0.5)"
    ></Multi-Level-Sortable>
  </template>
</template>

<script>
export default {
  name: 'MultiLevelSortable',
  props: {
    tree: {
      type: Array,
      default() {
        return [];
      },
    },
    parentDepth: {
      type: Number,
    },
    parentIndex: {
      type: Number,
    },
  },
  methods: {
    getDepth() {
      return typeof this.parentDepth !== 'undefined' ? this.parentDepth + 1 : 1;
    },
    getKey(index) {
      return typeof this.parentIndex !== 'undefined' ? `${this.parentIndex}.${index}` : `${index}`;
    },
  },
};
</script>

It can be observed that there is a <template> as the root element along with a v-for, which are typically not recommended in Vue.js. How can I overcome this and achieve rendering the elements in the specified format mentioned above?

Note: I attempted using vue-fragment and achieved the desired structure, but faced compatibility issues when incorporating Sortable.js, where it did not recognize any of the .sortable-item elements.

Any guidance or suggestions would be highly appreciated! Thank you!

Answer №1

Big shoutout to @AlexMA for guiding me through using a functional component to resolve my issue. Check out the code snippet below:

import SortableItemContent from './SortableItemContent.vue';

export default {
  functional: true,
  props: {
    tree: {
      type: Array,
      default() {
        return [];
      },
    },
  },
  render(createElement, { props }) {
    const flat = [];

    function flatten(data, depth) {
      const depthRef = typeof depth !== 'undefined' ? depth + 1 : 0;

      data.forEach((item, index) => {
        const itemCopy = item;

        itemCopy.index = index;
        itemCopy.depth = depthRef;
        itemCopy.indentation = new Array(depthRef);

        flat.push(itemCopy);

        if (item.children.length) {
          flatten(item.children, depthRef);
        }
      });
    }

    flatten(props.tree);

    return flat.map((element) => createElement('div', {
      attrs: {
        'data-index': element.index,
        'data-depth': element.depth,
        class: 'sortable-item',
      },
    },
    [
      createElement(SortableItemContent, {
        props: {
          title: element.title,
          indentation: element.indentation,
        },
      }),
    ]));
  },
};

The SortableItemContent module is structured as follows:

<template>
  <div class="item-content">
    <div
      v-for="(item, index) in indentation"
      :key="index"
      class="item-indentation"
    ></div>
    <div class="item-wrapper">
      <div class="item-icon"></div>
      <div class="item-title">{{ title }}</div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'SortableItemContent',
  props: {
    title: String,
    indentation: Array,
  },
};
</script>

After incorporating the advice provided by @AlexMA and utilizing Functional Components, the HTML elements now render according to my specifications:

<div data-index="0" data-depth="0" class="sortable-item">
  <div class="item-content">
    <div class="item-wrapper">
      <div class="item-icon"></div>
      <div class="item-title">Header</div>
    </div>
  </div>
</div>
<div data-index="0" data-depth="1" class="sortable-item">
  <div class="item-content">
    <div class="item-indentation"></div>
    <div class="item-wrapper">
      <div class="item-icon"></div>
      <div class="item-title">Paragraph</div>
    </div>
  </div>
</div>
<div data-index="1" data-depth="0" class="sortable-item">
  <div class="item-content">
    <div class="item-wrapper">
      <div class="item-icon"></div>
      <div class="item-title">Container</div>
    </div>
  </div>
</div>
<div data-index="0" data-depth="1" class="sortable-item">
  <div class="item-content">
    <div class="item-indentation"></div>
    <div class="item-wrapper">
      <div class="item-icon"></div>
      <div class="item-title">Paragraph</div>
    </div>
  </div>
</div>

Special thanks once again to @AlexMA for introducing me to Functional Components.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is it possible to establish a limit on a field's value in MongoDB?

Just a quick query - I'm curious if there's a feature in mongodb that allows for fields to have a set maximum value. For instance, let's say the field "cards" is restricted to a maximum value of 100. If an increment would exceed this limit, ...

`questioning the unusual behavior of document.onload(), seeking answers`

Before delving into this, let's avoid downvoting as this is not another typical question about window.onload vs document.onload. window.onload triggers once all DOM nodes are fully loaded, while document.onload triggers when all DOM nodes are ready w ...

When defining multiple types for props in Vue, the default behavior for Boolean type props is not preserved

Imagine you have a component called MyComponent with a prop named myProp declared as: props: { myProp: Boolean } By simply using <MyComponent myProp/>, the default behavior would set myProp to true. However, this simplicity is lost when there ...

Converting HTML/Javascript codes for Android Application use in Eclipse: A Step-by-Step Guide

How can I implement this in Java? Here is the HTML: <head> <title>Google Maps JavaScript API v3 Example: Geocoding Simple</title> <link href="http://code.google.com/apis/maps/documentation/javascript/examples/default.css" rel="styles ...

Using InnerHTML in Javascript within the Quasar/VueJS framework is unsupported

I am looking to dynamically create tables based on the items inside the 'counts' array below. The number of tables and their contents can change. Here is my divContainer, where the innerHTML will be appended: <div id="divContainer" style="pa ...

The bar chart in chartjs is not displaying properly due to incorrect grouping

I attempted to generate a multi bar chart using Chart.js, but encountered an issue where the jobType and jobCount were not displayed correctly based on each companyName. Below is the table: Here is the PHP file (CompanySelection.php): <?php header(& ...

AngularJS error: Uncaught MinError Object

Recently, I started a new AngularJS project and successfully set it up. The installation of angular and angular-resource using bower went smoothly. However, upon installing another service that I have used previously - https://github.com/Fundoo-Solutions/a ...

Having issues with implementing PrimeNG components (Directive annotation not detected)

Having difficulty integrating PrimeNG components (beta5) with Angular2 (rc.1). Whenever attempting to utilize a component, such as the menubar, the following error is consistently encountered: No Directive annotation found on Menubar New to Angular and ...

What is the best way to reorganize an object's properties?

Looking for a way to rearrange the properties of an existing object? Here's an example: user = { 'a': 0, 'b': 1, 'c': 3, 'd': 4 } In this case, we want to rearrange it to look like this: user = { &a ...

The setLanguage function in jsPDF does not support rendering different language characters

I'm currently working with jsPDF in Angular 2 and I'm experiencing an issue where my HTML content is not converting successfully into a PDF when it's written in Hebrew. Other languages seem to work fine, but Hebrew is causing a problem. How ...

The communication between the extension and chrome.runtime.connect() fails, possibly due to an issue with the chrome manifest version

I've been working on converting a Chrome extension that stopped functioning in manifest version 2. I've removed inline JavaScript and switched from chrome.extension.connect to chrome.runtime.connect. However, I'm still encountering issues wi ...

Increase and decrease thumbnail size using an onclick event

I've been experimenting for hours to try and create a functionality where the thumbnail image on my HTML page enlarges when clicked, and then shrinks back down when clicked again. However, I'm encountering an issue where it only gets bigger and d ...

Fire the click event following the parsing of the HTML page

Is it possible to programmatically navigate to a URL, retrieve the response, and then activate the JavaScript click event of an HTML element within that response? For example, if the response contains an element like this: <div id="test">Click me< ...

Clicking on the ng-repeat will trigger the ng-click event, which populates all the data using ng

I need help including an HTML page using ng-click within ng-repeat. However, it is currently loading all the content for every ng-repeat element. My specific requirement is to only bind(ng-include) the clicked element. Please see the attachment for m ...

Access information from multiple div elements using JavaScript data-attributes

Having trouble retrieving data-attribute values from multiple divs with the same class when clicked. The goal is to display the data value of the clicked div. function playSound(e) { const audio = document.querySelector(`audio[data-key="${e.keyCode}"]`) ...

Setting attributes on dynamically appended elements using Jquery

Whenever a user clicks on another user's name, a popup will appear with a form to send a message to that specific user. The goal is to dynamically change the 'action' attribute to include the user's ID in the form submission URL. Althou ...

Display information in a paginated format using components

As a newcomer to React, I may use the wrong terms so please bear with me. I am attempting to implement pagination for an array of components. To achieve this, I have divided the array into pages based on the desired number of items per page and stored eac ...

Delete a particular table while utilizing $.fn.DataTable.tables()

On a single page, I have several tables that need to be removed when the user decides. My approach was to utilize let table = $.fn.DataTable.tables() table[i-1].destroy(); This code is aimed at obtaining an array of all the tables and subsequently destroy ...

What is the best way to create a form that includes both dynamic objects and dynamic arrays using a JSON schema?

I have observed how a JSON schema can be utilized to construct dynamic arrays. My goal is to develop a JSON web form using a JSON schema that allows for objects (dictionaries) to be expandable similar to arrays. For example, you can visit the demonstrati ...

What is the best way to add child elements to existing elements?

When it comes to creating elements with jQuery, most of us are familiar with the standard method: jQuery('<div/>', { id: 'foo', href: 'http://google.com', }).appendTo('#mySelector'); However, there ar ...