Comma, comma & and

An ampersand

I recently had to concatenate author names with commas and an ampersand in the following manner:

commaAnd(['Emir Jong']);
// becomes: "Emir Jong"

commaAnd(['Kristian Josefsen', 'Tetyana Bohuňková']);
// becomes: "Kristian Josefsen & Tetyana Bohuňková"

commaAnd(['Luana Ferreira Carvalho', 'Jian Tu', 'Ambessa Afwerki']);
// becomes: "Luana Ferreira Carvalho, Jian Tu & Ambessa Afwerki"

Here are a few implementations in JavaScript:

function commaAnd(strs, comma = ", ", and = " & ") {
  const glue = (i) => ["", and, comma][Math.min(strs.length - i - 1, 2)];
  return strs.reduce((res, str, i) => res + str + glue(i), "");
}
function commaAnd(strs, comma = ", ", and = " & ") {
  return (
    ((s) => (s ? s + and : ""))(strs.slice(0, -1).join(comma)) + strs.slice(-1)
  );
}
function commaAnd(strs, comma = ", ", and = " & ") {
  const init = strs.slice(0, -1).join(comma);
  return [init, strs.slice(-1)].filter((s) => s).join(and);
}

Vue

But then each author name needed a bit of markup as well. In Vue, we could concatenate HTML strings and use v-html, but we should avoid that if we can.

Here’s my method using slot scopes (much like that todo list example):

<template>
  <span>
    <span v-for="(item, i) in items" :key="i"
      ><slot name="item" :item="item">{{ item }}</slot
      >{{ [null, and, comma][Math.min(items.length - i - 1, 2)] }}</span
    >
  </span>
</template>

<script>
export default {
  name: "CommaAnd",
  props: {
    items: { type: Array, default: Array },
    comma: { type: String, default: () => ", " },
    and: { type: String, default: () => " & " },
  },
};
</script>

The usage is as follows:

<CommaAnd :items="authors">
  <template v-slot:item="{ item }">
    {{ fullName(item)
    }}<sup>{{
      affiliations.indexOf(item.affiliation) + 1
    }}</sup></template
  >
</CommaAnd>

From the CommaAnd component, we call out to the parent component to define how to render each item. Inside the component, we only define what to do in between the items.

Note that much of the whitespace between elements and interpolations ({{ }}) must be eliminated when we are dealing with inline text. Furthermore, forcing HTML/XML into short lines is difficult (Prettier is doings its best here…) and the Vue syntax for slot scopes is a bit entangled, so the result is not super readable.

The resulting output is also pretty loud. Lots of <span>s in <span>s. If you don’t want that you should probably go with v-html anyway.

Leave a Reply

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