fabric.js add custom property to IText - javascript

I want to add a custom property to fabricjs.IText, i used a same script i used with my fabricjs.Text class:
fabric.CustomIText = fabric.util.createClass(fabric.IText, {
type : 'custom-itext',
initialize : function(element, options) {
this.callSuper('initialize', element, options);
options && this.set('textID', options.textID);
},
toObject: function() {
return fabric.util.object.extend(this.callSuper('toObject'), {textID: this.textID});
}
});
fabric.CustomIText.fromObject = function(object) {
return new fabric.CustomIText(object.text, object);
};
fabric.CustomIText.async = false;
When I create my new custom-itext there is no problem.
var text = new fabric.CustomIText('NewText', { left: 0, top: 0 , fill: color, fillColor:color, textID: "SommeID"});
canvas.add(text);
But whene I want to load my new CustomItext from a JSON i have a javascrip error:
Uncaught TypeError: Cannot read property 'async' of undefined
Thank you

Here's a code saving additional attributes in serialization for any object on canvas. This might solve your problem, it worked for me
// 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);

I got it to work with async initialization:
fabric.TextAsset = fabric.util.createClass(fabric.IText, {
type: 'textAsset',
initialize: function(element, options) {
this.callSuper('initialize', element, options);
this.set('extraProp', options.extraProp);
},
toObject: function() {
return fabric.util.object.extend(this.callSuper('toObject'), {
extraProp: this.get('extraProp')
});
}
});
fabric.TextAsset.fromObject = function (object, callback) {
callback(new fabric.TextAsset(object.text, object));
};
fabric.TextAsset.async = true;
}

Related

Calling a private/nested javascript function from an outer scope

