digital

Story of writing a custom VueJS audio button for WordPress Gutenberg

It may not seem like it, but this audio button (screenshot below) took quite a bit of time to get working:

VueJS audio button
Audio button developed for Bitesize Irish. Written in Vue. Test it out.

The button is a custom Gutenberg editor “block” I wrote. That means we can feature any of the thousands of audio recordings we have on Bitesize by specifying it ID when writing a post (or lesson).

Gutenberg is the default editor for WordPress. You build your page up through “blocks”. A block can be a heading, a paragraph, or something a little more complex like an image with a caption.

Gutenberg allows developers to create custom blocks. So between your paragraphs you can have your own block (let’s say you’re a property listing site, then you might invest into developing a block that features a specific property).

The editing view

The view of the audio button when writing a post in WordPress.

Let’s say I’m writing a blog post. I write a paragraph, but then I insert our custom audio button block. It asks me for a “Sound ID” (which I have to get manually from another page). That’s all that’s being stored in WordPress: the Sound ID I want the audio button to play when someone views the page.

The mechanism I used for this custom block was the Advanced Custom Fields (ACF) plugin for WordPress, which allows you to create custom Gutenberg blocks.

That’s the storage of an ID field, but how do you get it to load the audio info and show a play button?

The VueJS audio button

I had already separately developed the audio button for Bitesize Irish quizzes. It was a real treat to develop it in Vue (I love the simplicity of Vue).

With the ACF custom block (see above), you get to define what the block outputs to the browser. So each audio button is its own little instance of VueJS. The ACF block outputs a custom element <bitesize-audio-button> and creates a Vue instance on that element.

The Bitesize audio button written in VueJS is responsible for:

  • Defining the HTML template of the audio button itself
  • Making a request to the sounds API, given the sound ID, to get the Irish language, English translation, and phonetic pronunciation of this phrase
  • Making an “new Audio()” instance, loading in the MP3
  • Playing/stopping the audio for the user

I had to also edit the theme, so that the audio button’s script file and CSS file are loaded once per page.

Back to the learner

This is all in the goal of helping people learn and speak the Irish language. With this audio button, we’ll be able to develop rich lessons right in WordPress, with the ultimate goal of having a full program covering lessons and a community.

Vue.js Recipes

This is a record of some gotchas I’ve come across with Vue.js v2. I love its simplicity.

Caveat: examples are taken from real code, but I have not tested them directly. If you see a problem, please contact me.

Contents of this post:

  • AJAX Request
  • kebab-case for Props with HTML Components
  • Force Refresh of Object

AJAX Request

Include Axios:

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

Make the request, noting the self trick to keep a reference to this:

Vue.component('my-vue-component', {
  ...
  data: {
    widgets: null
  },
  methods: {

fetchData: function() {
      let self = this;
      axios.get("/api/widgets")
        .then(function (response) {
          self.widgets = response.data;
        })
        .catch(function (error) {
          console.log(error);
        });
    }
  }
  ...
});

kebab-case for Props with HTML Components

Use camelCase in the JavaScript, and kebab-case in the HTML.

This is the HTML template for your component, note how the widgetName changes to widget-name:

<template id="widget-editor">
  <p>{{ widgetName }}</p>
</template>

<div id="my-app">
  <widget-editor :widget-name="Weather Widget"></widget-editor>
</div>

Javascript for the component:

Vue.component('widget-editor', {
  // camelCase in JavaScript
  props: ['widgetName'],
  template: '#widget-editor'
})

Force refresh of object

Problem: let’s say you have a component that displays a model, like a “todo list” which contains another component “todo item”. If you load new data for that todo list itme from the server, you might want the todo list item component to be recreated completely so that it goes through all its initialisation steps. If you don’t destroy the model, you component might have state from the older model. For example, you might forget to reset a boolean flag “show top priority item” when the item changes. Most of this is covered by how Vue.js watches for changes in its properties you set. This is more about overcoming programmer bugs. Maybe this really isn’t a problem, and I didn’t understand what the correct approach should have been.

Controversial approach: Vue.js documentation advises you to be “data-centric”. In other words, instead of destroying and re-creating an object, you should watch for changes in your component of that object. But I’ve found in one case it was cleaner to destroy (nullify) the object first, then assign a new object to the variable, so that no state is left over from the older object.

HTML:

<template id="todo-list">
  <p>
    My one thing to do:
    <todo-item :item-object="topPriorityItem" v-if="topPriorityItem"></todo-item>
</template>

JavaScript:

Vue.component('todo-list', {
  data: {
    todoItems: ['Get kebab', 'Go for a walk'],
    topPriorityItem: null
  },
  methods: {
    setTopPriority: function(itemIndex) {
      this.topPriorityItem = null;
      this.$nextTick(function() {
        // Trick: waits until next "tick" to ensure that item nullification destroys <todo-item>
        this.topPriorityItem = this.todoItems[itemIndex];
      });
    }
  }
}
Vue.component('todo-item', {
  props: [itemObject],
  mounted: function() {
    // Complicated state setting here that would be hard to track
    // if the item wasn't destroyed and recreated
  }
});

Vue.js and Choosing Simplicity

I’m a fan of Vue.js, a progressive JavaScript framework.

Vue.js can be used in two ways:

  1. Direct <script> include
  2. NPM

Even with Vue, you have the choice to choose simplicity (direct include) or to choose advanced features (NPM building).

In fairness to the Vue.js documentation, they warn you against the NPM route unless you’re building a “large scale application”.

Use the “Direct Include” Method for Vue’s Progressiveness

What does Vue do really well? It lets you include their CDN-hosted .js file, and in 5 minutes you have a dynamic web page. That’s the implementation of its progressiveness.

When you’re testing out Vue, just include its script direclty. Don’t start building in NPM.

“But I’m too cool, I need NPM.”

If you know you need NPM, this article is already not for you. It is, however, a suggestion to use the direct include method for as long as you can.

The biggest reason I have to not use NPM (and this is subjective): it will present a whole extra technological barrier to letting other developers work on your application. It’s very easy for us techies to forget how many layers of technology you need to understand to get a complex application built, deployed, and maintained. It’s only when you try to explain the application to a second developer that you might suddenly realise all the brain complexity involved just for delving into developing the application.

It can be an ego thing, admit it. If you choose a solution that’s “too simple”, you might be afraid of people judging you as “too simple”.

In Summary

In an iPhone-wielding, Macbook-wielding, hipster-inspired, checked shirts-dominated world, it’s easy to want to use the more cool (and complicated) approach.

But stepping back and choosing the simplest technological solution can often win out over advanced complexity.