Fabric.js - how to save canvas on server with custom attributes Fabric.js - how to save canvas on server with custom attributes json json

Fabric.js - how to save canvas on server with custom attributes


Good question.

If you're adding custom properties to objects, those objects are likely "special" in some way. It seems like subclassing them would be a reasonable solution.

For example, here's how we would subclass a fabric.Image into a named image. Those image objects could then have names like "Gandalf" or "Samwise".

fabric.NamedImage = fabric.util.createClass(fabric.Image, {  type: 'named-image',  initialize: function(element, options) {    this.callSuper('initialize', element, options);    options && this.set('name', options.name);  },  toObject: function() {    return fabric.util.object.extend(this.callSuper('toObject'), { name: this.name });  }});

First, we give these objects a type. This type is used by loadFromJSON to automatically invoke fabric.<type>.fromObject method. In this case it would be fabric.NamedImage.fromObject.

Then we overwrite initialize (constructor) instance method, to also set "name" property when initializing an object (if that property is given).

Then we overwrite toObject instance method to include "name" in returned object (this is a cornerstone of object serialization in fabric).

Finally, we'll also need to implement that fabric.NamedImage.fromObject that I mentioned earlier, so that loadFromJSON would know which method to invoke during JSON parsing:

fabric.NamedImage.fromObject = function(object, callback) {  fabric.util.loadImage(object.src, function(img) {    callback && callback(new fabric.NamedImage(img, object));  });};

We're loading an image here (from "object.src"), then creating an instance of fabric.NamedImage out of it. Note how at that point, constructor will already take care of "name" setting, since we overwrote "initialize" method earlier.

And we'll also need to specify that fabric.NamedImage is an asynchronous "class", meanining that its fromObject does not return an instance, but passes it to a callback:

fabric.NamedImage.async = true;

And now we can try this all out:

// create image elementvar img = document.createElement('img');img.src = 'https://www.google.com/images/srpr/logo3w.png';// create an instance of named imagevar namedImg = new fabric.NamedImage(img, { name: 'foobar' });// add it to canvascanvas.add(namedImg);// save jsonvar json = JSON.stringify(canvas);// clear canvascanvas.clear();// and load everything from the same jsoncanvas.loadFromJSON(json, function() {  // making sure to render canvas at the end  canvas.renderAll();  // and checking if object's "name" is preserved  console.log(canvas.item(0).name);});


Wow. Am I missing something here?

I've done this plenty of times and it doesn't need any fancy subclassing.

The docs cover it: http://fabricjs.com/docs/fabric.Canvas.html#toJSON

Just include an array of property names as strings in your call to toJSON().

Eg

canvas.toJSON(['wizard','hobbit']);

Hopefully.... for bonus points you can add a reviver function which will rehydrate your custom attributes.

Again this is covered in the docs and has an example.


I had the same issue but I didn't want to extend the fabric.js classes.

I wrote a function that takes the fabric canvas in parameter and returns a stringified version with my special attributes:

function stringifyCanvas(canvas){    //array of the attributes not saved by default that I want to save    var additionalFields = ['selectable', 'uid', 'custom'];     sCanvas = JSON.stringify(canvas);    oCanvas = JSON.parse(sCanvas) ;    $.each(oCanvas.objects, function(n, object) {        $.each(additionalFields, function(m, field) {            oCanvas.objects[n][field] = canvas.item(n)[field];        });    });    return JSON.stringify(oCanvas);     }

The special attributes seems properly imported when I use canvas.loadFromJSON(), I'm using fabric 1.7.2.