group computed properties in vue component

Posted on

Problem

I have the following vue component which all works fine

<template>
  <div class="sales-agreements">
    <nav class="sales-agreements__navigation">
      <ul class="sales-agreements__navigation-list">
        <li class="sales-agreements__navigation-item">
          <router-link :to="{ path: '/' }" class="sales-agreements__navigation-link">All</router-link>
        </li>
        <li class="sales-agreements__navigation-item">
          <router-link :to="{ path: '/', query: { salesType: 'mortars' } }" class="sales-agreements__navigation-link">Mortars</router-link>
        </li>
        <li class="sales-agreements__navigation-item">
          <router-link :to="{ path: '/', query: { salesType: 'bricks' } }" class="sales-agreements__navigation-link">Bricks</router-link>
        </li>
      </ul>
    </nav>
    <div class="sales-agreements__header">
      <div class="sales-agreements__header-box sales-agreements__header-box--welcome">
        Welcome
      </div>
      <h1 class="sales-agreements__header-box sales-agreements__header-box--title">
        {{ title }}
      </h1>
    </div>
    <div class="sales-agreements__results">
      <h2 class="sales-agreements__results-title">{{ subTitle }} sales agreements</h2>
      <div v-if="salesAgreementItems.length">
        <div v-for="salesAgreementItem in filteredSalesAgreementItems" :key="salesAgreementItem.id" class="sales-agreements__item">
          <div class="sales-agreements__item-details">
            <div class="sales-agreements__item-id"><strong>{{ salesAgreementItem.id }}</strong></div>
            <div class="sales-agreements__item-icon-holder">
              <bricks-icon v-if="salesAgreementItem.department === 'bricks'" :icon-class="'sales-agreements__item-icon'"></bricks-icon>
              <mortars-icon v-if="salesAgreementItem.department === 'mortars'" :icon-class="'sales-agreements__item-icon'"></mortars-icon>
            </div>
            <div class="sales-agreements__item-address">
              <span v-if="salesAgreementItem.deliveryStreet">{{ salesAgreementItem.deliveryStreet }}<br></span>
              <span v-if="salesAgreementItem.deliveryCity">{{ salesAgreementItem.deliveryCity }}<br></span>
              <span v-if="salesAgreementItem.deliveryPostcode">{{ salesAgreementItem.deliveryPostcode }}</span>
            </div>
          </div>
        </div>
      </div>
      <div v-else>
        <p><strong>Sorry, there are currently no sales agreements to view.</strong></p>
      </div>
    </div>
  </div>
</template>

<script>
  import BricksIcon from '../icons/bricks-icon.vue';
  import MortarsIcon from '../icons/mortars-icon.vue';
  import './sales-agreements.scss';

  export default {
    name: 'SalesAgreements',
    components: {
      BricksIcon,
      MortarsIcon,
    },
    props: {
      salesAgreementItems: {
        default() {
          return [];
        },
        type: Array,
      },
    },
    computed: {
      filteredSalesAgreementItems() {
        switch (this.$route.query.salesType) {
          case 'bricks':
            return this.salesAgreementItems.filter(obj => obj.department === 'bricks');

          case 'mortars':
            return this.salesAgreementItems.filter(obj => obj.department === 'mortars');

          default:
            return this.salesAgreementItems;
        }
      },
      subTitle() {
        switch (this.$route.query.salesType) {
          case 'bricks':
            return 'Bricks';

          case 'mortars':
            return 'Mortars';

          default:
            return 'All';
        }
      },
      title() {
        switch (this.$route.query.salesType) {
          case 'bricks':
            return 'Bricks';

          case 'mortars':
            return 'Mortars';

          default:
            return 'Bricks & Mortars';
        }
      },
    },
  };
</script>

But as you can see, the computed properties repeats the same switch – is there a way to change all three properties in one switch statement so if I add further properties or switch cases, I do not have to do it multiple times?

Solution

Computed properties can return objects, which seems useful for you.

computed: {
  filteredItems() {
    switch (this.$route.query.salesType) {
      case 'bricks':
        return {
            filteredSalesAgreementItems: this.salesAgreementItems.filter(obj => obj.department === 'bricks'),
            subTitle: 'Bricks',
            title: 'Bricks'
        }
      case 'mortars':
        return {
          filteredSalesAgreementItems: this.salesAgreementItems.filter(obj => obj.department === 'mortars'),
          subTitle: 'Mortars',
          title: 'Mortars'
        }
      default:
        return {
          filteredSalesAgreementItems: this.salesAgreementItems,
          subTitle: 'All',
          title: 'Bricks & Mortars'
        }
    }
  },

You can then refer to the values using filteredItems.filteredSalesAgreementItems, filteredItems.subTitle and filteredItems.title

One option is to ensure that the sales type is in (or not in) a white list of types using Array.prototype.includes() – for example:

filteredSalesAgreementItems() {
  if (!['bricks', 'mortars'].includes(this.$route.query.salesType)) {
    return this.salesAgreementItems;
  }
  return this.salesAgreementItems.filter(obj => obj.department === this.$route.query.salesType);
}
subTitle() {
  if (!['bricks', 'mortars'].includes(this.$route.query.salesType)) {
    return 'All';
  }
  const type = this.$route.query.salesType;
  return type.charAt(0).toUpperCase() + type.subString(1);
},
title() {
  if (!['bricks', 'mortars'].includes(this.$route.query.salesType)) {
    return 'Bricks & Mortars';
  }
  const type = this.$route.query.salesType;
  return type.charAt(0).toUpperCase() + type.subString(1);
},

The last return values of the latter two computed properties could be stored in a method – e.g. toTitleCase().

if (!String.prototype.toTitleCase) {
  String.prototype.toTitleCase = function() {
    return this.charAt(0).toUpperCase() + this.subString(1);
  }
}

So then that can be used to simplify those last two computed properties – e.g.:

return this.$route.query.salesType.toTitleCase();

And the list of sales types could be declared before the component – e.g.

const SALES_TYPES = Object.freeze(['bricks', 'mortars']);

Then that list can be referenced within the computed properties.

Leave a Reply

Your email address will not be published. Required fields are marked *