Vue example app with TypeScript and SCSS

JointJS+ is a standard JavaScript library that you can use in any JavaScript framework. In this tutorial, we will show how you can use JointJS+ in your Vue apps. The tutorial will go over the typical process of creating a Vue app, setting up the build process, and integrating JointJS+ components into your Vue app. The resulting very simple demo app will display the JointJS+'s Paper and PaperScroller components, and render a rectangle element to that paper area. Similarly, you can add any other JointJS+ UI components and build up a fully-featured diagramming application. Let's go!

The source code for the following tutorial can be found here. Looking for a different version of your favourite framework? Be sure to checkout the different branches of the repository to see if you find what you need.

Prerequisites

Create a Vue project

Let's create a new project from the Vue CLI. Simply run:

vue create vue-rappid

You will be prompted to choose a preset and a few options:

After a couple of seconds, a new Vue project should be created. Get started with the following commands:

cd vue-rappid
npm run serve

The app should be ready and running at http://localhost:8080/

Add JointJS+ to the Vue project

Firstly, we need to add JointJS+ to our dependencies, and as we are using TypeScript, we will also add some "@types" to our devDependencies. For this tutorial, we will place our installable "rappid.tgz" package in the root directory of our Vue app. After that is completed, we can update our existing dependencies and devDependencies in our package.json file with the following:

"dependencies": {
    ...
    "@clientio/rappid": "file:rappid.tgz", // path to file
    ...
},
"devDependencies": {
    ...
    "@types/backbone": "latest", // "latest" value will install the latest package version
    "@types/jquery": "latest",
    ...
}

Then, run npm install.

Next, open the "tsconfig.json" file in the root project directory, and in the "compilerOptions" set "strictPropertyInitialization" to false, and "skipLibCheck" to "true" as there is no need for the TS compiler to check the JointJS+ library:

"compilerOptions": {
    ...
    "strictPropertyInitialization": false,
    "skipLibCheck": true,
    ...
},

Specific Cases

When working with DirectedGraphs, you need to add some additional dependencies to your app. Additionally, if you would like to overwrite any dependencies used in JointJS+, you can add a specific package version.

"dependencies": {
    ...
    "@clientio/rappid": "file:rappid.tgz",
    "dagre": "latest", // "latest" value will install the latest package version
    "graphlib": "latest",
    "lodash": "4.17.11", // use specific package version
    ...
},

"devDependencies": {
    ...
    "@types/dagre": "latest",
    "@types/graphlib": "latest",
    ...
},

Build your app

Before we start, let's clean up our project a little bit:

After completing those steps, your "App.vue" should look like the following:

<template>
</template>

<script lang="ts">
    import { Options, Vue } from 'vue-class-component';

    @Options({})
    export default class App extends Vue {}
</script>

<style lang="scss">
</style>

Now, let's add our canvas container. Inside the "<template>" tags, add an empty "div" with a class of "canvas", and a ref attribute with a value of "canvas". Ref is a Vue instance property that will allow us to access an HTML element from the script. More information on ref can be found here: docs

Our template will now look like this:

<template>
    <div class="canvas" ref="canvas"></div>
</template>

It's time to add some basic styling. Change your "<style></style>" section to:

<style lang="scss">
    @import "~@clientio/rappid/rappid.css";

    body {
    height: 100vh;
    box-sizing: border-box;
    margin: 0;

    .canvas {
        width: 100%;
        height: 100%;

        .joint-paper {
            border: 1px solid #A0A0A0;
        }
      }
    }
</style>

Notice the import of the JointJS+ stylesheet at the top of the section. The prefix "~" at the start of the path instructs the Webpack loader to resolve the import from a node module path. Now let's move to the "<script>" section.

In "App.vue", import the JointJS+ dia, ui and shapes namespaces:

import { dia, ui, shapes } from '@clientio/rappid';

After that, inside the component class add:

declare public $refs: {
   canvas: HTMLDivElement;
}