I have my javascript code like this . Inside that I have an init() function and in that function I have an options JSON object and in that object I have a function defined as objectselected(). How I call that function in a button click event
I have tried like this WorkFlow.init().options.Objectselected() but it is not working,
var WorkFlow = {
connectionData: [],
selectedTouchpoints: [],
init: function () {
var options = {
palleteId: "myPaletteElement",
elementId: "playAreaContainer",
TextStoreList: ['One', 'Two', 'Three'],
LinkTextStoreList: $('#drpLinkType option').map(function () {
return this.text;
}).get(),
shapeList: ['RoundedRectangle', 'Circle', 'Rectangle', 'Ellipse', 'Square', 'Diamond', 'Card', 'Database'],
diagramUpdate: function (e) {
},
objectSelected: function (e) {
},
linkUpdate: function (e) {
},
initialize: function () {
}
myGraph = new Graph(options);
options.initialize();
},
}
How to call that function.
One way around is you can return options and than call it.
init: function () {
var options = {
...your code..}
return options;
},
and call it than
var options = WorkFlow.init();
options.Objectselected();
As it stands, you have no access to options because it's a local variable - that is, local to its scope.
To access its contents, you'll need to return it from init().
Think about it:
WorkFlow.init()
Currently this returns undefined, because your init() returns nothing. You're trying to chain like in jQuery, but that relies on the API always returning the instance. Your path finds a dead-end at init().
To fix this, have init() return options - or at least the part of it you want to access from outside - an "export".
So (basic example)
init: function() {
var options {
my_func: function() { }, //<-- we want outside access to this
private: 'blah' //<-- this can stay private - leave it out of the export
}
//return an export, exposing only what we need to
return {
my_func: options.my_func
}
}
You need to return options as it is inside init function's scope
var WorkFlow = {
connectionData: [],
selectedTouchpoints: [],
init: function () {
var options = {
palleteId: "myPaletteElement",
elementId: "playAreaContainer",
TextStoreList: ['One', 'Two', 'Three'],
LinkTextStoreList: $('#drpLinkType option').map(function () {
return this.text;
}).get(),
shapeList: ['RoundedRectangle', 'Circle', 'Rectangle', 'Ellipse', 'Square', 'Diamond', 'Card', 'Database'],
diagramUpdate: function (e) {
},
objectSelected: function (e) {
},
linkUpdate: function (e) {
},
initialize: function () {
}
myGraph = new Graph(options);
options.initialize();
return options;
},
}
And call it as WorkFlow.init().objectSelected();
Building on Patrick's comment, you'd need to return options from the init function:
var WorkFlow = {
connectionData: [],
selectedTouchpoints: [],
init: function () {
var options = {
palleteId: "myPaletteElement",
...
options.initialize();
return options;
},
}

How to prevent object properties to not be extended in javascript

Im trying to seal an object property .
My question is ,here i have given Object.seal(personObject),this particular object is sealed and does not allow to configure or make any extensions in this object,but as i did not mention on personObject_2 it does allow to extend or configure
How can i make it on prototype .I mean like any class of type person should have/respect this seal.Can we achieve such behaviour
"use strict";
var personModule=(function (module) {
var person=function (fname,lname) {
Object.defineProperty(this,'firstName',{
get:function () {
return fname;
}
,set:function (newValue) {
fname=newValue;
},
configurable:true
});
Object.defineProperty(this,'lastName',{
get:function () {
return lname;
}
,set:function (newValue) {
lname=newValue;
},
configurable:true
});
Object.defineProperty(this,'fullName',{
get:function () {
return fname+lname;
},
configurable:true
});
}
module.person=person;
return module;
})(personModule || {});
var personObject=new personModule.person( "Raju","Rani");
console.log(personObject.fullName);
Object.seal(personObject);
//delete personObject.firstName;-->It throws error here
var personObject2=new personModule.person( "Shiva","Kumar");
delete personObject2.firstName;
console.log(personObject2.firstName);
Thanks
Here is Proxy version in case you do not prefer adding Object.seal on constructor
"use strict";
var personModule=(function (module) {
var person=function (fname,lname) {
Object.defineProperty(this,'firstName',{
get:function () {
return fname;
}
,set:function (newValue) {
fname=newValue;
},
configurable:true
});
Object.defineProperty(this,'lastName',{
get:function () {
return lname;
}
,set:function (newValue) {
lname=newValue;
},
configurable:true
});
Object.defineProperty(this,'fullName',{
get:function () {
return fname+lname;
},
configurable:true
});
}
module.person=new Proxy(person, {
construct(target, args){
args.unshift(null);
let ctor = target.bind.apply(target, args);
let result = new ctor();
Object.seal(result);
return result;
}
});
return module;
})(personModule || {});
var personObject=new personModule.person( "Raju","Rani");
console.log(personObject.fullName);
Object.seal(personObject);
//delete personObject.firstName;-->It throws error here
var personObject2=new personModule.person( "Shiva","Kumar");
delete personObject2.firstName;
console.log(personObject2.firstName);
Did you tried - immutable-js
var personObject = new personModule.person("Raju", "Rani");
var sealed = Immutable.Map(personObject);

Console logging "this" returns "null"

I am trying to create a flux store for a React app I am building. I am using an object-assign polyfill npm package and Facebook's Flux library.
Initially I was getting the error "Cannot read property '_data' of null' error in the console which was refering to var currIds = this._data.map(function(m){return m.id;});. That method is currently the only one being called directly. I then did console.log(this) which returned "null".
I find this strange. What is going on?
My code:
var Assign = require('object-assign');
var EventEmitterProto = require('events').EventEmitter.prototype;
var CHANGE_EVENT = 'CHANGE';
var StoreMethods = {
init: function() {},
set: function (arr) {
console.log(this);
var currIds = this._data.map(function(m){return m.id;});
arr.filter(function (item){
return currIds.indexOf(item.id) === -1;
}).forEach(this.add.bind(this));
},
add: function(item){
console.log(this);
this._data.push(item);
},
all: function() {
return this._data;
},
get: function(id){
return this._data.filter(function(item){
return item.cid === id;
})[0];
},
addChangeListener: function(fn) {
this.on(CHANGE_EVENT, fn);
},
removeChangeListener: function(fn) {
this.removeListener(CHANGE_EVENT, fn);
},
emitChange: function() {
this.emit(CHANGE_EVENT);
},
bind: function(actionType, actionFn) {
if(this.actions[actionType]){
this.actions[actionType].push(actionFn);
} else {
this.actions[actionType] = [actionFn];
}
}
};
exports.extend = function(methods) {
var store = {
_data: [],
actions: {}
};
Assign(store, EventEmitterProto, StoreMethods, methods);
store.init();
require('../dispatcher').register(function(action){
if(store.actions[action.actionType]){
store.actions[action.actionType].forEach(function(fn){
fn.call(null, action.data);
})
}
});
return store;
};
I can't see where set is called, however your this can be null if the function is invoked through call (see here) or apply, and your first argument is null.
This also happens in your require.register callback:
fn.call(null, action.data) //First parameter is your 'this'.

Alternative to multiple Object.defineProperty

Given a product might have several attributes such as name, price, sku, description and so on - the following will become quite long winded to describe a product model...
function Product(data) {
var productData = data || {};
Object.defineProperty(this, "sku", {
get: function() {
return productData.sku;
}
});
Object.defineProperty(this, "name", {
get: function() {
return productData.name;
}
});
Object.defineProperty(this, "price", {
get: function() {
return productData.price;
}
});
}
module.exports = Product;
What alternatives are there in javascript for this and how is this normally handled?
#Pointy deserves the points here with Object.defineProperties-
function Product(data) {
var productData = data || {};
Object.defineProperties(this, {
"sku": {
get: function() {
return productData.sku;
}
},
"name": {
get: function() {
return productData.name;
}
},
"price": {
get: function() {
return productData.price;
}
}
});
}
module.exports = Product;
Support is near identical to Object.defineProperty so there is no real reason not to use this method when defining multiple properties at the same time.
You can use a single loop to define all the properties:
var self = this;
Object.keys(productData).forEach(function(prop){
Object.defineProperty(self, prop, {
get: function() {
return productData[prop];
}
});
});
Demo

Model not firing parse function

I'm having a problem where my backbone model isn't parsing something correctly. Here is the listing.js:
SpendYourSavings.Models.Listing = Backbone.Model.extend({
urlRoot: "api/listings/",
images: function() {
this._images = this._images || new SpendYourSavings.Collections.Images([], { listing: this });
return this._images;
},
reviews: function() {
this._reviews = this._reviews || new SpendYourSavings.Collections.Reviews([], { listing: this });
return this._reviews;
},
shop: function() {
this._shop = this._shop || new SpendYourSavings.Models.Shop([], { listing: this });
return this._shop;
},
parse: function(data) {
if(data.images) {
this.images().set(data.images, { parse: true });
delete data.images;
}
if(data.reviews) {
this.reviews().set(data.reviews, { parse: true });
delete data.reviews;
}
if(data.shop) {
this.shop().set(data.shop, { parse: true });
delete data.shop;
}
return data;
}
});
Images and reviews work, but shop doesn't quite work. It sets the attributes of shop correctly, but it doesn't set the image properly.
Here is the shop.js:
SpendYourSavings.Models.Shop = Backbone.Model.extend({
urlRoot: "/api/shops",
reviews: function() {
this._reviews = this._reviews || new SpendYourSavings.Collections.Reviews([], {});
return this._reviews;
},
listings: function() {
this._listings = this._listings || new SpendYourSavings.Collections.Listings([], {});
return this._listings;
},
user: function() {
this._user = this._user || new SpendYourSavings.Models.User([], {});
return this._user;
},
image: function() {
this._image = this._image || new SpendYourSavings.Models.Image([], {});
return this._image
},
parse: function(data) {
console.log("shop parse data: " + data);
debugger
if(data.listings) {
this.listings().set(data.listings, { parse: true });
delete data.listings;
}
if(data.reviews) {
this.reviews().set(data.reviews, { parse: true });
delete data.reviews;
}
if(data.user) {
this.user().set(data.user, { parse: true });
delete data.user;
}
if(data.image) {
debugger
this.image().set(data.image, { parse: true });
delete data.image;
}
return data
}
});
The parse function in the shop.js never even when I receive a shop in the listing.js parse function! shop.image() doesn't get set to an image model properly, so I have to call something wonky like shop.get('image').url to get the url.
Presumably, the reason you're memoizing the image model in the shop is to maintain listeners and keep a single instance of that model around.
Collection#set takes a parse option that tells it to call parse on all the models that were set on the collection. Model#set is the method called immediately after calling parse using the attributes returned from parse.
In this case, we want to call #set on the associated shop model using the parsed attributes. So first lets call parse. It should look something like this:
SpendYourSavings.Models.Listing = Backbone.Model.extend({
urlRoot: "api/listings",
images: function() {
this._images = this._images || new SpendYourSavings.Collections.Images([], { listing: this });
return this._images;
},
reviews: function() {
this._reviews = this._reviews || new SpendYourSavings.Collections.Reviews([], { listing: this });
return this._reviews;
},
shop: function() {
// Notice the first argument is an object when initializing models.
this._shop = this._shop || new SpendYourSavings.Models.Shop({}, { listing: this });
return this._shop;
},
parse: function(data) {
if(data.images) {
this.images().set(data.images, { parse: true });
delete data.images;
}
if(data.reviews) {
this.reviews().set(data.reviews, { parse: true });
delete data.reviews;
}
if(data.shop) {
var shopParams = this.shop().parse(data.shop);
this.shop().set(shopParams);
delete data.shop;
}
return data;
}
}
});
Your issue is that parse: true on set only really applies to collections.
These lines
this.images().set(data.images, { parse: true });
this.reviews().set(data.reviews, { parse: true });
work, because you are saying "add whole new models from this JSON".
This line
this.image().set(data.image, { parse: true });
however, is trying to say, parse these params, and set values, but that is weird on a model. Should it literally only parse the attributes that were passed in? Should it merge the attributes that the model already has? What if there were dependencies between the things already in the model and the things being parsed?
Instead, you might try restructuring your top-level parsing, e.g
SpendYourSavings.Models.Listing = Backbone.Model.extend({
urlRoot: "api/listings/",
images: function() {
return this.get('images');
},
reviews: function() {
return this.get('reviews');
},
shop: function() {
return this.get('shop');
},
parse: function(data) {
if (data.images){
data.images = new SpendYourSavings.Collections.Images(data.images, { listing: this, parse: true});
}
if (data.reviews){
data.reviews = new SpendYourSavings.Collections.Reviews(data.reviews, { listing: this, parse: true});
}
if (data.shop){
data.shop = new SpendYourSavings.Models.Shop(data.shop, { listing: this, parse: true});
}
return data;
}
});

Categories