How to Allow Editing of Mathematical equations in Vue.js?

How to Allow Editing of Mathematical equations in Vue.js?

If you want to allow your users to easily input the Mathematical Equations in your Vue app, then you are in the right place. Your users don’t need to learn the complex LaTex syntax. I used the Mathlive.io library.

mathlive-vuejs-equations-editor.png

The Problem in finding Math Equation Editor

When I hit this problem, I came across much mathematical equation editor’s but I wanted the solution which is clean enough to be implemented in VueJS and also easy enough to for the app users to use.

A lot of solutions depend on typing in the LaTeX syntax and then they display the equation. I didn’t find it user friendly at all. If you are wondering what is LaTeX, then please check this link.

Then I found MathLive.io. This is simply amazing.

How to integrate it in VueJS?

The next challenge is how to integrate Mathlive.io in the VueJS project. They give you a VueJS example. It is good to start but it didn’t work. Especially the CSS. I played around with various versions of this library and came with this procedure to integrate it.

Step by Step Procedure

Here are the steps you need to follow.

Step 1: Install Mathlive Dependencies

 npm i mathlive@0.27.4 --save

Please make sure you install this particular version only. As of this writing, the latest version is not working.

Step 2: Import the Required Mathlive CSS

Now you need to import the CSS from Mathlive which is going to make life easier for your users.

 <link rel="stylesheet" type="text/css" href="https://unpkg.com/mathlive@0.27.4/dist/mathlive.css">
    <link rel="stylesheet" type="text/css" href="https://unpkg.com/mathlive@0.27.4/dist/mathlive.core.css">

Again, make sure that it is the same exact version or things might not work.

Step 3: Create the MathliveComponent.vue

This is your Vue component, which is basically a wrapper around Mathlive’s Javascript library.

<template>
  <span class="mathfield" :id="id"><slot></slot></span>
</template>

<script>
/* eslint no-unused-vars: ["error", { "args": "none" }] */
import MathLive from "mathlive";

export default {
  name: "MathLiveInput",
    props:
    {
        id: {
            type: String,
            default: ''
        },
        value: {
            type: String,
            default: ''
        },
        config: {
            type: Object,
            default: () => ({})
        },
        onKeystroke: {
            type: Function,
            default: function(_keystroke, _ev) { return true; }
        },
        onMoveOutOf: {
            type: Function,
            default: function(_direction) { return true; }
        },
        onTabOutOf: {
            type: Function,
            default: function(_direction) { return true; }
        }
    },
    watch: {
        value: function(newValue, oldValue) {
            // When the `value` prop (from the model) is modified
            // update the mathfield to stay in sync, but don't send back content
            // change notifications, to avoid infinite loops.
            if (newValue !== oldValue) {
                this.$el.mathfield.latex(newValue, {
                    suppressChangeNotifications: true
                });
            }
        },
        config: {
            deep: true,
            handler: function(config) {
                this.$el.mathfield.$setConfig(config)
            }
        },
    },
    mounted: function () {
        // A new instance is being created
        const vm = this;  // Keep a reference to the ViewModel
        // Wait until the DOM has been constructed...
        this.$nextTick(function () {
            // ... then make the MathField
            MathLive.makeMathField(vm.$el, {
                ...vm.config,
                // To support the 'model' directive, this handler will connect
                // the content of the mathfield to the ViewModel
                onContentDidChange: _ => {
                    // When the mathfield is updated, notify the model.
                    // The initial input value is generated from the <slot>
                    // content, so it may need to be updated.
                    vm.$emit('input', vm.$el.mathfield.latex());
                },
                // Those asynchronous notification handlers are translated to events
                onFocus: _ => { vm.$emit('focus'); },
                onBlur: _ => { vm.$emit('blur'); },
                onContentWillChange: _ => { vm.$emit('content-will-change'); },
                onSelectionWillChange: _ => { vm.$emit('selection-will-change'); },
                onUndoStateWillChange: (_, command) => { vm.$emit('undo-state-will-change', command); },
                onUndoStateDidChange: (_, command) => { vm.$emit('undo-state-did-change', command); },
                onVirtualKeyboardToggle: (_, visible, keyboardElement) => { vm.$emit('virtual-keyboard-toggle', visible, keyboardElement); },
                onReadAloudStatus: (_, status) => { vm.$emit('read-aloud-status', status); },

                // Those notification handlers expect an answer back, so translate
                // them to callbacks via props
                onKeystroke: function(_, keystroke, ev) { return vm.onKeystroke(keystroke, ev); },
                onMoveOutOf: (_, direction) => { return vm.onMoveOutOf(direction); },
                onTabOutOf: (_, direction) => { return vm.onTabOutOf(direction); },

            });
        });
    },
    methods: {
        /**
         *
         * @param {string} selector
         */
        perform: function(selector) {
            this.$el.mathfield.$perform(selector);
        },
        /**
         * @return {boolean}
         */
        hasFocus: function() {
            return this.$el.mathfield.$hasFocus();
        },
        focus: function() {
            this.$el.mathfield.$focus();
        },
        blur: function() {
            this.$el.mathfield.$blur();
        },
        text: function(format) {
            return this.$el.mathfield.$text(format);
        },
        selectedText: function(format) {
            return this.$el.mathfield.$selectedText(format);
        },
        insert: function(text, options) {
            this.$el.mathfield.$insert(text, options);
        },
        keystroke: function(keys, evt) {
            return this.$el.mathfield.$keystroke(keys, evt);
        },
        typedText: function(text) {
            this.$el.mathfield.$keystroke(text);
        },
        selectionIsCollapsed: function() {
            return this.$el.mathfield.$selectionIsCollapsed();
        },
        selectionDepth: function() {
            return this.$el.mathfield.$selectionDepth();
        },
        selectionAtStart: function() {
            return this.$el.mathfield.$selectionAtStart();
        },
        selectionAtEnd: function() {
            return this.$el.mathfield.$selectionAtEnd();
        },
        select: function() {
            this.$el.mathfield.$select();
        },
        clearSelection: function() {
            this.$el.mathfield.$clearSelection();
        }
    }};
