Hone logo
Hone
Problems

Mastering Vue's Template Refs for Dynamic DOM Manipulation

In Vue.js, efficiently accessing and manipulating DOM elements directly from your component logic is a common requirement. Template refs provide a powerful and declarative way to establish a connection between your template and your script. This challenge will test your understanding of how to create and utilize template refs to dynamically interact with DOM elements in a Vue component.

Problem Description

You are tasked with building a Vue component that displays a list of items. For each item, you need to display a "highlight" button. When this button is clicked, the corresponding list item should have a specific CSS class applied to it to visually highlight it. You must achieve this using Vue's template refs mechanism.

Key Requirements:

  1. Dynamic Item Rendering: The component should accept an array of strings as a prop, where each string represents the content of a list item.
  2. Highlight Button per Item: For each item in the list, render a button with the text "Highlight".
  3. Template Refs for Each Item: Each list item (<li>) should have a template ref assigned to it. This ref should be dynamically generated to refer to the specific <li> element.
  4. Click to Highlight: When a "Highlight" button is clicked, the corresponding <li> element (accessed via its template ref) should have the CSS class highlighted added to it.
  5. Prevent Redundant Class Application: If an item is already highlighted, clicking its "Highlight" button should not cause any errors or unintended behavior.

Expected Behavior:

When the component mounts, it displays a list of items. Each item has a "Highlight" button. Clicking a "Highlight" button for an item will apply the highlighted class to that specific list item. Subsequent clicks on the same button will have no visual effect if the item is already highlighted.

Examples

Example 1:

<template>
  <div>
    <ul>
      <li v-for="(item, index) in items" :key="index" :ref="el => itemRefs[index] = el">
        {{ item }}
        <button @click="highlightItem(index)">Highlight</button>
      </li>
    </ul>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue';

export default defineComponent({
  props: {
    items: {
      type: Array as () => string[],
      required: true,
    },
  },
  setup(props) {
    const itemRefs = ref<HTMLElement[]>([]); // Array to store refs

    const highlightItem = (index: number) => {
      const element = itemRefs.value[index];
      if (element) {
        element.classList.add('highlighted');
      }
    };

    return {
      itemRefs,
      highlightItem,
    };
  },
});
</script>

<style>
.highlighted {
  background-color: yellow;
  font-weight: bold;
}
</style>

Input Component Usage:

<MyComponent :items="['Apple', 'Banana', 'Cherry']" />

Expected DOM State after clicking "Highlight" on "Banana":

<div>
  <ul>
    <li>
      Apple
      <button>Highlight</button>
    </li>
    <li class="highlighted">
      Banana
      <button>Highlight</button>
    </li>
    <li>
      Cherry
      <button>Highlight</button>
    </li>
  </ul>
</div>

Explanation: The v-for directive iterates through the items prop. A dynamic template ref el => itemRefs[index] = el is used to assign each <li> element to an index in the itemRefs array. When the "Highlight" button for "Banana" is clicked, highlightItem(1) is called. This accesses itemRefs.value[1] (which is the <li> for "Banana") and adds the highlighted class.

Example 2: Edge Case - Already Highlighted Item

If the highlighted class is already present on an <li> and its "Highlight" button is clicked again:

Input Component Usage:

<MyComponent :items="['Dog', 'Cat']" />

Initial State (after clicking "Highlight" on "Dog"):

<div>
  <ul>
    <li class="highlighted">
      Dog
      <button>Highlight</button>
    </li>
    <li>
      Cat
      <button>Highlight</button>
    </li>
  </ul>
</div>

Action: Click the "Highlight" button for "Dog" again.

Expected DOM State (no change):

<div>
  <ul>
    <li class="highlighted">
      Dog
      <button>Highlight</button>
    </li>
    <li>
      Cat
      <button>Highlight</button>
    </li>
  </ul>
</div>

Explanation: The element.classList.add('highlighted') method is idempotent. If the class already exists, calling add again has no effect, thus fulfilling the requirement of not causing unintended behavior.

Constraints

  • The items prop will always be an array of strings.
  • The number of items in the items array will not exceed 100.
  • The solution should be implemented using Vue 3 Composition API with TypeScript.
  • Avoid direct DOM manipulation outside of using template refs.

Notes

  • Consider how to properly initialize and manage the array that will hold your template refs.
  • The ref function in Vue's Composition API is key here. You'll need to assign a callback function to the template ref attribute (ref="...") within the v-for loop.
  • The callback function will receive the DOM element as an argument, allowing you to store it.
  • Remember that refs assigned dynamically within v-for loops will be an array.
  • You'll need to add a CSS rule for the .highlighted class to see the visual effect.
Loading editor...
typescript