private graph: dia.Graph;
private paper: dia.Paper;
private scroller: ui.PaperScroller;

The property "$refs" is an extension that will allow us to assign a type to our ref. We also add our class properties for Graph, Paper, and PaperScroller. More information about "$refs" can be found here.

It's time to create our JointJS+ components. First of all, let's add a Vue lifecycle method called "created". More information about the Vue lifecycle and hooks can be found at the links provided.

<script lang="ts">
   import { Options, Vue } from 'vue-class-component';
   import { dia, ui, shapes } from '@clientio/rappid';

   @Options({})
   export default class App extends Vue {
       declare public $refs: {
           canvas: HTMLDivElement;
       }

       private graph: dia.Graph;
       private paper: dia.Paper;
       private scroller: ui.PaperScroller;

       public created(): void {

       }
   }
</script>

Inside the "created" method add Graph, Paper, and PaperScroller like this:

const graph = this.graph = new dia.Graph({}, { cellNamespace: shapes });

const paper = this.paper = new dia.Paper({
    model: graph,
    background: {
        color: '#F8F9FA',
    },
    frozen: true,
    async: true,
    sorting: dia.Paper.sorting.APPROX,
    cellViewNamespace: shapes
});

const scroller = this.scroller = new ui.PaperScroller({
    paper,
    autoResizePaper: true,
    cursor: 'grab'
});

Resources:

Now, let's render our PaperScroller component, create a simple rectangle shape, and add it to our graph. Add the following code inside the "created" method:

scroller.render();

const rect = new shapes.standard.Rectangle({
    position: { x: 100, y: 100 },
    size: { width: 100, height: 50 },
    attrs: {
        label: {
            text: 'Hello World'
        }
    }
});

this.graph.addCell(rect);

Next, we can use another lifecycle method called "mounted". Add it after the "created" method.

public mounted(): void {

}

Inside the "mounted" method, we are able to use our "refs" to work with DOM elements. Append the created JointJS+ PaperScroller component, center it, and also unfreeze the Paper.

const { scroller, paper, $refs : { canvas } } = this;
canvas.appendChild(this.scroller.el);
scroller.center();
paper.unfreeze();

The final code in "App.vue" should look like the following:

<template>
    <div class="canvas" ref="canvas"></div>
</template>

<script lang="ts">
import { Options, Vue } from 'vue-class-component';
import { dia, ui, shapes } from '@clientio/rappid';

@Options({})
export default class App extends Vue {

    declare public $refs: {
        canvas: HTMLDivElement;
    }
    private graph: dia.Graph;
    private paper: dia.Paper;
    private scroller: ui.PaperScroller;

    public created(): void {
        const graph = this.graph = new dia.Graph({}, { cellNamespace: shapes });

        const paper = this.paper = new dia.Paper({
            model: graph,
            background: {
                color: '#F8F9FA',
            },
            frozen: true,
            async: true,
            sorting: dia.Paper.sorting.APPROX,
            cellViewNamespace: shapes
        });

        const scroller = this.scroller = new ui.PaperScroller({
            paper,
            autoResizePaper: true,
            cursor: 'grab'
        });

        scroller.render();

        const rect = new shapes.standard.Rectangle({
            position: { x: 100, y: 100 },
            size: { width: 100, height: 50 },
            attrs: {
                label: {
                    text: 'Hello World'
                }
            }
        });

        this.graph.addCell(rect);
    }

    public mounted(): void {
        const { scroller, paper, $refs : { canvas } } = this;
        canvas.appendChild(this.scroller.el);
        scroller.center();
        paper.unfreeze();
    }
}
</script>

<style lang="scss">
    @import "~@clientio/rappid/rappid.css";

    body {
    height: 100vh;
    box-sizing: border-box;
    margin: 0;

    .canvas {
        width: 100%;
        height: 100%;

        .joint-paper {
            border: 1px solid #A0A0A0;
        }
    }
}
</style>
          
    

Everything is ready! Now run npm run serve to host your application. Your demo app should look like this:

exampleApp