Fabric: error loading subclass object from JSON - fabricjs

In this jsfiddle I have a line that is created from a subclass (it's basically a line with two additional attributes). I'm trying to serialize/deserialize the line to/from JSON. I can serialize with no problems (including the two additional attributes) but when I try to deserialize with loadFromJSON I get the Cannot read property 'async' of undefined exception.
Uncomment the last line in the jsfiddle to get the error.
I implemented the fromObject() method, but I'm not sure it's correct. What's wrong with this code?
Javascript:
var canvas = new fabric.Canvas('c');
fabric.PolySegment = fabric.util.createClass(fabric.Line, {
type: 'seg',
initialize: function(points,options) {
options || (options = { });
this.callSuper('initialize', points,options);
this.set('poly', options.poly);
this.set('id', options.id);
},
toObject: function() {
return fabric.util.object.extend(this.callSuper('toObject'), {
poly: this.get('poly'),
id: this.get('id')
});
},
_render: function(ctx) {
this.callSuper('_render', ctx);
}
});
fabric.PolySegment.fromObject = function (object, callback) {
return new fabric.PolySegment(object);
};
fabric.PolySegment.async = true;
var coords1 = [ 10, 10, 100, 100 ];
var seg1 = new fabric.PolySegment(coords1, {
stroke: 'black',
strokeWidth: 6,
originX: 'left',
originY: 'top',
poly: 111111,
id: 222222
});
canvas.add(seg1);
var json = JSON.stringify(canvas);
document.getElementById('t').innerHTML = json;
// uncomment the line below, you'll get an exception
//canvas.loadFromJSON(json);

In looking at your Fiddle, it seems like the issue is the type value you have given your new class. If I change:
type: 'seg',
to
type: 'polySegment',
and uncomment the last line, I don't get the error. However, I also don't get the line to load on the canvas. But I guess I'm not 100% sure what is happening in your script there - you're loading the polySegment, then converting it to JSON and reloading it? I'm assuming this is merely for illustration purposes.
While what I suggest here gets rid of your error, I'm personally not 100% sure why it works. In reading the FabricJS documentation on Subclassing, it doesn't specify that the subclass type needs to match that of the subclass definition, but it seems to work...
I hope that helps in some way.
One last comment about this, I've always used the toJSON method to transfer a canvas element to JSON. Though this retrieves a JSON object, not a string like you are doing. Also, the toJSON method requires that you specify what properties you want to capture, so maybe this isn't the best method for your case. It's discussed on this page. But my point for bringing this up, is I know for certain that the toJSON method works well with the loadFromJSON method. So I mention that in case you find the JSON.stringify method to be the issue, there is a alternate method to approach the same concept.

Related

change tabulator's placeholder at runtime

I bumped into the problem of how to change tabulator's placeholder at runtime. As it is suggested to ask questions on StackOverflow, here it is. There is an issue (closed - https://github.com/olifolkerd/tabulator/issues/1415) having a suggested solution that I tried. Unfortunately, it throws an error during creation:
tabulator.min.js:4 Uncaught TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.
at s.redraw (tabulator.min.js:4)
at e.h.redraw (tabulator.min.js:6)
at ResizeObserver.<anonymous> (tabulator.min.js:12)
tabulator version is 4.9.3. jsfiddle: https://jsfiddle.net/ivos/6pq75brv/3/
the setup is simple:
var data = [{id:1}, {id:2}, {id:3}];
var placeholder = $("<span>Waiting for data</span>");
var conf = {
// placeholder: "Waiting for data",
placeholder: placeholder,
columns: [
{ title: "Id", field: "id", headerFilter: "input" },
],
dataFiltered: (filters, rows) =>{
placeholder.text(filters.length > 0 ? 'No Results': 'Waiting for data');
},
};
var t = new Tabulator('#tbl', conf);
setTimeout(function(){ t.setData(data)}, 5000); // timeout is just to show the initial placeholder
If I apply a filter (i.e. 5) I got
Uncaught TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.
at s.redraw (tabulator.min.js:4)
at e.h.redraw (tabulator.min.js:6)
at ResizeObserver.<anonymous> (tabulator.min.js:12)
Because Tabulator uses a virtual DOM you cannot manipulate elements from outside of it.
There is no built in way to change the place holder option once the table has been set.
That being said i can certainly point you to a very hacky way of achieving this, that i would recommend against because it manipulates internal variables and that may result in it not working in future, but it should achieve what you are hoping for.
So assuming you are creating your table on a variable called table, the following should work:
table.options.placeholder = "new placeholder message";

How to set target anchor when creating a link

I'm trying to set a specific anchor point when creating a link. I believe I'm doing everything correctly, but the anchor options are being ignored. In fact, any options I pass in are being ignored.
My code looks something like this:
new joint.shapes.standard.Link().target({id: 'xxx'}, {
anchor: {
name: 'center',
args: { dy: -15 }
}
});
The target id is being correctly handled, but whatever I pass in the second parameter is totally ignored.
Has anyone come across this before?
After experimenting, I worked out that when passing an object with id, rather than parsing a target element, that the opts need to go inside the object with the id. This is not documented AFAIK.
i.e.
.target({id: element.id, opts})
In my specific case, I'm passing the following:
.target({ id: to.id, anchor: { name: 'center', args: { dy: -15 }}})
This seems to work correctly

How to observe all object property changes?

For arrays I know you can do something like this:
function() {
}.observes("array.#each")
What I did was convert the object into an array and observe the properties with a #each, but is there a better way to observe object all property changes without converting it into an array?
You can observe isDirty to see if any of the object's values have been modified since last save (if you are using Ember Data).
Alternatively you can pass a comma separated list of properties to observes. This might be long if you have a lot of properties on your object, but will work.
A third approach could be to override setUnknownProperty() and set a property, a 'dirty flag' (or perform any action you may want in there.
There's also an old SO post that gives the following answer:
App.WatchedObject = Ember.Object.extend({
firstProp: null,
secondProp: "bar",
init: function(){
this._super();
var self = this;
Ember.keys(this).forEach(function(key){
if(Ember.typeOf(self.get(key)) !== 'function'){
self.addObserver(key, function(){
console.log(self.get(key));
});
}
});
}
});
You could probably split this out into a Mixin to keep your code DRY.
probably you could create something like a blabbermouth mixin and override the set method to get notified of property changes:
App.BlabbermouthMixin = Ember.Mixin.create({
set: function(keyName, value) {
this.set('updatedProperty', keyName);
this._super(keyName, value);
}
});
and observe the updatedProperty property?
You can get a list of properties in an object and apply them to a new property:
attrs = Ember.keys(observedObject);
var c = Ember.computed(function() {
// Do stuff when something changes
})
Ember.defineProperty(target, propertyName, c.property.apply(c, attrs));
Here is a working jsbin. Creating an observer instead of a property should be possible using a similar approach.

Can I add attributes to a Backbone View?

I have been working with backbone for a while and I am now using a number of views. In some of my views I sometimes add custom attributes like:
var DataGrid = Backbone.View.extend({
className:"datagrid",
lookup: {
header: "", //Header wrapper row element
headers: [], //Views in header
body: "", //Body wrapper row element
rows: [] //Views in body
},
events: {
...
},
initialize: function() {
...
},
render: function() {
...
}
});
As you can see I have "lookup" as an extra attribute to the Object. I use DataGrid in a number of my views and I am experiencing a very strange behaviour. When I switch between views that use DataGrid, "lookup" would still be populated with the old data. I use "new" when creating a new DataGrid but I still find old data. Am I missing something?
EDIT: Following #rabs reply. I did a search on static variables in Backbone and found this: Simplify using static class properties in Backbone.js with Coffeescript
I know an answer has been accepted on this (a while ago), but as I came across this question while working on a backbone project recently, I thought it would be worth mentioning that you can define attributes as a function also. This is especially useful for views that need to have attributes set to values in their current models.
By defining attributes as a function you can do something like
var myObject = Backbone.View.extends({
attributes: function() {
if(this.model) {
return {
value: this.model.get('age')
}
}
return {}
}
});
Hope that helps someone
Declaring variables in this way the scope of the variable is to the class not the instance, similar to s static or class variable.
So yeah the lookup object will shared between your different instances.
You could pass the lookup object in to your instance when you create it that way it will behave as an instance variable.

SWFObject Flash not showing

i've tried to get SWFObject register a static flash object, but i wont show it. I know visibility is set to hidden, and that is why, but why does it do that?!
http://manual.businesstool.dk/test.html
Your example uses JavaScript to embed the SWF, it isn't using SWFObject's static approach, so it appears your question is no longer valid.
BTW, you can simplify your code by eliminating the redundant hasFlashPlayerVersion check and by using SWFObject's callback function.
Current:
if (swfobject.hasFlashPlayerVersion("6.0.0")) {
var fn = function() {
swfobject.embedSWF("http://manual.businesstool.dk/gfx/flash/oprettelse-af-kunde.swf", "myReplace", "560px", "340px", "9.0.0");
var obj = swfobject.getObjectById("myReplace");
swffit.fit("myReplace");
console.log(obj);
};
swfobject.addLoadEvent(fn);
}
.
Simplified:
var mycallback = function (e){
swffit.fit(e.ref);
};
swfobject.embedSWF("http://manual.businesstool.dk/gfx/flash/oprettelse-af-kunde.swf", "myReplace", "560px", "340px", "6.0.0", false, false, false, false, mycallback);
.
SWFObject's swfobject.embedSWF method includes a domready check, so you don't need to use addLoadEvent. It also includes a check for a specified version of Flash Player, so you don't need to wrap your code in the swfobject.hasFlashPlayerVersion block.

Resources