Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@joint/decorators

Package Overview
Dependencies
Maintainers
2
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@joint/decorators

Decorators module for JointJS

  • 0.4.0
  • latest
  • Source
  • npm
  • Socket score

Version published
Maintainers
2
Created
Source

JointJS Decorators

ECMAScript / TypeScript decorator for defining JointJS shapes.

This library fully depends on JointJS (>=3.5), so please read its README before using this library.

Setup

Enable the experimentalDecorators compiler option in your tsconfig.json.

Then install JointJS Decorators from NPM:

npm i -S @joint/decorators

License

Mozilla Public License 2.0

Usage

There are a few class decorators:

And several class member decorators:


@Model(options: ModelOptions)

The decorator allows you to:

  • paste an existing SVG and use it as the model's markup
  • keep your SVG attributes in sync with your model's attributes
  • transform data using functions
  • introduce a new SVG attribute or change the behavior of an existing one
import { dia } from '@joint/core';
import { Model } from '@joint/decorators';

@Model({
    template: `
        <g>
            <rect
                width="calc(w)"
                height="calc(h)"
                :fill="{{color}}"
                stroke="black"
            />
            <text
                x="calc(0.5*w)"
                y="calc(0.5*h)"
                text-anchor="middle"
                text-vertical-anchor="middle"
                font-size="14"
                font-family="sans-serif"
                fill="black"
            >{{firstName}} {{lastName}}</text>
        </g>
    `,
    attributes: {
        color: 'red',
        firstName: 'John',
        lastName: 'Doe'
    }
})
class MyElement extends dia.Element {

}

ModelOptions

OptionDescriptionOptional
templatethe SVG string markup of the modelNo
attributesthe default attributes of the modelYes
namespacethe namespace for the model class to be added toYes

template

The decorator uses an SVG-based template syntax that allows you to declaratively bind the rendered DOM to the underlying model's data. All templates are syntactically valid SVG that can be parsed by spec-compliant browsers and SVG parsers.

While using the SVG XML string in the markup attribute is not recommended (every cell view needs to parse the string and it might affect the performance), the parsing of the decorator's template runs only once per class (translating it into JSON markup and defining event listeners needed for reactivity).

Text Interpolation

The most basic form of data binding is text interpolation using the "Mustache" syntax (double curly braces):

<text font-size="14">{{ label }}</text>

The mustache tag will be replaced with the value of the label property from the corresponding model's instance. It will also be updated whenever the label property changes.

Attributes Binding

To bind to an SVG attribute to a model's attribute, add a colon symbol (:) before the attribute's name.

<rect :fill="color" />

The colon symbol, :, instructs the decorator to keep the SVG attribute in sync with the model's attribute. The model's color value can be set this way.

model.set('color', 'red');

If the bound value is null, then the SVG attribute will be removed from the rendered element.

model.set('color', null);

It's possible to use mustaches inside binding expressions to combine multiple model's attributes into a single result.

<rect :fill="rgb({{red}},{{green}},{{blue}})" />
Calling Functions

The value of an attribute can be modified with functions before set/display.

<rect :stroke="color" :fill="lighten(color)"/>
<text>{{ capitalize(label) }}</text>

Functions called inside binding expressions will be called every time the cell view updates, so they should not have any side effects, such as changing data or triggering asynchronous operations.

It is possible to call a component-exposed method inside a binding expression only if the method is decorated with the Function decorator.

The function can accept any number of additional arguments. Every such argument shall be parsable with the JSON.parse function.

@Model({
    template: `
        <text y="10">{{ maxLength(label1, 20) }}</text>
        <text y="30">{{ maxLength(label2, 10) }}</text>
    `
})
class MyElement extends dia.Element {

    @Function()
    maxLength(value: string, max: number) {
        return value.substr(0, max);
    }
}

Multiple dependencies can be defined using an array.

<path :d="data([param1, param2])"/>

The function is run every time one or more attributes in the dependency array are changed.

@Model({
    attributes: {
        param1: 20,
        param2: 30
    },
    template: `
        <path :d="data([param1, param2])" stroke="white" fill="transparent" />
    `
})
class Arrow extends dia.Element {
    @Function()
    data(param1: number, param2: number): string {
        return `
                M ${param1} 0
                L calc(w) calc(0.5*h)
                L ${param1}  calc(h)
                V calc(h/2 + ${param2 / 2})
                H 0
                v -${param2}
                H ${param1}
                Z
        `;
    }
}
Selectors

If you want to modify any of the template attributes programmatically, you must add the @selector attribute to the SVG element.

<rect @selector="body" stroke="black" fill="red">
model.attr(['body', 'fill'], 'blue');

The <g> wrapper in the template is added automatically and always has a @selector equal to root. In case you want to add SVG attributes to the root group, wrap the template with one of them.

<g @selector="root" data-tooltip="My Tooltip">
    <rect @selector="body" stroke="black" fill="red">
</g>

