Fabricjs custom serialization - fabricjs

I would like to create a canvas in which I would load images of some entity. the images of these entities could change from time to time.
FabricJs provides a built in serialization & de-serialization mechanism using following methods
fabric.Canvas#toJSON to serialize the canvas and
fabric.Canvas#loadFromJSON to de-serialize it but the problem with them is they serialize the image source as well which is enormous and useless in my case
(fabric.Canvas#toDatalessJSON & fabric.Canvas#loadFromDatalessJSON seems to work only for complex object but not images gitHub Ref)
whats is the approach to this?
should customize serialization by serializing all the required properties and recreate objects based on those info?
I have even try to create a subclass of Image removing src from toObject to avoid serializing it but it seems that does not work as well
here is what I have tried with subclass
fabric.MyImage = fabric.util.createClass(fabric.Image, {
type: 'MyImage',
initialize: function(options) {
options || (options = { })
this.callSuper('initialize', options)
this.set('artIndexes', options.artIndexes || '')
},
toObject: function() {
const TO = this.callSuper('toObject')
fabric.util.object.extend(TO, {
artIndexes: this.get('artIndexes')
})
delete TO.src
return TO
},
_render: function(ctx) {
this.callSuper('_render', ctx)
}// ,
// fromURL: function(a, b) {
// this.callSuper('fromURL', a, b)
// }
})
fabric.MyImage.fromObject = function (object, callback) {
fabric.util.enlivenObjects(object.objects, function (enlivenedObjects) {
delete object.objects
callback && callback(new fabric.MyImage(enlivenedObjects, object))
})
}
fabric.MyImage.async = true
and to add Image i would do somthing like this
let img = new fabric.MyImage({entetyReference: entetyRef})
img.setSrc(`${imageSource}`, image => {
this.canvas.add(img)
})
but during serialization and de-serializatiin using FabricJs provided method the object get lost from canvas
I am using latest version of fabricJs

Related

Redux Toolkit - Slice utility methods

I'm building a React app with redux-toolkit and I'm splitting my store into some slices with redux-toolkit's helper function createSlice.
Here it is a simple use case:
const sidebar = createSlice({
name: "sidebar",
initialState:
{
menus: {}, // Keep track of menus states (guid <-> open/close)
visible: true
},
reducers:
{
show(state, action)
{
state.visible = action.payload.visible;
},
setMenuOpen(state, action)
{
const { id, open } = action.payload;
state.menus[id] = open;
return state;
}
}
});
export default sidebar;
Everything works fine until I "add" actions (that change the store) to the slice but consider your team looking for an utility function "getMenuOpen": this method doesn't change the store (it's not an action and cannot be addeded to reducers object). You can of course read directly the data from the store (state.menus[<your_id>]) but consider a more complex example where manipulating the data requires some library imports, more complex code, etc...I want to modularize/hide each slice as much as possible.
Actually I'm using this workaround:
const sidebar = createSlice({ /* Same previous code... */ });
sidebar.methods =
{
getMenuOpen: (state, id) => state.menus[id]
};
export default sidebar;
The above code allows importing the slice from a component, mapStateToProps to the redux store, and invoke the utilty function getMenuOpen like this:
import sidebar from "./Sidebar.slice";
// Component declaration ...
const mapStateToProps = state => ({
sidebar: state.ui.layout.sidebar,
getMenuOpen(id)
{
return sidebar.methods.getMenuOpen(this.sidebar, id);
}
});
const mapDispatchToProps = dispatch => ({
setMenuOpen: (id, open) => dispatch(sidebar.actions.setMenuOpen({id, open}))
});
The ugly part is that I need to inject the slice node (this.sidebar) as fist param of getMenuOpen because it's not mapped (as for actions with reducers/actions) automatically from redux-toolkit.
So my question is: how can I clean my workaround in order to automatically map the store for utility functions? createSlice doesn't seem to support that but maybe some internal redux's api could help me in mapping my "slice.methods" automatically to the store.
Thanks

Fabricjs - clarification with extending toObject method with additional attributes

I got the following code form fabric JS site as mentioned this code is for extending rectangle class with additional property, But I can't understand how it works clearly can someone please explain this piece of code
var rect = new fabric.Rect();
rect.toObject = (function(toObject) {
return function() {
return fabric.util.object.extend(toObject.call(this), {
name: this.name
});
};
})(rect.toObject);
canvas.add(rect);
rect.name = 'trololo';
This code is not extending the toObject method of all the fabric.Rect(s).
Is overwriting the toObject method of your particular instance of fabric.Rect, hosted in the rect var you just created.
(function(toObject) {
return function() {
return fabric.util.object.extend(toObject.call(this), {
name: this.name
});
};
})(rect.toObject);
fabric.util.object.extend is like lodash merge. It takes an object as first argument and adds to it the properties of the object from the second argument.
to.Object.call(this) is calling the function toObject that is the first argument of the wrapping function, that is rect.toObject before being modified.
Fabric support this functionality without modifying the code but using:
rect.toObject(['name']);
Or a nicer way to write this would be:
var originalToObject = fabric.Rect.prototype.toObject;
fabric.Rect.prototype.toObject = function(additionalProps) {
var originalObject = originalToObject.call(this, additionalProps);
originalObject.name = this.name;
return originalObject;
};
In this case can be more clear.

