Search code examples
vue.jsvuejs2alignmentbootstrap-vue

Is it possible to concatenate class properties in vuejs?


General Question: I was wondering if I can concatenate class properties based on condition. See pseudo-code in the v-forline.

My use-case: I want to align all images that have an even index at the right side.

If I use flex flex-row-reverse for the parent section I get the images aligned on the right. But I don't know how to construct the class in such a way that I do not have to repeat the code for the child elements.

<section
    v-for="(quote, index) of $frontmatter.quotes :class="lg:flex my-4 mx-12 overflow-auto" + {even: index % 2, odd: !(index % 2)}"
  >
    <img
      class="quote-image right rounded-full h-64 w-64 flex items-center justify-center shadow-md mx-auto lg:ml-16 lg:mr-10 mt-12"
      :src="$withBase(quote.image)"
    />

    <div class="px-8 pt-6">
        <h3 class="text-primary font-bold mb-4">
          {{ quote.title }}
        </h3>
      <p>
        {{ quote.text }}
      </p>
    </div>
  </section>

And call the class extension something like:

.even {
   flex-row-reverse
}

Currently,I use this structure - however, I am not happy with that, as I have to repeat my code for the child elements...

<section
        v-for="(quote, index) of $frontmatter.quotes"
        class= "my-16 mx-24 overflow-auto"
      >
      <div v-if="index % 2"
      class="lg:flex flex-row-reverse">
        <img
          class="quote-image rounded-full h-64 w-64 flex items-center justify-center shadow-md mx-auto lg:ml-16 lg:mr-10 mt-12"
          :src="$withBase(quote.image)"
        />
        <div class="px-8 pt-6">
          <blockquote>
            <h2 class="text-primary font-bold mb-4">
              {{ quote.title }}
            </h2>
          </blockquote>
          <p class="quote-text">
            {{ quote.text }}
          </p>
        </div>
      </div>
      <div v-else
        class="lg:flex">
        <img
          class="quote-image rounded-full h-64 w-64 flex items-center justify-center shadow-md mx-auto lg:ml-16 lg:mr-10 mt-12"
          :src="$withBase(quote.image)"
        />
        <div class="px-8 pt-6">
          <blockquote>
            <h2 class="text-primary font-bold mb-4">
              {{ quote.title }}
            </h2>
          </blockquote>
          <p class="quote-text">
            {{ quote.text }}
          </p>
        </div>
      </div>

      </section>

It should look something like: enter image description here


Solution

  • First, just to clarify the question I'm trying to answer. Given the following code:

    <div v-if="index % 2" class="lg:flex flex-row-reverse">
      ... children ...
    </div>
    <div v-else class="lg:flex">
      ... identical children ...
    </div>
    

    Is there a way to avoid the v-if and conditionally add the flex-row-reverse class instead?

    You've got a number of options here. Firstly, the attributes class and style have special behaviour that allows you to specify both a bound and static copy of the same attribute. You can't do that for other attributes. e.g.

    <div
      class="lg:flex"
      :class="{ 'flex-row-reverse': index % 2 }"
    >
    

    So the class lg:flex is added as a static class whereas, flex-row-reverse is added conditionally. Vue will combine them as appropriate to create the class attribute of the finished DOM nodes.

    There are a number of other ways this could be written. Here are a couple to ponder:

    <div :class="{ 'lg:flex': true, 'flex-row-reverse': index % 2 }">
    
    <div :class="['lg:flex', { 'flex-row-reverse': index % 2 }]">
    

    Arrays can be nested arbitrarily deep. Plain strings will be treated as classes to add. Objects will be treated as collections of conditional classes with the class name as the property name and the condition the property value.

    All of this is using Vue's support for conditionally adding classes. However, a bound expression is just arbitrary JavaScript so you could apply the conditionality using normal JavaScript syntax instead of having Vue do it for you.

    This is generally clunkier. This has to be a single expression so you can't use if/else. However, you can use ?: instead:

    <div :class="'lg:flex' + (index % 2 ? ' flex-row-reverse' : '')">
    

    The official documentation for these various ways to build classes is here:

    https://v2.vuejs.org/v2/guide/class-and-style.html#Binding-HTML-Classes