Emile Haas
Emile Haas

Reputation: 440

How do I reload custom fabricJS canvas objects from JSON?

I'm working w/ fabricJS and custom classes.

I'm having issues w/ canvas.loadFromJSON() so I took a basic MRE from another question to figure out what's wrong with my custom fabric classes. The example in the question works fine, but it creates a class based on fabric.Rect.

I need a custom class based on fabric.Polyline. I modified the code a bit and got this :

var canvas = new fabric.Canvas('c', {
});

// my custom TreeNode class
fabric.TreeNode = fabric.util.createClass(fabric.Polyline, {
    type: 'treeNode',

    initialize: function (options) {
        options || (options = {});
        // console.log(options);

        this.callSuper('initialize', options);
        this.stroke = 'blue';
    },

    toObject: function () {
        return fabric.util.object.extend(this.callSuper('toObject'), {
            stroke: this.get('stroke')
        });
    },

    _render: function (ctx) {
        this.callSuper('_render', ctx);
    }
});

fabric.TreeNode.fromObject = function (object, callback) {
    return fabric.Object._fromObject('TreeNode', object, callback);
};


var rect = new fabric.Rect({
    left: 300,
    top: 200,
    fill: 'blue',
    width: 50,
    height: 50,
});
canvas.add(rect);

var node = new fabric.TreeNode([{ x: 50, y: 100 }, { x: 200, y: 300 }, { x: 10, y: 10 }]);
canvas.add(node);

// Serializing the whole canvas to JSON
var myJSON = JSON.stringify(canvas);

// console.log(myJSON);
// '{"version":"4.3.1","objects":[{"type":"rect","version":"4.3.1","originX":"left","originY":"top","left":300,"top":200,"width":50,"height":50,"fill":"blue","stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeUniform":false,"strokeMiterLimit":4,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"rx":0,"ry":0},{"type":"treeNode","version":"4.3.1","originX":"left","originY":"top","left":9.5,"top":9.5,"width":190,"height":290,"fill":"rgb(0,0,0)","stroke":"blue","strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeUniform":false,"strokeMiterLimit":4,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"points":[{"x":50,"y":100},{"x":200,"y":300},{"x":10,"y":10}]}]}'


// De-serializing doesn't work if fabric.TreeNode object is part of canvas
canvas.loadFromJSON(myJSON, canvas.renderAll.bind(canvas));
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.3.1/fabric.js"
        integrity="sha512-CzyxOXSECwo2nGJQZ2P8fdDxWVMVzn0jNdT2lYJ3afbqDJbaQ4qxgv9sLc85f6KP8HO2y929Bu+lnPKGC3ofSg=="
        crossorigin="anonymous"></script>

</head>

<body>
    <canvas id="c" width="1000" height="600" style="border: 1px solid rgb(219, 40, 40);"></canvas>
    <!-- <script src="./loadJSON.js"></script> -->
</body>

</html>

As you can see, when trying to load back canvas from JSON I get a Uncaught TypeError: Cannot read property 'x' of undefined at find (fabric.js:2469) at min (fabric.js:2444)

On the browser I can inspect the origin of error in source code, but I can't figure out what is the problem. Any help much appreciated. enter image description here

Upvotes: 1

Views: 2263

Answers (1)

Emile Haas
Emile Haas

Reputation: 440

I posted my question on the fabricJS gitHub page (link here), and @asturur was kind enough to point out I had to change the fromObject function.

the fromObject for the polyline specifically is:

>   fabric.Polyline.fromObject = function(object, callback) {
>     return fabric.Object._fromObject('Polyline', object, callback, 'points');   };

Try to have the correct initialize function and a fromObject function that refer to points on the last argument, i assume it could fix your issues

So, with that said I modified my snippet, and now the loadFromJSON() works just fine. Example here :

var canvas = new fabric.Canvas('c', {
});

// my custom TreeNode class
fabric.TreeNode = fabric.util.createClass(fabric.Polyline, {
    type: 'treeNode',

    initialize: function (options) {
        options || (options = {});
        // console.log(options);

        this.callSuper('initialize', options);
        this.stroke = 'blue';
    },

    toObject: function () {
        return fabric.util.object.extend(this.callSuper('toObject'), {
            stroke: this.get('stroke')
        });
    },

    _render: function (ctx) {
        this.callSuper('_render', ctx);
    }
});

fabric.TreeNode.fromObject = function (object, callback) {
    // ** CHANGE : using the _fromObject() of Polyline
    return fabric.Object._fromObject('Polyline', object, callback, 'points');
};


var rect = new fabric.Rect({
    left: 300,
    top: 200,
    fill: 'blue',
    width: 50,
    height: 50,
});
canvas.add(rect);

var node = new fabric.TreeNode([{ x: 50, y: 100 }, { x: 200, y: 300 }, { x: 10, y: 10 }]);
canvas.add(node);

// Serializing the whole canvas to JSON
var myJSON = JSON.stringify(canvas);

console.log(myJSON);
// {"version":"4.3.1","objects":[{"type":"rect","version":"4.3.1","originX":"left","originY":"top","left":300,"top":200,"width":50,"height":50,"fill":"blue","stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeUniform":false,"strokeMiterLimit":4,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"rx":0,"ry":0},{"type":"treeNode","version":"4.3.1","originX":"left","originY":"top","left":9.5,"top":9.5,"width":190,"height":290,"fill":"rgb(0,0,0)","stroke":"blue","strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeUniform":false,"strokeMiterLimit":4,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"points":[{"x":50,"y":100},{"x":200,"y":300},{"x":10,"y":10}]}]}


// De-serializing now works for both rect and TreeNode objects
canvas.loadFromJSON(myJSON, canvas.renderAll.bind(canvas));
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.3.1/fabric.js"
        integrity="sha512-CzyxOXSECwo2nGJQZ2P8fdDxWVMVzn0jNdT2lYJ3afbqDJbaQ4qxgv9sLc85f6KP8HO2y929Bu+lnPKGC3ofSg=="
        crossorigin="anonymous"></script>

</head>

<body>
    <canvas id="c" width="1000" height="600" style="border: 1px solid rgb(219, 40, 40);"></canvas>
    <!-- <script src="./loadJSON.js"></script> -->
</body>

</html>

Upvotes: 1

Related Questions