fabric.js canvas to json with svg custom attributes

I added new attributes for path svg:
var canvas = new fabric.Canvas('c');
var group = [];
fabric.loadSVGFromURL(svg_file, function(objects,options) {
var obj = fabric.util.groupSVGElements(objects, options);
obj.set({
left: 100,
top: 100
});
canvas.add(obj);
canvas.renderAll();
},
// add new attributes
function(item, object) {
object.set('id',item.getAttribute('id'));
object.set('tag_id', tag_id);
object.set('elem_id', elem_id );
group.push(object);
}
);
And:
// see new attributes
console.log(canvas.getObjects());
// save to JSON
console.log('json:', canvas.toJSON())
But new attributes not save.
Saving JSON in canvas with fabric.js not work for me.
I read Saving JSON in canvas with fabric.js,
it says to create a new class based on fabric.Image, it works for me,
but what to do with fabric.LoadSVGFromURL and fabric.util.groupSVGElements?
Need to create a new class or something else? Help me.
Solved:
https://github.com/kangax/fabric.js/issues/471
http://jsfiddle.net/Ypn2k/4/
You should try to set custom attribute in this way :
svgelement.toObject = (function (toObject) {
return function () {
return fabric.util.object.extend(toObject.call(this), {
"custom_attr_key": "Value",
"custom_attr_key": "Value",
});
};
})(svgelement.toObject);
and also pass this custom attribute key in toJson function like This :
canvas.toJSON(['custom_attr_key','custom_attr_key']);

FabricJs - How do I Add properties to each object

I need to introduce few additional properties to existing object properties set.
like:
ID
Geo Location
etc
Whenever I draw a shape, I need to add additional properties to the shape and need to get from toDataLessJSON()
As of version 1.7.0 levon's code stopped working. All you need to do is to fix as follows:
// Save additional attributes in Serialization
fabric.Object.prototype.toObject = (function (toObject) {
return function (properties) {
return fabric.util.object.extend(toObject.call(this, properties), {
textID: this.textID
});
};
})(fabric.Object.prototype.toObject);
You have to receive properties argument and pass it on to toObject.
Here's a code for adding custom properties and saving them in JSON serialization for any object on canvas. (I used standard javascript object properties, but it works for me)
canvas.myImages = {};
fabric.Image.fromURL('SOME-IMAGE-URL.jpg', function(img) {
canvas.myImages.push(img);
var i = canvas.myImages.length-1;
canvas.myImages[i].ID = 1; // add your custom attributes
canvas.myImages[i].GeoLocation = [40, 40];
canvas.add(canvas.myImages[i]);
canvas.renderAll();
});
And you then include the custom attribute in object serialization.
// Save additional attributes in Serialization
fabric.Object.prototype.toObject = (function (toObject) {
return function () {
return fabric.util.object.extend(toObject.call(this), {
textID: this.textID
});
};
})(fabric.Object.prototype.toObject);
// Test Serialization
var json = JSON.stringify(canvas.toDatalessJSON());
console.log(json);
canvas.clear();
// and load everything from the same json
canvas.loadFromDatalessJSON(json, function() {
// making sure to render canvas at the end
canvas.renderAll();
}

Is it possible to pass a model to a layout in Express?

I know that it is possible to pass a model to a view in express by doing something like this:
exports.locations = function(req, res){
Location.find(function(err, results) {
res.render('locations', { title: 'Locations', locations: results });
});
};
But is it possible to pass a model to my layout?
Assuming you have all (relevant) routes inside a single .js file, you could add a function like this:
function applyGlobals(pageModel) {
pageModel.myGlobalThing = "I'm always available";
pageModel.anotherGlobalThing = 8675309;
return(pageModel);
}
exports.locations = function(req, res){
Location.find(function(err, results) {
res.render('locations', applyGlobals({ title: 'Locations', locations: results }));
});
};
You could also create a more generalizable solution:
function Globalizer(baseContent) {
var theFunc = function(specificContent) {
var keys = Object.keys(baseContent);
for (var i = 0; i < keys.length; i++)
{
// the lets the page content override global content by not
// overwriting it if it exists;
if(!specificContent.hasOwnProperty(keys[i])){
specificContent[keys[i]] = baseContent[keys[i]];
}
}
return specificContent;
};
return theFunc;
};
// And use it like so.
var applyGlobals = new Globalizer({global1: 12, global2: 'otherthing'});
var pageVars = applyGlobals({item1: 'fifteen', 'item2': 15, global2: 'override'});
console.log(require('util').inspect(pageVars));
Which would emit:
{ item1: 'fifteen',
item2: 15,
global2: 'override',
global1: 12 }
Similarly, you could use one of the various mixin, extend assign or similar functions of various libraries like lodash, underscore, etc. See the doc for lodash.assign() which illustrates accomplishing the same sort of thing.
UPDATE One more way of doing it.
You might want to check out Express' app.locals documentation as well - it might work well for you.

Resources