To create selectors pointing to multiple SVG elements at once, use @group-selector.

<rect @group-selector="rectangles" fill="red">
<rect @group-selector="rectangles" fill="blue">
<rect @group-selector="rectangles" fill="green">
// Change the stroke of all rectangles
model.attr(['rectangles', 'stroke'], 'black');
Caveats

Some JointJS attributes expect their value to be an object (fill & stroke gradient, filters, markers and textWrap).

The solution is to define the property inside the attributes (mixing the template attributes with explicit model attributes).

const selector = 'label';

@Model({
    attributes: {
        title: 'My Title',
        attrs: {
            [selector]: {
                textWrap: {
                    maxLineCount: 1,
                    ellipsis: true
                }
            }
        }
    },
    template: `
        <text @selector="${selector}">{{title}}</text>
    `
})
class MyElement extends dia.Element {

}

attributes

The default attributes of the model. When creating an instance of the model, any unspecified attributes will be set to their default value.

import { dia, shapes } from '@joint/core';
import { Model } from '@joint/decorators';

@Model({
    attributes: {
        color: 'red'
    }
})
class MyElement extends dia.Element {

}

is equivalent to

import { dia, shapes } from '@joint/core';

class MyElement extends dia.Element {

    defaults() {
        const attributes = {
            color: 'red'
        };
        return {
            ...super.defaults,
            ...attributes,
            type: 'MyElement'
        }
    }
}

namespace

Syntactic sugar for adding a model to the namespace.

import { dia, shapes } from '@joint/core';
import { Model } from '@joint/decorators';

@Model({
    namespace: shapes
})
class MyElement extends dia.Element {

}

is equivalent to

import { dia, shapes } from '@joint/core';

class MyElement extends dia.Element {

}

Object.assign(shapes, {
    'MyElement': MyElement
});

@View(options: ViewOptions)

ViewOptions

OptionDescriptionOptional
namespacethe namespace for the view class to be added toYes
modelsan array of model classes this view is to be used withYes

Define a new cell view, which is automatically used by 2 different models.

import { dia, shapes } from '@joint/core';
import { View } from '@joint/decorators';

@View({
    namespace: shapes
    models: [MyElement, MyOtherElement]
})
class MyElementView extends dia.ElementView {

}

is equivalent to

import { dia, shapes } from '@joint/core';

class MyElementView extends dia.ElementView {

}

Object.assign(shapes, {
    'MyElementView': MyElementView,
    'MyOtherElementView': MyElementView
});

@Function(name?: string)

Define functions to transform data (e.g strings, amounts, dates) to be used within the template.

@Model({
    template: `
        <text>{{ capitalize(name) }}</text>
    `,
    attributes: {
        name: 'john',
    }
})
class MyElement extends dia.Element {

    @Function()
    capitalize(value: string) {
        return value.charAt(0).toUpperCase() + value.slice(1);
    }
}

@SVGAttribute(attributeName: string)

Introduce new SVG attributes or redefine existing ones.

import { dia, g, attributes } from '@joint/core';
import { Model, SVGAttribute } from '@joint/decorators';

@Model({
    attributes: {
        width: 140,
        height: 100
    },
    template: `
        <rect
            line-style="dashed"
            stroke-width="2"
        />
    `,
})
class MyElement extends dia.Element {

    /* `stroke-dasharray` that adjusts based on the current node's `stroke-width` */
    @SVGAttribute('line-style')
    setStrokeDasharray(
        this: dia.CellView,
        value: string,
        rect: g.Rect,
        node: SVGElement,
        nodeAttrs: attributes.SVGAttributes
    ) {
        const { strokeWidth = 1 } = nodeAttrs;
        let pattern;
        switch (value) {
            case 'dashed': {
                pattern = `${4 * strokeWidth},${2 * strokeWidth}`;
                break;
            }
            case 'dotted': {
                pattern = `${strokeWidth},${strokeWidth}`;
                break;
            }
            case 'solid': {
                pattern = 'none';
                break;
            }
            default: {
                throw new Error('Invalid line-style value.');
            }
        }
        node.setAttribute('stroke-dasharray', pattern);
    }
}

SVGAttribute function signature

ArgumentDescriptionExample
valuethe right-hand side of the template's attribute"dashed"
recta rectangle describing the coordinate system the node is rendered in (if no ref attribute is in use, the value is the model's bounding box relative to the model's position, otherwise it is the relative bounding box of the node referenced by the ref attribute)new g.Rect(0, 0, 140, 100)
nodea rendered DOM SVGElement<rect/> as SVGElement
nodeAttrsan object with all defined attributes of the node{ lineStyle: "dashed", strokeWidth: "2" }

@On(eventName: string)

Decorate an event handler in the context of the method it refers to.

class MyElementView extends dia.ElementView {

    @On('click')
    onClick() {
        console.log('click!', this.model.id);
    }
}

Copyright © 2013-2024 client IO

Keywords

FAQs

Package last updated on 04 Feb 2024

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc