Drawing with FabricJS and TypeScript Part 8: Cut/Copy/Paste and Hotkeys
Tags: fabricjs, typescript, dotnet, tutorial
Published: 2020-05-22 18:30:58

We're nearing the end of our [Drawing with FabricJS and TypeScript series](https://dev.to/exceptionnotfnd/drawing-with-fabricjs-and-typescript-part-1-intro-goals-and-setup-1imo). In this part, let's add one last set of improvements by implementing cut, copy, and paste functionality as well as hotkey shortcuts for those, plus undo and redo.

![](https://dev-to-uploads.s3.amazonaws.com/i/knv90cqzflc8c1p8t8do.jpg)<figcaption>Watch your fingers!  Photo by [Igor Miske](https://unsplash.com/@igormiske?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit) / [Unsplash](https://unsplash.com/?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit)</figcaption>

##The Sample Project

Don't forget about [the sample project over on GitHub](https://github.com/exceptionnotfound/FabricJSDrawingWalkthrough)! It contains the final code for this entire series.

##The Copier Class

Let's start with the copier class, which will implement our cut, copy, and paste functionality. The skeleton of this class looks like this:

```javascript
class Copier {
    //During cut or paste, memorizedObject is the object that 
    //will be held in memory and then pasted
    private memorizedObject: fabric.Object

    constructor(private readonly editor: DrawingEditor) { }

    copy(callBack?: Function) { }

    cut() { }

    paste() { }
}
```

We need to fill in each of the methods (other than the constructor, which for this class doesn't have an implementation; it just gets an instance of *DrawingEditor*). 

Let's start with the *copy()* method. When we copy an object, we place a copy of it in memory so that it can be pasted later. In this particular case, we need to get the active object on the FabricJS canvas, clone it, and then set it to the *memorizedObject* property of *Copier*. Plus, we have an optional callback method that needs to be executed, if it is not undefined. Here's how that looks:

```javascript
copy(callBack?: Function) {
    //Get active object on canvas
    const activeObject = this.editor.canvas.getActiveObject();

    //If the active object is not null
    if (activeObject !== null) {
        //Clone the object...
        activeObject.clone((object) => {
            //...and set it as the memorizedObject property
            this.memorizedObject = object;
            
            //When the object is pasted, offset the paste location
            //from the original object.
            object.set({
                left: object.left + 10,
                top: object.top + 10
            });

            //If the callback method exists, call it
            if (callBack !== undefined)
                callBack();
        });
    }
}
```

The callback method becomes immediately useful, because we now need to implement the *cut()* method. Cut is really just copy and then delete, so our *cut()* method will pass a function that deletes the selected object after it is cut. Here's the implementation:

```javascript
cut() {
    this.copy(() => this.editor.deleteSelected());
}
```

The last part of this is the *paste()* method. In this method, we clone the memorized object, add it to the canvas, and re-render the canvas. Here's the code:

```javascript
paste() {
    if (this.memorizedObject !== undefined) {
        this.memorizedObject.clone((clonedObject) => {
            this.editor.canvas.add(clonedObject);
            this.editor.canvas.setActiveObject(clonedObject);
            this.editor.stateManager.saveState();
            this.editor.canvas.renderAll();
        });
    }
}
```

All that's left to do is to make our main *DrawingEditor* class aware of the new *Copier* class...

```javascript
class DrawingEditor {
    private copier: Copier;
    
    //...Other properties

    constructor(private readonly selector: string,
        canvasHeight: number, canvasWidth: number) {
        //...Rest of constructor
        
        this.copier = new Copier(this);
    }
}
```

But wait! There's currently no way the user can actually use this functionality! For cut/copy/paste, we aren't going to add display components to the toolbar, like we did with previous controls. Instead, we're going to enable hotkeys (e.g. Ctrl+X, Ctrl+C, Ctrl+V).

##Hotkeys

Not only will we enable hotkeys for cut, copy, and paste, we'll also enable them for undo (Ctrl+Z) and redo (Ctrl+Y). To start, we need our *DrawingEditor* class to be aware of the key codes for each of the letters. One of the ways we can do this is by using a very neat site called keycode.info. Here's the keycodes for X, C, V, Z, and Y:

```javascript
class DrawingEditor {
    //...Other properties

    private keyCodes = {
        'C': 67,
        'V': 86,
        'X': 88,
        'Y': 89,
        'Z': 90
    }
}
```

TypeScript defines for us a [KeyboardEvent class](https://microsoft.github.io/PowerBI-JavaScript/interfaces/_node_modules_typedoc_node_modules_typescript_lib_lib_dom_d_.keyboardevent.html) which represents an event where the user pressed a key on the keyboard. That event also tells us if the Ctrl key was pressed during the event. We can use this class to initialize our key code events:

```javascript
class DrawingEditor {
    //...Other properties
    
    constructor(private readonly selector: string,
        canvasHeight: number, canvasWidth: number) {
        //...Rest of constructor
        this.initializeKeyCodeEvents();
    }
    
    //...Other methods
    
    private initializeKeyCodeEvents() {
        //When a key is pressed...
        window.addEventListener('keydown', (event: KeyboardEvent) => {
            //...check if the Ctrl key was also pressed...
            if (event.ctrlKey) {
                //...and do different events based on the other key pressed.
                switch (event.keyCode) {
                    case this.keyCodes['Z']:
                        this.undo();
                        break;
                    case this.keyCodes['Y']:
                        this.redo();
                        break;
                    case this.keyCodes['C']:
                        this.copier.copy();
                        break;
                    case this.keyCodes['X']:
                        this.copier.cut();
                        break;
                    case this.keyCodes['V']:
                        this.copier.paste();
                        break;
                }

                event.preventDefault();
                event.stopPropagation();
                return false;
            }
        });
    };
}
```

That's all we need to do for hotkeys! Now the user can cut, copy, paste, undo, and redo using the keyboard shortcuts for those events! This is difficult to put into a GIF (because you wouldn't be able to see what keyboard shortcut I was using), so there won't be one for this part of the series.

##Summary

To implement cut/copy/paste, we needed to:
1. Create a *Copier* class that defines this functionality.
2. Wire that *Copier* class to the *DrawingEditor*.

To implement hotkey shortcuts, we needed to:
1. Create the hotkey initialization method.
2. Call that method in the *DrawingEditor* constructor.

Don't forget to check out the [sample project over on GitHub](https://github.com/exceptionnotfound/FabricJSDrawingWalkthrough)!

In the next and final part of this series, we'll see how to submit the generated JSON of objects on our canvas to the server, so that it can be saved into a database!

Happy Drawing!