Writing spreadsheet with SVG and Vue.js
Tags: svg, vue, javascript
Published: 2018-03-13 18:17:30

---
title: Writing spreadsheet with SVG and Vue.js
published: true
description: 
tags: #SVG #Vue.js #javascript
---

![screenshot](https://thepracticaldev.s3.amazonaws.com/i/8i1spuqz2xqpvxz2v754.gif)

I like [handsontable](https://handsontable.com/), but I want to write my own from scratch.

# DEMO

Try it from here:

 - https://anydown-example-vue-spreadsheet-lite.netlify.com/

Repo: 

 - https://github.com/anydown/vue-spreadsheet-lite


# How to implement

There are so many things to implement. I can't do them all.


## Selection UI

![](https://thepracticaldev.s3.amazonaws.com/i/e87e609dmxc2j4vqrl5f.png)

Because SVG Spec doesn't have z-index for now, I decided to split UI and Contents layer into two svg elements. 

The UI layer has visual elements such as bounding-boxes or selection rects. They have to be always on top.

## In-place editing

![](https://thepracticaldev.s3.amazonaws.com/i/mn4wvm36hpote2t0z75o.png)

There is a hidden text field on selected cell. At first its opacity is set to 0. When the cell is clicked, opacity changes to 1.

This hidden text field always capture key inputs to handle Input Method. This is important to users who uses Chinese characters.

## SVG

SVG is very useful to implement complex GUI. It's just DOM and bindable with Vue's ViewModel. Especially, I like to handle SVG with computed.


```html
<template>
  <div class="grid" @mouseup="onMouseUpSvg()" @mousemove="headerResizeMove">
    <svg :width="positionLeft(data.length + 1) + 1" height=24>
      <g v-for="(col, ci) in headerObj" :key="ci" :transform="translateCol(ci)" @mousedown="startColumnSelect(ci)" @mousemove="changeColumnSelect(ci)" @mouseup="endColumnSelect">
        <rect class="col-header" x=0 y=0 :width="widthAt(ci)" :height="rowHeight">
        </rect>
        <text class="col-header__text" text-anchor="middle" :x="widthAt(ci) / 2" y=12 :width="widthAt(ci)" :height="rowHeight">{{col.name}}</text>
        <rect class="col-header__resize" :class="{'active': ci === headerResizeAt}" :x="widthAt(ci) - 5" :y=0 :width="5" :height="rowHeight" @mousedown.stop="headerResizeStart(ci)"></rect>
      </g>
    </svg>

    <div ref="wrapper" style="height: 400px; overflow: scroll; position:relative;">
      <svg :width="positionLeft(data.length + 1) + 1" :height="data.length * 24" >
        <g v-for="(row, ri) in data" :key="ri" :transform="translateRow(ri)">
          <g v-for="(col, ci) in row" :key="ci" :transform="translateCol(ci)" @mousedown="onMouseDownCell(ci, ri)" @mousemove="onMouseMoveCell(ci, ri)">
            <rect x=0 y=0 :width="widthAt(ci)" :height="rowHeight">
            </rect>
            <text x=2 y=12 :width="widthAt(ci)" :height="rowHeight">{{col}}</text>
          </g>
        </g>
        <rect :transform="selectionTransform" class="selection" x=0 y=0 :width="selectionSize.w" :height="selectionCount.h * rowHeight"></rect>
      </svg>
      <div class="editor__frame" :style="editorStyleObj">
        <input ref="hiddenInput"  @mousedown="onMouseDownCell(selection.c, selection.r)" class="editor__textarea" v-model="editingText" @blur="onBlur" :class="{'editor--visible': editing}" autofocus />
      </div>
    </div>
  </div>
</template>
```

This is an one-file template and [just 413 lines](https://github.com/anydown/vue-spreadsheet-lite/blob/master/src/components/VueSpreadsheetLite.vue). If I use canvas or div to implement this, I think its LOC will be doubled.

## Bundle with bili

[bili](https://github.com/egoist/bili) is a useful tool to distribute SFCs.

I created this project with `vue create`, but its default `.babelrc` seems to prevent build with bili.

According to [this issue](https://github.com/egoist/bili/issues/89), I should use this:

```
bili --plugin vue --no-babel.babelrc
```

**UPDATE 03-14-2018**

This issue has been fixed today, We no longer need `--no-babel.babelrc`. Thanks [EGOIST](https://github.com/egoist)!


then I could publish this to npm.

https://www.npmjs.com/package/@anydown/vue-spreadsheet-lite


# Conclusion

Writing GUI with SVG is so fun!