Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I'd like to be able to save the current canvas' state to a server-side database, probably as a JSON string, and then later restore it with loadFromJSON. Typically, this is easily accomplished using:

var canvas = new fabric.Canvas(); function saveCanvas() {     // convert canvas to a json string     var json = JSON.stringify( canvas.toJSON() );      // save via xhr     $.post('/save', { json : json }, function(resp){          // do whatever ...     }, 'json'); } 

And then

function loadCanvas(json) {    // parse the data into the canvas   canvas.loadFromJSON(json);    // re-render the canvas   canvas.renderAll();    // optional   canvas.calculateOffset(); } 

However, I've also been setting a few custom attributes on the fabric objects I'm adding to the canvas using the builtin Object#set method:

// get some item from the canvas var item = canvas.item(0);  // add misc properties item.set('wizard', 'gandalf'); item.set('hobbit', 'samwise');  // save current state saveCanvas(); 

The problem is that when I check the request on the server-side, I see that my custom attributes were not parsed from the canvas and sent along with everything else. This probably has to do with how toObject method removes anything that's not a default attribute in the object class. What would be a good way to tackle this issue, such that I'll be able to both save and restore the canvas from a JSON string sent by the server, and the restored canvas will also include my custom attributes? thanks.

like image 788
sa125 Avatar asked Jun 30 '12 08:06

sa125


1 Answers

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 element var img = document.createElement('img'); img.src = 'https://www.google.com/images/srpr/logo3w.png';  // create an instance of named image var namedImg = new fabric.NamedImage(img, { name: 'foobar' });  // add it to canvas canvas.add(namedImg);  // save json var json = JSON.stringify(canvas);  // clear canvas canvas.clear();  // and load everything from the same json canvas.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); }); 
like image 162
kangax Avatar answered Sep 18 '22 17:09

kangax