Objects and custom properties
Custom properties
When building your application you may need to attach to objects some custom properties.
One very common need is to have an id
or a name
on objects.
If you are using Typescript or if you want your IDE to suggest you completition you will need to clearly state which those properties are.
On top of that there is the serialization issue that will require you to pass down those properties in the argument of the function toObject
.
// example without proper typing:(myRect as any).name = 'rectangle';myRect.toObject(['name', 'id']);
To make this nicer you have to use the Typescript feature for interfaces and a custom property hook in the object class
import { FabricObject } from 'fabric';
declare module "fabric" { // to have the properties recognized on the instance and in the constructor interface FabricObject { id?: string; name?: string; } // to have the properties typed in the exported object interface SerializedObjectProps { id?: string; name?: string; }}
// to actually have the properties added to the serialized objectFabricObject.customProperties = ['name', 'id'];
This change will make the types work correcty:
Custom methods
In general it makes things simplier if you can stick to external utility functions, but in the cases in which you want to attach a specific method to the prototype of different classes, you have to do modify the interface again:
// declare the methods for typescriptdeclare module "fabric" { // to have the properties recognized on the instance and in the constructor interface Rect { getText: () => string; } // to have the properties typed in the exported object interface Text { getText: () => string; }}
// then add the methods to the classes:Rect.prototype.getText = function() { return 'none'; }Text.prototype.getText = function() { return this.text; }
Custom events
If you are looking to fire or understand custom events, look here: Events.
Subclassing
Subclassing is easier but is not always possible.
If you want to subclass leaf nodes like Rect
, Textbox
, IText
, Path
, that is possible and easy
import { classRegistry, SerializedPathProps } from 'fabric';
interface UniquePathPlusProps { id?: string; name?: string;}
export interface SerializedPathPlusProps extends SerializedPathProps, UniquePathPlusProps {}
export interface PathPlusProps extends SerializedPathProps, UniqueRectProps {}
export class PathPlus< Props extends TOptions<PathPlusProps> = Partial<PathPlusProps>, SProps extends SerializedPathPlusProps = SerializedPathPlusProps, EventSpec extends ObjectEvents = ObjectEvents,> extend Path<Props, SProps, EventSpec> { static type: 'path' // if you want it to override Path completely declare id?: string; declare name?: string;
toObject< T extends Omit<Props & TClassProperties<this>, keyof SProps>, K extends keyof T = never, >(propertiesToInclude: K[] = []): Pick<T, K> & SProps { return super.toObject([...propertiesToInclude, 'id', 'name']); }}
// to make possible restoring from serializationclassRegistry.setClass(PathPlus, 'path');// to make PathPlus connected to svg Path elementclassRegistry.setSVGClass(PathPlus, 'path');
But you can’t subclass FabricObject and add it back in the prototype chain of the other objects.
Warning
You should add custom properties when they make your life simpler during rendering or event handling. In general Fabric.js classes/objects on the canvas shouldn’t contain data that is not relevant to their rendering needs or behaviour configuration, they shouldn’t become data stores of your application.