database - Fabric.js: cómo guardar lienzo en el servidor con atributos personalizados
json savechanges (4)
Me gustaría poder guardar el estado del lienzo actual en una base de datos del lado del servidor, probablemente como una cadena JSON, y luego restaurarlo con loadFromJSON
. Típicamente, esto se logra fácilmente usando:
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'');
}
Y entonces
function loadCanvas(json) {
// parse the data into the canvas
canvas.loadFromJSON(json);
// re-render the canvas
canvas.renderAll();
// optional
canvas.calculateOffset();
}
Sin embargo, también he estado configurando algunos atributos personalizados en los objetos de estructura que estoy agregando al lienzo utilizando el método de Object#set
incorporado:
// 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();
El problema es que cuando consulto la solicitud en el lado del servidor, veo que mis atributos personalizados no se analizaron desde el lienzo y se enviaron junto con todo lo demás. Esto probablemente tiene que ver con cómo el método toObject
elimina cualquier cosa que no sea un atributo predeterminado en la clase de objeto. ¿Cuál sería una buena manera de abordar este problema, de forma que pueda guardar y restaurar el lienzo desde una cadena JSON enviada por el servidor, y el lienzo restaurado también incluirá mis atributos personalizados? Gracias.
Buena pregunta.
Si agrega propiedades personalizadas a los objetos, es probable que esos objetos sean "especiales" de alguna manera. Parece que subclasarlos sería una solución razonable.
Por ejemplo, así es como fabric.Image
una fabric.Image
. Imagen en una imagen con nombre. Esos objetos de imagen podrían tener nombres como "Gandalf" o "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 });
}
});
Primero, damos un tipo a estos objetos. Este tipo es utilizado por loadFromJSON
para invocar automáticamente fabric.<type>.fromObject
method. En este caso, sería fabric.NamedImage.fromObject
.
A continuación, sobrescribimos el método de instancia de initialize
(constructor) para establecer también la propiedad "nombre" al inicializar un objeto (si se proporciona esa propiedad).
A continuación, sobrescribimos el método de instancia de toObject
para incluir "nombre" en el objeto devuelto (esta es una piedra angular de la serialización de objetos en el tejido).
Finalmente, también necesitaremos implementar ese fabric.NamedImage.fromObject
que mencioné anteriormente, para que loadFromJSON
sepa qué método invocar durante el análisis JSON:
fabric.NamedImage.fromObject = function(object, callback) {
fabric.util.loadImage(object.src, function(img) {
callback && callback(new fabric.NamedImage(img, object));
});
};
Estamos cargando una imagen aquí (desde "object.src"), y luego creando una instancia de fabric.NamedImage
fuera de ella. Observe cómo en ese punto, el constructor ya se ocupará de la configuración de "nombre", ya que sobrescribimos el método de "inicialización" más temprano.
Y también necesitaremos especificar que fabric.NamedImage
es una "clase" asíncrona, lo que significa que fromObject
no devuelve una instancia, sino que la pasa a una devolución de llamada:
fabric.NamedImage.async = true;
Y ahora podemos intentarlo todo:
// 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);
});
Guau. ¿Me estoy perdiendo de algo?
Lo he hecho muchas veces y no necesita subclases sofisticadas.
Los documentos lo cubren: http://fabricjs.com/docs/fabric.Canvas.html#toJSON
Simplemente incluya una matriz de nombres de propiedades como cadenas en su llamada a toJSON ().
P.ej
canvas.toJSON([''wizard'',''hobbit'']);
Con suerte ... para obtener puntos de bonificación, puede agregar una función reviver que rehidratará sus atributos personalizados.
De nuevo, esto está cubierto en los documentos y tiene un ejemplo.
Tuve el mismo problema pero no quería extender las clases de fabric.js.
Escribí una función que toma el lienzo de tela en el parámetro y devuelve una versión codificada con mis atributos especiales:
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);
}
Los atributos especiales parecen importados correctamente cuando uso canvas.loadFromJSON()
, estoy usando fabric 1.7.2
.
Un enfoque más simple sería agregar las propiedades post-stringify:
var stringJson = JSON.stringify(this.canvas);
var objectJson = JSON.parse(string.Json);
//remove property1 property
delete objectJson.property1;
//add property2 property
delete objectJson.property2;
// stringify the object again
stringJson = JSON.stringify(objectJson);
// at this point stringJson is ready to be sent over to the server
$http.post(''http://serverurl/'',stringJson);