</script>

##Step 4: Import MathLiveInput and Mathlive in your component

Now, you need to use what you have setup. You need to import the Vue Component created in the last step into the component where you want to use it.

import MathLive from "mathlive";
import MathLiveInput from "./components/MathLiveInput.vue";

For simplicity, I have done it in App.vue for demo purposes.

Step 5: Initialize Mathlive

In the mounted hook of your component, you need to initialize "Mathlive", so that it can render the equations if any on your component. Call the renderMathInDocument function like this.

MathLive.renderMathInDocument();

Step 6: Use the MathLiveInput component

Now simply utilize the component created in step 2.

<MathLiveInput :config="config" v-model="formula" v-on:input="input()">g(x)=</MathLiveInput>

Be sure to register the component in the components section or use Vue.component()

Also, initialize the data fields, this will enable the keyboard for easy editing.

formula: 'h(x)',
 config:{
      smartMode: true,
      virtualKeyboardMode: "manual",
 }

Here is the complete code for easy reference.

<template>
  <div id="app">
    <h1>Hello Mathematical Equations</h1>

    <p>I wrote $$e^{i\pi} + 1 = 0$$. Can you type it? </p>
    <div style="width:50%;border:#aeaeae;background:#eeeeee">
<MathLiveInput :config="config" v-model="formula" v-on:input="input()">g(x)=</MathLiveInput>

    </div>
      <!-- <div id='output'>{{formula}}</div> -->
  </div>
</template>

<script>
import MathLive from "mathlive";
import MathLiveInput from "./components/MathLiveInput.vue";

export default {
  name: "app",
  components: {
    MathLiveInput
  },
  data: function() {
    return {
      formula: 'h(x)',
    config:{
      smartMode: true,
      virtualKeyboardMode: "manual",
    }
     }
  },
  mounted: function() {
    MathLive.renderMathInDocument();
  },
  methods: {
    input : function() {
      console.log(this.formula)
    }
  }
};


</script>

<style>
#app {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

Conclusion

I used Mathlive’s npm module to import Mathlive.io functionality into the VueJS app. It allows users to edit the complex mathematical equations without any fuss.

If you find any difficulty in implementing mathematical equations in VueJS. Then please reach out to me at Twitter @MohitSehgl.

Keep Coding. Good Luck 🙂