Skip to content

⬅️ Back to Table of Contents

📄 flow.module.js

📊 Analysis Summary

Metric Count
🔧 Functions 225
🧱 Classes 26
📊 Variables & Constants 148

📚 Table of Contents

🛠️ File Location:

📂 playground/libs/flow.module.js

Variables & Constants

Name Type Kind Value Exported
REVISION "3" let/var '3'
_id number let/var 0
object any let/var null
id number let/var this.id
objects any let/var data.objects
event any let/var e.touches ? e.touches[ 0 ] : e
dragData any let/var null
zoomDOM any let/var dom
zoom any let/var zoomDOM.style.zoom
event any let/var e.touches ? e.touches[ 0 ] : e
event any let/var e.touches ? e.touches[ 0 ] : e
e Event let/var dom
selected any let/var null
element this let/var this
touch any let/var e.changedTouches[ 0 ]
type any let/var e.type
dom HTMLElement let/var this.dom
dom HTMLElement let/var this.dom
link Link let/var new Link( this, element )
inputs any[] let/var []
properties any[] let/var []
links any[] let/var []
id any let/var input.toJSON( data ).id
inputs any[] let/var this.inputs
index number let/var 0
nodeDOM any let/var this.node.dom
defaultOutput "lio" \| "rio" let/var Link.InputDirection === 'left' ? 'lio' : 'rio'
link Link let/var type === defaultOutput ? new Link( this ) : new Link( null, this )
previewLink Link let/var new Link( link.inputElement, link.outputElement )
isInvalid boolean let/var previewLink.inputElement !== null && previewLink.outputElement !== null && pr...
canvas any let/var this.canvas
dom HTMLElement let/var this.dom
style CSSStyleDeclaration let/var dom.style
dom HTMLElement let/var this.dom
dom HTMLElement let/var this.dom
dom HTMLElement let/var this.dom
canvas any let/var this.canvas
links any[] let/var []
element any let/var this.elements[ this.elements.length - 1 ]
elements any[] let/var []
elements any[] let/var this.elements
index number let/var 0
dx number let/var p2x - p1x
dy number let/var p2y - p1y
offset number let/var Math.sqrt( ( dx * dx ) + ( dy * dy ) ) * ( invert ? - .3 : .3 )
colors string[] let/var [ '#ff4444', '#44ff44', '#4444ff' ]
touchData any let/var null
delta number let/var e.deltaY * .003
clientX number let/var ( e.touches[ 0 ].clientX + e.touches[ 1 ].clientX ) / 2
clientY number let/var ( e.touches[ 0 ].clientY + e.touches[ 1 ].clientY ) / 2
delta number let/var ( touchData.distance - distance ) * .003
clientX any let/var e.touches[ 0 ].clientX
clientY any let/var e.touches[ 0 ].clientY
zoom number let/var this.zoom
dropEnterCount number let/var 0
centerNodeX number let/var dropNode.getWidth() / 2
zoom number let/var this.zoom
clientMapX number let/var data.client.x - rect.left
clientMapY number let/var data.client.y - rect.top
overMapScreen boolean let/var clientMapX > screen.x && clientMapY > screen.y && clientMapX < screen.x + scr...
scaleX number let/var this._mapInfo.width / this.mapCanvas.width
scrollLeft number let/var - this._mapInfo.left - ( clientMapX * scaleX )
scrollTop number let/var - this._mapInfo.top - ( clientMapY * ( this._mapInfo.height / this.mapCanvas....
event any let/var e.touches ? e.touches[ 0 ] : e
rectClientX number let/var ( this.clientX - rect.left ) / zoom
rectClientY number let/var ( this.clientY - rect.top ) / zoom
bounds { x: number; y: number; width: number... let/var { x: Infinity, y: Infinity, width: - Infinity, height: - Infinity }
classList DOMTokenList let/var this.dom.classList
userAgent string let/var navigator.userAgent
isSafari boolean let/var /Safari/.test( userAgent ) && ! /Chrome/.test( userAgent )
nodes any[] let/var this.nodes
nodes any[] let/var this.nodes
links any[] let/var []
previousNode any let/var this.selected
boundsWidth number let/var - bounds.x + bounds.width
boundsHeight number let/var - bounds.y + bounds.height
mapScale number let/var Math.min( mapCanvas.width / boundsWidth, mapCanvas.height / boundsHeight ) * .5
boundsMapWidth number let/var boundsWidth * mapScale
boundsMapHeight number let/var boundsHeight * mapScale
boundsOffsetX number let/var ( mapCanvas.width / 2 ) - ( boundsMapWidth / 2 )
boundsOffsetY number let/var ( mapCanvas.height / 2 ) - ( boundsMapHeight / 2 )
selectedNode any let/var null
screenMapX number let/var ( - ( scrollLeft + bounds.x ) * mapScale ) + boundsOffsetX
screenMapY number let/var ( - ( scrollTop + bounds.y ) * mapScale ) + boundsOffsetY
screenMapWidth number let/var ( canvas.width * mapScale ) / zoom
screenMapHeight number let/var ( canvas.height * mapScale ) / zoom
domRect DOMRect let/var this.rect
aPos { x: number; y: number; } let/var { x: 0, y: 0 }
bPos { x: number; y: number; } let/var { x: 0, y: 0 }
offsetIORadius 10 let/var 10
dragging string let/var ''
draggingLink string let/var ''
length number let/var 0
drawContext CanvasRenderingContext2D let/var draggingLink ? frontContext : context
colorA any let/var null
colorB any let/var null
color string let/var colors[ i ] \|\| '#ffffff'
marginY 4 let/var 4
colorA any let/var lioElement.getRIOColor() \|\| color
colorB any let/var rioElement.getLIOColor() \|\| color
aCenterY number let/var ( ( rioLength * marginY ) * .5 ) - ( marginY / 2 )
bCenterY number let/var ( ( lioLength * marginY ) * .5 ) - ( marginY / 2 )
aPosY number let/var ( aIndex * marginY ) - 1
bPosY number let/var ( bIndex * marginY ) - 1
nodes any[] let/var []
dom HTMLElement let/var this.dom
lastContext any let/var null
button any let/var e ? e.target : null
button any let/var e.target
dom HTMLElement let/var this.dom
filter boolean let/var true
filterNeedUpdate boolean let/var true
key string let/var e.key
index number let/var this.filteredIndex
index number let/var this.filteredIndex
filteredItem any let/var this.filtered[ index ]
tags WeakMap<WeakKey, any> let/var this.tags
filtered any[] let/var []
buttonTags string let/var tags.has( button ) ? ' ' + tags.get( button ) : ''
score number let/var text.length / label.length
button any let/var this.filtered[ i ].button
diff number let/var delta.x - delta.y
value any let/var data.value + ( diff * this.step )
fract number let/var this.step % 1
dom any let/var this.dom
defaultValue any let/var dom.value
containsDefaultValue boolean let/var false
opt any let/var options[ index ]
currentOptions any let/var this.options
sensibility 0.001 let/var .001
field NumberInput let/var new NumberInput( value, min, max, step )
keyDownStr string let/var ''
dom any let/var this._datalistDOM
datalistId string let/var 'input-dt-' + this.id
dom any let/var this._buttonsDOM
childrenDOM any let/var this.childrenDOM
dom HTMLElement let/var this.dom
LoaderLib {} let/var {}
parseType string let/var this.parseType
type any let/var json.type
flowClass any let/var lib[ type ] ? lib[ type ] : ( LoaderLib[ type ] \|\| Flow[ type ] )
flowObj any let/var new flowClass()
objects {} let/var {}
obj any let/var json.objects[ id ]
type any let/var obj.type
flowClass any let/var lib[ type ] ? lib[ type ] : ( LoaderLib[ type ] \|\| Flow[ type ] )
ref Map<any, any> let/var new Map()
newObject any let/var objects[ id ]

Functions

__flow__addCSS(css: any): void

JSDoc:

/**
 * https://github.com/sunag/flow
 */

Parameters:

  • css any

Returns: void

Calls:

  • document.createElement
  • style.setAttribute
  • document.head.appendChild
Code
function __flow__addCSS( css ) {

    try {

        const style = document.createElement( 'style' );

        style.setAttribute( 'type', 'text/css' );
        style.innerHTML = css;
        document.head.appendChild( style );

    } catch ( e ) {}

}

Serializer.setSerializable(value: any): this

Parameters:

  • value any

Returns: this

Code
setSerializable( value ) {

        this._serializable = value;

        return this;

    }

Serializer.getSerializable(): boolean

Returns: boolean

Code
getSerializable() {

        return this._serializable;

    }

Serializer.serialize(): void

Returns: void

Calls:

  • console.warn
Code
serialize( /*data*/ ) {

        console.warn( 'Serializer: Abstract function.' );

    }

Serializer.deserialize(): void

Returns: void

Calls:

  • console.warn
Code
deserialize( /*data*/ ) {

        console.warn( 'Serializer: Abstract function.' );

    }

Serializer.deserializeLib(): void

Returns: void

Code
deserializeLib( /*data, lib*/ ) {

        // Abstract function.

    }

Serializer.toJSON(data: any): any

Parameters:

  • data any

Returns: any

Calls:

  • this.serialize
Code
toJSON( data = null ) {

        let object = null;

        const id = this.id;

        if ( data !== null ) {

            const objects = data.objects;

            object = objects[ id ];

            if ( object === undefined ) {

                object = { objects };

                this.serialize( object );

                delete object.objects;

                objects[ id ] = object;

            }

        } else {

            object = { objects: {} };

            this.serialize( object );

        }

        object.id = id;
        object.type = this.className;

        return object;

    }

PointerMonitor.start(): this

Returns: this

Calls:

  • window.addEventListener
Code
start() {

        if ( this.started ) return;

        this.started = true;

        window.addEventListener( 'wheel', this._onMoveEvent, true );

        window.addEventListener( 'mousedown', this._onMoveEvent, true );
        window.addEventListener( 'touchstart', this._onMoveEvent, true );

        window.addEventListener( 'mousemove', this._onMoveEvent, true );
        window.addEventListener( 'touchmove', this._onMoveEvent, true );

        window.addEventListener( 'dragover', this._onMoveEvent, true );

        return this;

    }

draggableDOM(dom: any, callback: any, settings: {}): void

Parameters:

  • dom any
  • callback any
  • settings {}

Returns: void

Calls:

  • Object.assign
  • Number
  • e.stopImmediatePropagation
  • callback
  • window.addEventListener
  • getZoom
  • Math.abs
  • dom.classList.add
  • document.body.classList.add
  • className.split
  • dom.classList.remove
  • document.body.classList.remove
  • window.removeEventListener
  • dom.removeEventListener
  • onMouseDown
  • dom.addEventListener
Code
( dom, callback = null, settings = {} ) => {

    settings = Object.assign( {
        className: 'dragging',
        click: false,
        bypass: false
    }, settings );

    let dragData = null;

    const { className, click, bypass } = settings;

    const getZoom = () => {

        let zoomDOM = dom;

        while ( zoomDOM && zoomDOM !== document ) {

            const zoom = zoomDOM.style.zoom;

            if ( zoom ) {

                return Number( zoom );

            }

            zoomDOM = zoomDOM.parentNode;

        }

        return 1;

    };

    const onMouseDown = ( e ) => {

        const event = e.touches ? e.touches[ 0 ] : e;

        if ( bypass === false ) e.stopImmediatePropagation();

        dragData = {
            client: { x: event.clientX, y: event.clientY },
            delta: { x: 0, y: 0 },
            start: { x: dom.offsetLeft, y: dom.offsetTop },
            frame: 0,
            isDown: true,
            dragging: false,
            isTouch: !! e.touches
        };

        if ( click === true ) {

            callback( dragData );

            dragData.frame ++;

        }

        window.addEventListener( 'mousemove', onGlobalMouseMove );
        window.addEventListener( 'mouseup', onGlobalMouseUp );

        window.addEventListener( 'touchmove', onGlobalMouseMove );
        window.addEventListener( 'touchend', onGlobalMouseUp );

    };

    const onGlobalMouseMove = ( e ) => {

        const { start, delta, client } = dragData;

        const event = e.touches ? e.touches[ 0 ] : e;

        const zoom = getZoom();

        delta.x = ( event.clientX - client.x ) / zoom;
        delta.y = ( event.clientY - client.y ) / zoom;

        dragData.x = start.x + delta.x;
        dragData.y = start.y + delta.y;

        if ( dragData.dragging === true ) {

            if ( callback !== null ) {

                callback( dragData );

                dragData.frame ++;

            } else {

                dom.style.cssText += `; left: ${ dragData.x }px; top: ${ dragData.y }px;`;

            }

            if ( bypass === false ) e.stopImmediatePropagation();

        } else {

            if ( Math.abs( delta.x ) > 2 || Math.abs( delta.y ) > 2 ) {

                dragData.dragging = true;

                dom.classList.add( 'drag' );

                if ( className ) document.body.classList.add( ...className.split( ' ' ) );

                if ( bypass === false ) e.stopImmediatePropagation();

            }

        }

    };

    const onGlobalMouseUp = ( e ) => {

        if ( bypass === false ) e.stopImmediatePropagation();

        dom.classList.remove( 'drag' );

        if ( className ) document.body.classList.remove( ...className.split( ' ' ) );

        window.removeEventListener( 'mousemove', onGlobalMouseMove );
        window.removeEventListener( 'mouseup', onGlobalMouseUp );

        window.removeEventListener( 'touchmove', onGlobalMouseMove );
        window.removeEventListener( 'touchend', onGlobalMouseUp );

        if ( callback === null ) {

            dom.removeEventListener( 'mousedown', onMouseDown );
            dom.removeEventListener( 'touchstart', onMouseDown );

        }

        dragData.dragging = false;
        dragData.isDown = false;

        if ( callback !== null ) {

            callback( dragData );

            dragData.frame ++;

        }

    };

    if ( dom instanceof Event ) {

        const e = dom;
        dom = e.target;

        onMouseDown( e );

    } else {

        dom.addEventListener( 'mousedown', onMouseDown );
        dom.addEventListener( 'touchstart', onMouseDown );

    }

}

getZoom(): number

Returns: number

Calls:

  • Number
Code
() => {

        let zoomDOM = dom;

        while ( zoomDOM && zoomDOM !== document ) {

            const zoom = zoomDOM.style.zoom;

            if ( zoom ) {

                return Number( zoom );

            }

            zoomDOM = zoomDOM.parentNode;

        }

        return 1;

    }

onMouseDown(e: any): void

Parameters:

  • e any

Returns: void

Calls:

  • e.stopImmediatePropagation
  • callback
  • window.addEventListener
Code
( e ) => {

        const event = e.touches ? e.touches[ 0 ] : e;

        if ( bypass === false ) e.stopImmediatePropagation();

        dragData = {
            client: { x: event.clientX, y: event.clientY },
            delta: { x: 0, y: 0 },
            start: { x: dom.offsetLeft, y: dom.offsetTop },
            frame: 0,
            isDown: true,
            dragging: false,
            isTouch: !! e.touches
        };

        if ( click === true ) {

            callback( dragData );

            dragData.frame ++;

        }

        window.addEventListener( 'mousemove', onGlobalMouseMove );
        window.addEventListener( 'mouseup', onGlobalMouseUp );

        window.addEventListener( 'touchmove', onGlobalMouseMove );
        window.addEventListener( 'touchend', onGlobalMouseUp );

    }

onGlobalMouseMove(e: any): void

Parameters:

  • e any

Returns: void

Calls:

  • getZoom
  • callback
  • e.stopImmediatePropagation
  • Math.abs
  • dom.classList.add
  • document.body.classList.add
  • className.split
Code
( e ) => {

        const { start, delta, client } = dragData;

        const event = e.touches ? e.touches[ 0 ] : e;

        const zoom = getZoom();

        delta.x = ( event.clientX - client.x ) / zoom;
        delta.y = ( event.clientY - client.y ) / zoom;

        dragData.x = start.x + delta.x;
        dragData.y = start.y + delta.y;

        if ( dragData.dragging === true ) {

            if ( callback !== null ) {

                callback( dragData );

                dragData.frame ++;

            } else {

                dom.style.cssText += `; left: ${ dragData.x }px; top: ${ dragData.y }px;`;

            }

            if ( bypass === false ) e.stopImmediatePropagation();

        } else {

            if ( Math.abs( delta.x ) > 2 || Math.abs( delta.y ) > 2 ) {

                dragData.dragging = true;

                dom.classList.add( 'drag' );

                if ( className ) document.body.classList.add( ...className.split( ' ' ) );

                if ( bypass === false ) e.stopImmediatePropagation();

            }

        }

    }

onGlobalMouseUp(e: any): void

Parameters:

  • e any

Returns: void

Calls:

  • e.stopImmediatePropagation
  • dom.classList.remove
  • document.body.classList.remove
  • className.split
  • window.removeEventListener
  • dom.removeEventListener
  • callback
Code
( e ) => {

        if ( bypass === false ) e.stopImmediatePropagation();

        dom.classList.remove( 'drag' );

        if ( className ) document.body.classList.remove( ...className.split( ' ' ) );

        window.removeEventListener( 'mousemove', onGlobalMouseMove );
        window.removeEventListener( 'mouseup', onGlobalMouseUp );

        window.removeEventListener( 'touchmove', onGlobalMouseMove );
        window.removeEventListener( 'touchend', onGlobalMouseUp );

        if ( callback === null ) {

            dom.removeEventListener( 'mousedown', onMouseDown );
            dom.removeEventListener( 'touchstart', onMouseDown );

        }

        dragData.dragging = false;
        dragData.isDown = false;

        if ( callback !== null ) {

            callback( dragData );

            dragData.frame ++;

        }

    }

dispatchEventList(list: any, params: any[]): boolean

Parameters:

  • list any
  • params any[]

Returns: boolean

Calls:

  • callback
Code
( list, ...params ) => {

    for ( const callback of list ) {

        if ( callback( ...params ) === false ) {

            return false;

        }

    }

    return true;

}

numberToPX(val: any): any

Parameters:

  • val any

Returns: any

Calls:

  • isNaN
Code
( val ) => {

    if ( isNaN( val ) === false ) {

        val = `${ val }px`;

    }

    return val;

}

numberToHex(val: any): any

Parameters:

  • val any

Returns: any

Calls:

  • isNaN
  • val.toString( 16 ).padStart
Code
( val ) => {

    if ( isNaN( val ) === false ) {

        val = `#${ val.toString( 16 ).padStart( 6, '0' ) }`;

    }

    return val;

}

rgbaToArray(rgba: any): any

Parameters:

  • rgba any

Returns: any

Calls:

  • rgba.substring( rgba.indexOf( '(' ) + 1, rgba.indexOf( ')' ) ) .split( ',' ) .map
  • parseInt
  • num.trim
Code
( rgba ) => {

    const values = rgba.substring( rgba.indexOf( '(' ) + 1, rgba.indexOf( ')' ) )
        .split( ',' )
        .map( num => parseInt( num.trim() ) );

    return values;

}

removeDOMClass(dom: any, classList: any): void

Parameters:

  • dom any
  • classList any

Returns: void

Calls:

  • classList.split( ' ' ).forEach
  • dom.classList.remove
Code
( dom, classList ) => {

    if ( classList ) classList.split( ' ' ).forEach( alignClass => dom.classList.remove( alignClass ) );

}

addDOMClass(dom: any, classList: any): void

Parameters:

  • dom any
  • classList any

Returns: void

Calls:

  • classList.split( ' ' ).forEach
  • dom.classList.add
Code
( dom, classList ) => {

    if ( classList ) classList.split( ' ' ).forEach( alignClass => dom.classList.add( alignClass ) );

}

Element.setAttribute(name: any, value: any): this

Parameters:

  • name any
  • value any

Returns: this

Calls:

  • this.dom.setAttribute
Code
setAttribute( name, value ) {

        this.dom.setAttribute( name, value );

        return this;

    }

Element.onValid(callback: any): this

Parameters:

  • callback any

Returns: this

Calls:

  • this.events.valid.push
Code
onValid( callback ) {

        this.events.valid.push( callback );

        return this;

    }

Element.onConnect(callback: any, childrens: boolean): this

Parameters:

  • callback any
  • childrens boolean

Returns: this

Calls:

  • this.events.connect.push
  • this.events.connectChildren.push
Code
onConnect( callback, childrens = false ) {

        this.events.connect.push( callback );

        if ( childrens ) {

            this.events.connectChildren.push( callback );

        }

        return this;

    }

Element.setObjectCallback(callback: any): this

Parameters:

  • callback any

Returns: this

Code
setObjectCallback( callback ) {

        this.objectCallback = callback;

        return this;

    }

Element.setObject(value: any): this

Parameters:

  • value any

Returns: this

Code
setObject( value ) {

        this.object = value;

        return this;

    }

Element.getObject(output: any): any

Parameters:

  • output any

Returns: any

Calls:

  • this.objectCallback
Code
getObject( output = null ) {

        return this.objectCallback ? this.objectCallback( output ) : this.object;

    }

Element.setVisible(value: any): this

Parameters:

  • value any

Returns: this

Code
setVisible( value ) {

        this.visible = value;

        this.dom.style.display = value ? '' : 'none';

        return this;

    }

Element.getVisible(): boolean

Returns: boolean

Code
getVisible() {

        return this.visible;

    }

Element.setEnabledInputs(value: any): this

Parameters:

  • value any

Returns: this

Calls:

  • dom.classList.remove
  • dom.classList.add
Code
setEnabledInputs( value ) {

        const dom = this.dom;

        if ( ! this.enabledInputs ) dom.classList.remove( 'inputs-disable' );

        if ( ! value ) dom.classList.add( 'inputs-disable' );

        this.enabledInputs = value;

        return this;

    }

Element.getEnabledInputs(): boolean

Returns: boolean

Code
getEnabledInputs() {

        return this.enabledInputs;

    }

Element.setColor(color: any): this

Parameters:

  • color any

Returns: this

Calls:

  • numberToHex
Code
setColor( color ) {

        this.dom.style[ 'background-color' ] = numberToHex( color );
        this.color = null;

        return this;

    }

Element.getColor(): string

Returns: string

Calls:

  • window.getComputedStyle
  • css.getPropertyValue
Code
getColor() {

        if ( this.color === null ) {

            const css = window.getComputedStyle( this.dom );

            this.color = css.getPropertyValue( 'background-color' );

        }

        return this.color;

    }

Element.setStyle(style: any): this

Parameters:

  • style any

Returns: this

Calls:

  • dom.classList.remove
  • dom.classList.add
Code
setStyle( style ) {

        const dom = this.dom;

        if ( this.style ) dom.classList.remove( this.style );

        if ( style ) dom.classList.add( style );

        this.style = style;
        this.color = null;

        return this;

    }

Element.setInput(length: any): this

Parameters:

  • length any

Returns: this

Calls:

  • this.setLIO
  • this.setRIO
Code
setInput( length ) {

        if ( Link.InputDirection === 'left' ) {

            return this.setLIO( length );

        } else {

            return this.setRIO( length );

        }

    }

Element.setInputColor(color: any): this

Parameters:

  • color any

Returns: this

Calls:

  • this.setLIOColor
  • this.setRIOColor
Code
setInputColor( color ) {

        if ( Link.InputDirection === 'left' ) {

            return this.setLIOColor( color );

        } else {

            return this.setRIOColor( color );

        }

    }

Element.setOutput(length: any): this

Parameters:

  • length any

Returns: this

Calls:

  • this.setRIO
  • this.setLIO
Code
setOutput( length ) {

        if ( Link.InputDirection === 'left' ) {

            return this.setRIO( length );

        } else {

            return this.setLIO( length );

        }

    }

Element.setOutputColor(color: any): this

Parameters:

  • color any

Returns: this

Calls:

  • this.setRIOColor
  • this.setLIOColor
Code
setOutputColor( color ) {

        if ( Link.InputDirection === 'left' ) {

            return this.setRIOColor( color );

        } else {

            return this.setLIOColor( color );

        }

    }

Element.setLIOColor(color: any): this

Parameters:

  • color any

Returns: this

Calls:

  • numberToHex
Code
setLIOColor( color ) {

        this.lioDOM.style[ 'border-color' ] = numberToHex( color );

        return this;

    }

Element.setLIO(length: any): this

Parameters:

  • length any

Returns: this

Calls:

  • this.dom.classList.add
  • this.dom.prepend
  • this.dom.classList.remove
  • this.lioDOM.remove
Code
setLIO( length ) {

        this.lioLength = length;

        this.lioDOM.style.visibility = length > 0 ? '' : 'hidden';

        if ( length > 0 ) {

            this.dom.classList.add( 'lio' );
            this.dom.prepend( this.lioDOM );

        } else {

            this.dom.classList.remove( 'lio' );
            this.lioDOM.remove();

        }

        return this;

    }

Element.getLIOColor(): any

Returns: any

Code
getLIOColor() {

        return this.lioDOM.style[ 'border-color' ];

    }

Element.setRIOColor(color: any): this

Parameters:

  • color any

Returns: this

Calls:

  • numberToHex
Code
setRIOColor( color ) {

        this.rioDOM.style[ 'border-color' ] = numberToHex( color );

        return this;

    }

Element.getRIOColor(): any

Returns: any

Code
getRIOColor() {

        return this.rioDOM.style[ 'border-color' ];

    }

Element.setRIO(length: any): this

Parameters:

  • length any

Returns: this

Calls:

  • this.dom.classList.add
  • this.dom.prepend
  • this.dom.classList.remove
  • this.rioDOM.remove
Code
setRIO( length ) {

        this.rioLength = length;

        this.rioDOM.style.visibility = length > 0 ? '' : 'hidden';

        if ( length > 0 ) {

            this.dom.classList.add( 'rio' );
            this.dom.prepend( this.rioDOM );

        } else {

            this.dom.classList.remove( 'rio' );
            this.rioDOM.remove();

        }

        return this;

    }

Element.add(input: any): this

Parameters:

  • input any

Returns: this

Calls:

  • this.inputs.push
  • this.inputsDOM.append
Code
add( input ) {

        this.inputs.push( input );

        input.element = this;

        this.inputsDOM.append( input.dom );

        return this;

    }

Element.setHeight(val: any): this

Parameters:

  • val any

Returns: this

Calls:

  • numberToPX
Code
setHeight( val ) {

        this.dom.style.height = numberToPX( val );

        return this;

    }

Element.getHeight(): number

Returns: number

Calls:

  • parseInt
Code
getHeight() {

        return parseInt( this.dom.style.height );

    }

Element.connect(element: any): boolean

Parameters:

  • element any

Returns: boolean

Calls:

  • this.disconnectDOM.dispatchEvent
  • dispatchEventList
  • this.links.push
  • document.createElement
  • this.dom.append
  • this.dom.removeChild
  • this.disconnectDOM.removeEventListener
  • element.removeEventListener
  • this.dispatchEvent
  • this.connect
  • e.stopPropagation
  • this.disconnectDOM.addEventListener
  • element.addEventListener

Internal Comments:

// remove the current input (x5)

Code
connect( element = null ) {

        if ( this.disconnectDOM !== null ) {

            // remove the current input

            this.disconnectDOM.dispatchEvent( new Event( 'disconnect' ) );

        }

        if ( element !== null ) {

            element = element.baseElement || element;

            if ( dispatchEventList( this.events.valid, this, element, 'connect' ) === false ) {

                return false;

            }

            const link = new Link( this, element );

            this.links.push( link );

            if ( this.disconnectDOM === null ) {

                this.disconnectDOM = document.createElement( 'f-disconnect' );
                this.disconnectDOM.innerHTML = Element.icons.unlink ? `<i class='${ Element.icons.unlink }'></i>` : '✖';

                this.dom.append( this.disconnectDOM );

                const onDisconnect = () => {

                    this.links = [];
                    this.dom.removeChild( this.disconnectDOM );

                    this.disconnectDOM.removeEventListener( 'mousedown', onClick, true );
                    this.disconnectDOM.removeEventListener( 'touchstart', onClick, true );
                    this.disconnectDOM.removeEventListener( 'disconnect', onDisconnect, true );

                    element.removeEventListener( 'connect', onConnect );
                    element.removeEventListener( 'connectChildren', onConnect );
                    element.removeEventListener( 'nodeConnect', onConnect );
                    element.removeEventListener( 'nodeConnectChildren', onConnect );
                    element.removeEventListener( 'dispose', onDispose );

                    this.disconnectDOM = null;

                };

                const onConnect = () => {

                    this.dispatchEvent( new Event( 'connectChildren' ) );

                };

                const onDispose = () => {

                    this.connect();

                };

                const onClick = ( e ) => {

                    e.stopPropagation();

                    this.connect();

                };

                this.disconnectDOM.addEventListener( 'mousedown', onClick, true );
                this.disconnectDOM.addEventListener( 'touchstart', onClick, true );
                this.disconnectDOM.addEventListener( 'disconnect', onDisconnect, true );

                element.addEventListener( 'connect', onConnect );
                element.addEventListener( 'connectChildren', onConnect );
                element.addEventListener( 'nodeConnect', onConnect );
                element.addEventListener( 'nodeConnectChildren', onConnect );
                element.addEventListener( 'dispose', onDispose );

            }

        }

        this.dispatchEvent( new Event( 'connect' ) );

        return true;

    }

Element.dispose(): void

Returns: void

Calls:

  • this.dispatchEvent
Code
dispose() {

        this.dispatchEvent( new Event( 'dispose' ) );

    }

Element.getInputByProperty(property: any): any

Parameters:

  • property any

Returns: any

Calls:

  • input.getProperty
Code
getInputByProperty( property ) {

        for ( const input of this.inputs ) {

            if ( input.getProperty() === property ) {

                return input;

            }

        }

    }

Element.serialize(data: any): void

Parameters:

  • data any

Returns: void

Calls:

  • input.toJSON
  • input.getProperty
  • inputs.push
  • properties.push
  • links.push
  • link.outputElement.toJSON
  • this.getHeight
Code
serialize( data ) {

        const inputs = [];
        const properties = [];
        const links = [];

        for ( const input of this.inputs ) {

            const id = input.toJSON( data ).id;
            const property = input.getProperty();

            inputs.push( id );

            if ( property !== null ) {

                properties.push( { property, id } );

            }

        }

        for ( const link of this.links ) {

            if ( link.inputElement !== null && link.outputElement !== null ) {

                links.push( link.outputElement.toJSON( data ).id );

            }

        }

        if ( this.inputLength > 0 ) data.inputLength = this.inputLength;
        if ( this.outputLength > 0 ) data.outputLength = this.outputLength;

        if ( inputs.length > 0 ) data.inputs = inputs;
        if ( properties.length > 0 ) data.properties = properties;
        if ( links.length > 0 ) data.links = links;

        if ( this.style !== '' ) {

            data.style = this.style;

        }

        data.height = this.getHeight();

    }

Element.deserialize(data: any): void

Parameters:

  • data any

Returns: void

Calls:

  • this.setInput
  • this.setOutput
  • this.getInputByProperty
  • this.add
  • this.connect
  • this.setStyle
  • this.setHeight
Code
deserialize( data ) {

        if ( data.inputLength !== undefined ) this.setInput( data.inputLength );
        if ( data.outputLength !== undefined ) this.setOutput( data.outputLength );

        if ( data.properties !== undefined ) {

            for ( const { id, property } of data.properties ) {

                data.objects[ id ] = this.getInputByProperty( property );

            }

        } else if ( data.inputs !== undefined ) {

            const inputs = this.inputs;

            if ( inputs.length > 0 ) {

                let index = 0;

                for ( const id of data.inputs ) {

                    data.objects[ id ] = inputs[ index ++ ];

                }

            } else {

                for ( const id of data.inputs ) {

                    this.add( data.objects[ id ] );

                }

            }

        }

        if ( data.links !== undefined ) {

            for ( const id of data.links ) {

                this.connect( data.objects[ id ] );

            }

        }

        if ( data.style !== undefined ) {

            this.setStyle( data.style );

        }

        if ( data.height !== undefined ) {

            this.setHeight( data.height );

        }

    }

Element.getLinkedObject(output: any): any

Parameters:

  • output any

Returns: any

Calls:

  • this.getLinkedElement
  • linkedElement.getObject
Code
getLinkedObject( output = null ) {

        const linkedElement = this.getLinkedElement();

        return linkedElement ? linkedElement.getObject( output ) : null;

    }

Element.getLinkedElement(): any

Returns: any

Calls:

  • this.getLink
Code
getLinkedElement() {

        const link = this.getLink();

        return link ? link.outputElement : null;

    }

Returns: any

Code
getLink() {

        return this.links[ 0 ];

    }

Element._createIO(type: any): HTMLElement

Parameters:

  • type any

Returns: HTMLElement

Calls:

  • document.createElement
  • e.preventDefault
  • e.stopPropagation
  • nodeDOM.classList.add
  • ioDOM.classList.add
  • dom.classList.add
  • this.links.push
  • draggableDOM
  • previewLink.outputElement.dom.classList.remove
  • previewLink.inputElement.dom.classList.remove
  • dispatchEventList
  • previewLink.outputElement.dom.classList.add
  • previewLink.inputElement.dom.classList.add
  • nodeDOM.classList.remove
  • ioDOM.classList.remove
  • dom.classList.remove
  • this.links.splice
  • this.links.indexOf
  • link.outputElement.node.isCircular
  • link.inputElement.connect
  • ioDOM.addEventListener

Internal Comments:

// check if is an is circular link
//

Code
_createIO( type ) {

        const { dom } = this;

        const ioDOM = document.createElement( 'f-io' );
        ioDOM.style.visibility = 'hidden';
        ioDOM.className = type;

        const onConnectEvent = ( e ) => {

            e.preventDefault();

            e.stopPropagation();

            selected = null;

            const nodeDOM = this.node.dom;

            nodeDOM.classList.add( 'io-connect' );

            ioDOM.classList.add( 'connect' );
            dom.classList.add( 'select' );

            const defaultOutput = Link.InputDirection === 'left' ? 'lio' : 'rio';

            const link = type === defaultOutput ? new Link( this ) : new Link( null, this );
            const previewLink = new Link( link.inputElement, link.outputElement );

            this.links.push( link );

            draggableDOM( e, ( data ) => {

                if ( previewLink.outputElement )
                    previewLink.outputElement.dom.classList.remove( 'invalid' );

                if ( previewLink.inputElement )
                    previewLink.inputElement.dom.classList.remove( 'invalid' );

                previewLink.inputElement = link.inputElement;
                previewLink.outputElement = link.outputElement;

                if ( type === defaultOutput ) {

                    previewLink.outputElement = selected;

                } else {

                    previewLink.inputElement = selected;

                }

                const isInvalid = previewLink.inputElement !== null && previewLink.outputElement !== null &&
                    previewLink.inputElement.inputLength > 0 && previewLink.outputElement.outputLength > 0 &&
                    dispatchEventList( previewLink.inputElement.events.valid, previewLink.inputElement, previewLink.outputElement, data.dragging ? 'dragging' : 'dragged' ) === false;

                if ( data.dragging && isInvalid ) {

                    if ( type === defaultOutput ) {

                        if ( previewLink.outputElement )
                            previewLink.outputElement.dom.classList.add( 'invalid' );

                    } else {

                        if ( previewLink.inputElement )
                            previewLink.inputElement.dom.classList.add( 'invalid' );

                    }

                    return;

                }

                if ( ! data.dragging ) {

                    nodeDOM.classList.remove( 'io-connect' );

                    ioDOM.classList.remove( 'connect' );
                    dom.classList.remove( 'select' );

                    this.links.splice( this.links.indexOf( link ), 1 );

                    if ( selected !== null && ! isInvalid ) {

                        link.inputElement = previewLink.inputElement;
                        link.outputElement = previewLink.outputElement;

                        // check if is an is circular link

                        if ( link.outputElement.node.isCircular( link.inputElement.node ) ) {

                            return;

                        }

                        //

                        if ( link.inputElement.inputLength > 0 && link.outputElement.outputLength > 0 ) {

                            link.inputElement.connect( link.outputElement );

                        }

                    }

                }

            }, { className: 'connecting' } );

        };

        ioDOM.addEventListener( 'mousedown', onConnectEvent, true );
        ioDOM.addEventListener( 'touchstart', onConnectEvent, true );

        return ioDOM;

    }

onSelect(e: any): void

Parameters:

  • e any

Returns: void

Calls:

  • document.elementFromPoint
Code
( e ) => {

            let element = this;

            if ( e.changedTouches && e.changedTouches.length > 0 ) {

                const touch = e.changedTouches[ 0 ];

                let overDOM = document.elementFromPoint( touch.clientX, touch.clientY );

                while ( overDOM && ( ! overDOM.element || ! overDOM.element.isElement ) ) {

                    overDOM = overDOM.parentNode;

                }

                element = overDOM ? overDOM.element : null;

            }

            const type = e.type;

            if ( ( type === 'mouseout' ) && selected === element ) {

                selected = null;

            } else {

                selected = element;

            }

        }

onDisconnect(): void

Returns: void

Calls:

  • this.dom.removeChild
  • this.disconnectDOM.removeEventListener
  • element.removeEventListener
Code
() => {

                    this.links = [];
                    this.dom.removeChild( this.disconnectDOM );

                    this.disconnectDOM.removeEventListener( 'mousedown', onClick, true );
                    this.disconnectDOM.removeEventListener( 'touchstart', onClick, true );
                    this.disconnectDOM.removeEventListener( 'disconnect', onDisconnect, true );

                    element.removeEventListener( 'connect', onConnect );
                    element.removeEventListener( 'connectChildren', onConnect );
                    element.removeEventListener( 'nodeConnect', onConnect );
                    element.removeEventListener( 'nodeConnectChildren', onConnect );
                    element.removeEventListener( 'dispose', onDispose );

                    this.disconnectDOM = null;

                }

onConnect(): void

Returns: void

Calls:

  • this.dispatchEvent
Code
() => {

                    this.dispatchEvent( new Event( 'connectChildren' ) );

                }

onDispose(): void

Returns: void

Calls:

  • this.connect
Code
() => {

                    this.connect();

                }

onClick(e: any): void

Parameters:

  • e any

Returns: void

Calls:

  • e.stopPropagation
  • this.connect
Code
( e ) => {

                    e.stopPropagation();

                    this.connect();

                }

onConnectEvent(e: any): void

Parameters:

  • e any

Returns: void

Calls:

  • e.preventDefault
  • e.stopPropagation
  • nodeDOM.classList.add
  • ioDOM.classList.add
  • dom.classList.add
  • this.links.push
  • draggableDOM
  • previewLink.outputElement.dom.classList.remove
  • previewLink.inputElement.dom.classList.remove
  • dispatchEventList
  • previewLink.outputElement.dom.classList.add
  • previewLink.inputElement.dom.classList.add
  • nodeDOM.classList.remove
  • ioDOM.classList.remove
  • dom.classList.remove
  • this.links.splice
  • this.links.indexOf
  • link.outputElement.node.isCircular
  • link.inputElement.connect

Internal Comments:

// check if is an is circular link
//

Code
( e ) => {

            e.preventDefault();

            e.stopPropagation();

            selected = null;

            const nodeDOM = this.node.dom;

            nodeDOM.classList.add( 'io-connect' );

            ioDOM.classList.add( 'connect' );
            dom.classList.add( 'select' );

            const defaultOutput = Link.InputDirection === 'left' ? 'lio' : 'rio';

            const link = type === defaultOutput ? new Link( this ) : new Link( null, this );
            const previewLink = new Link( link.inputElement, link.outputElement );

            this.links.push( link );

            draggableDOM( e, ( data ) => {

                if ( previewLink.outputElement )
                    previewLink.outputElement.dom.classList.remove( 'invalid' );

                if ( previewLink.inputElement )
                    previewLink.inputElement.dom.classList.remove( 'invalid' );

                previewLink.inputElement = link.inputElement;
                previewLink.outputElement = link.outputElement;

                if ( type === defaultOutput ) {

                    previewLink.outputElement = selected;

                } else {

                    previewLink.inputElement = selected;

                }

                const isInvalid = previewLink.inputElement !== null && previewLink.outputElement !== null &&
                    previewLink.inputElement.inputLength > 0 && previewLink.outputElement.outputLength > 0 &&
                    dispatchEventList( previewLink.inputElement.events.valid, previewLink.inputElement, previewLink.outputElement, data.dragging ? 'dragging' : 'dragged' ) === false;

                if ( data.dragging && isInvalid ) {

                    if ( type === defaultOutput ) {

                        if ( previewLink.outputElement )
                            previewLink.outputElement.dom.classList.add( 'invalid' );

                    } else {

                        if ( previewLink.inputElement )
                            previewLink.inputElement.dom.classList.add( 'invalid' );

                    }

                    return;

                }

                if ( ! data.dragging ) {

                    nodeDOM.classList.remove( 'io-connect' );

                    ioDOM.classList.remove( 'connect' );
                    dom.classList.remove( 'select' );

                    this.links.splice( this.links.indexOf( link ), 1 );

                    if ( selected !== null && ! isInvalid ) {

                        link.inputElement = previewLink.inputElement;
                        link.outputElement = previewLink.outputElement;

                        // check if is an is circular link

                        if ( link.outputElement.node.isCircular( link.inputElement.node ) ) {

                            return;

                        }

                        //

                        if ( link.inputElement.inputLength > 0 && link.outputElement.outputLength > 0 ) {

                            link.inputElement.connect( link.outputElement );

                        }

                    }

                }

            }, { className: 'connecting' } );

        }

Input.setExtra(value: any): this

Parameters:

  • value any

Returns: this

Code
setExtra( value ) {

        this.extra = value;

        return this;

    }

Input.getExtra(): any

Returns: any

Code
getExtra() {

        return this.extra;

    }

Input.setProperty(name: any): this

Parameters:

  • name any

Returns: this

Code
setProperty( name ) {

        this.property = name;

        return this;

    }

Input.getProperty(): any

Returns: any

Code
getProperty() {

        return this.property;

    }

Input.setTagColor(color: any): this

Parameters:

  • color any

Returns: this

Code
setTagColor( color ) {

        this.tagColor = color;

        this.dom.style[ 'border-left' ] = `2px solid ${color}`;

        return this;

    }

Input.getTagColor(): any

Returns: any

Code
getTagColor() {

        return this.tagColor;

    }

Input.setToolTip(text: any): this

Parameters:

  • text any

Returns: this

Calls:

  • document.createElement
  • this.dom.append
Code
setToolTip( text ) {

        const div = document.createElement( 'f-tooltip' );
        div.innerText = text;

        this.dom.append( div );

        return this;

    }

Input.onChange(callback: any): this

Parameters:

  • callback any

Returns: this

Calls:

  • this.events.change.push
Code
onChange( callback ) {

        this.events.change.push( callback );

        return this;

    }

Input.onClick(callback: any): this

Parameters:

  • callback any

Returns: this

Calls:

  • this.events.click.push
Code
onClick( callback ) {

        this.events.click.push( callback );

        return this;

    }

Input.setReadOnly(value: any): this

Parameters:

  • value any

Returns: this

Calls:

  • this.getInput
Code
setReadOnly( value ) {

        this.getInput().readOnly = value;

        return this;

    }

Input.getReadOnly(): any

Returns: any

Calls:

  • this.getInput
Code
getReadOnly() {

        return this.getInput().readOnly;

    }

Input.setValue(value: any, dispatch: boolean): this

Parameters:

  • value any
  • dispatch boolean

Returns: this

Calls:

  • this.getInput
  • this.dispatchEvent
Code
setValue( value, dispatch = true ) {

        this.getInput().value = value;

        if ( dispatch ) this.dispatchEvent( new Event( 'change' ) );

        return this;

    }

Input.getValue(): any

Returns: any

Calls:

  • this.getInput
Code
getValue() {

        return this.getInput().value;

    }

Input.getInput(): any

Returns: any

Code
getInput() {

        return this.dom;

    }

Input.serialize(data: any): void

Parameters:

  • data any

Returns: void

Calls:

  • this.getValue
Code
serialize( data ) {

        data.value = this.getValue();

    }

Input.deserialize(data: any): void

Parameters:

  • data any

Returns: void

Calls:

  • this.setValue
Code
deserialize( data ) {

        this.setValue( data.value );

    }

Node.setAlign(align: any): this

Parameters:

  • align any

Returns: this

Calls:

  • dom.classList.add
Code
setAlign( align ) {

        const dom = this.dom;
        const style = dom.style;

        style.left = '';
        style.top = '';
        style.animation = 'none';

        if ( typeof align === 'string' ) {

            dom.classList.add( align );

        } else if ( align ) {

            for ( const name in align ) {

                style[ name ] = align[ name ];

            }

        }

        return this;

    }

Node.setResizable(val: any): this

Parameters:

  • val any

Returns: this

Calls:

  • this.dom.classList.add
  • this.dom.classList.remove
  • this.updateSize
Code
setResizable( val ) {

        this.resizable = val === true;

        if ( this.resizable ) {

            this.dom.classList.add( 'resizable' );

        } else {

            this.dom.classList.remove( 'resizable' );

        }

        this.updateSize();

        return this;

    }

Node.onFocus(callback: any): this

Parameters:

  • callback any

Returns: this

Calls:

  • this.events.focus.push
Code
onFocus( callback ) {

        this.events.focus.push( callback );

        return this;

    }

Node.onBlur(callback: any): this

Parameters:

  • callback any

Returns: this

Calls:

  • this.events.blur.push
Code
onBlur( callback ) {

        this.events.blur.push( callback );

        return this;

    }

Node.setStyle(style: any): this

Parameters:

  • style any

Returns: this

Calls:

  • dom.classList.remove
  • dom.classList.add
Code
setStyle( style ) {

        const dom = this.dom;

        if ( this.style ) dom.classList.remove( this.style );

        if ( style ) dom.classList.add( style );

        this.style = style;

        return this;

    }

Node.setPosition(x: any, y: any): this

Parameters:

  • x any
  • y any

Returns: this

Calls:

  • numberToPX
Code
setPosition( x, y ) {

        const dom = this.dom;

        dom.style.left = numberToPX( x );
        dom.style.top = numberToPX( y );

        return this;

    }

Node.getPosition(): { x: number; y: number; }

Returns: { x: number; y: number; }

Calls:

  • parseInt
Code
getPosition() {

        const dom = this.dom;

        return {
            x: parseInt( dom.style.left ),
            y: parseInt( dom.style.top )
        };

    }

Node.setWidth(val: any): this

Parameters:

  • val any

Returns: this

Calls:

  • numberToPX
  • this.updateSize
Code
setWidth( val ) {

        this.dom.style.width = numberToPX( val );

        this.updateSize();

        return this;

    }

Node.getWidth(): number

Returns: number

Calls:

  • parseInt
Code
getWidth() {

        return parseInt( this.dom.style.width );

    }

Node.getHeight(): number

Returns: number

Code
getHeight() {

        return this.dom.offsetHeight;

    }

Node.getBound(): { x: number; y: number; width: number; height: number; }

Returns: { x: number; y: number; width: number; height: number; }

Calls:

  • this.getPosition
  • this.dom.getBoundingClientRect
Code
getBound() {

        const { x, y } = this.getPosition();
        const { width, height } = this.dom.getBoundingClientRect();

        return { x, y, width, height };

    }

Node.add(element: any): this

Parameters:

  • element any

Returns: this

Calls:

  • this.elements.push
  • element.addEventListener
  • this.dom.append
  • this.updateSize
Code
add( element ) {

        this.elements.push( element );

        element.node = this;
        element.addEventListener( 'connect', this._onConnect );
        element.addEventListener( 'connectChildren', this._onConnectChildren );

        this.dom.append( element.dom );

        this.updateSize();

        return this;

    }

Node.remove(element: any): this

Parameters:

  • element any

Returns: this

Calls:

  • this.elements.splice
  • this.elements.indexOf
  • element.removeEventListener
  • this.dom.removeChild
  • this.updateSize
Code
remove( element ) {

        this.elements.splice( this.elements.indexOf( element ), 1 );

        element.node = null;
        element.removeEventListener( 'connect', this._onConnect );
        element.removeEventListener( 'connectChildren', this._onConnectChildren );

        this.dom.removeChild( element.dom );

        this.updateSize();

        return this;

    }

Node.dispose(): void

Returns: void

Calls:

  • canvas.remove
  • element.dispose
  • this.dispatchEvent
Code
dispose() {

        const canvas = this.canvas;

        if ( canvas !== null ) canvas.remove( this );

        for ( const element of this.elements ) {

            element.dispose();

        }

        this.dispatchEvent( new Event( 'dispose' ) );

    }

Node.isCircular(node: any): boolean

Parameters:

  • node any

Returns: boolean

Calls:

  • this.getLinks
  • link.outputElement.node.isCircular
Code
isCircular( node ) {

        if ( node === this ) return true;

        const links = this.getLinks();

        for ( const link of links ) {

            if ( link.outputElement.node.isCircular( node ) ) {

                return true;

            }

        }

        return false;

    }

Returns: any[]

Calls:

  • links.push
Code
getLinks() {

        const links = [];

        for ( const element of this.elements ) {

            links.push( ...element.links );

        }

        return links;

    }

Node.getColor(): any

Returns: any

Calls:

  • this.elements[ 0 ].getColor
Code
getColor() {

        return this.elements.length > 0 ? this.elements[ 0 ].getColor() : null;

    }

Node.updateSize(): void

Returns: void

Code
updateSize() {

        for ( const element of this.elements ) {

            element.dom.style.width = '';

        }

        if ( this.resizable === true ) {

            const element = this.elements[ this.elements.length - 1 ];

            if ( element !== undefined ) {

                element.dom.style.width = this.dom.style.width;

            }

        }

    }

Node.serialize(data: any): void

Parameters:

  • data any

Returns: void

Calls:

  • this.getPosition
  • elements.push
  • element.toJSON
  • this.getWidth
Code
serialize( data ) {

        const { x, y } = this.getPosition();

        const elements = [];

        for ( const element of this.elements ) {

            elements.push( element.toJSON( data ).id );

        }

        data.x = x;
        data.y = y;
        data.width = this.getWidth();
        data.elements = elements;
        data.autoResize = this.resizable;

        if ( this.style !== '' ) {

            data.style = this.style;

        }

    }

Node.deserialize(data: any): void

Parameters:

  • data any

Returns: void

Calls:

  • this.setPosition
  • this.setWidth
  • this.setResizable
  • this.setStyle
  • this.add
Code
deserialize( data ) {

        this.setPosition( data.x, data.y );
        this.setWidth( data.width );
        this.setResizable( data.autoResize );

        if ( data.style !== undefined ) {

            this.setStyle( data.style );

        }

        const elements = this.elements;

        if ( elements.length > 0 ) {

            let index = 0;

            for ( const id of data.elements ) {

                data.objects[ id ] = elements[ index ++ ];

            }

        } else {

            for ( const id of data.elements ) {

                this.add( data.objects[ id ] );

            }

        }

    }

onDown(): void

Returns: void

Calls:

  • canvas.select
Code
() => {

            const canvas = this.canvas;

            if ( canvas !== null ) {

                canvas.select( this );

            }

        }

onDrag(e: any): void

Parameters:

  • e any

Returns: void

Calls:

  • e.preventDefault
  • draggableDOM
Code
( e ) => {

            e.preventDefault();

            if ( this.draggable === true ) {

                draggableDOM( this.node.dom, null, { className: 'dragging node' } );

            }

        }

TitleElement.setIcon(value: any): this

Parameters:

  • value any

Returns: this

Code
setIcon( value ) {

        this.iconDOM.className = value;

        return this;

    }

TitleElement.getIcon(): string

Returns: string

Code
getIcon() {

        return this.iconDOM.className;

    }

TitleElement.setTitle(value: any): this

Parameters:

  • value any

Returns: this

Code
setTitle( value ) {

        this.titleDOM.innerText = value;

        return this;

    }

TitleElement.getTitle(): string

Returns: string

Code
getTitle() {

        return this.titleDOM.innerText;

    }

TitleElement.addButton(button: any): this

Parameters:

  • button any

Returns: this

Calls:

  • this.buttons.push
  • this.toolbarDOM.append
Code
addButton( button ) {

        this.buttons.push( button );

        this.toolbarDOM.append( button.dom );

        return this;

    }

TitleElement.serialize(data: any): void

Parameters:

  • data any

Returns: void

Calls:

  • super.serialize
  • this.getTitle
  • this.getIcon
Code
serialize( data ) {

        super.serialize( data );

        const title = this.getTitle();
        const icon = this.getIcon();

        data.title = title;

        if ( icon !== '' ) {

            data.icon = icon;

        }

    }

TitleElement.deserialize(data: any): void

Parameters:

  • data any

Returns: void

Calls:

  • super.deserialize
  • this.setTitle
  • this.setIcon
Code
deserialize( data ) {

        super.deserialize( data );

        this.setTitle( data.title );

        if ( data.icon !== undefined ) {

            this.setIcon( data.icon );

        }

    }

dbClick(): void

Returns: void

Code
() => {

            this.node.canvas.focusSelected = ! this.node.canvas.focusSelected;

        }

drawLine(p1x: any, p1y: any, p2x: any, p2y: any, invert: any, size: any, colorA: any, ctx: any, colorB: any): void

Parameters:

  • p1x any
  • p1y any
  • p2x any
  • p2y any
  • invert any
  • size any
  • colorA any
  • ctx any
  • colorB any

Returns: void

Calls:

  • Math.sqrt
  • ctx.beginPath
  • ctx.moveTo
  • ctx.bezierCurveTo
  • ctx.createLinearGradient
  • gradient.addColorStop
  • ctx.stroke
Code
( p1x, p1y, p2x, p2y, invert, size, colorA, ctx, colorB = null ) => {

    const dx = p2x - p1x;
    const dy = p2y - p1y;
    const offset = Math.sqrt( ( dx * dx ) + ( dy * dy ) ) * ( invert ? - .3 : .3 );

    ctx.beginPath();

    ctx.moveTo( p1x, p1y );

    ctx.bezierCurveTo(
        p1x + offset, p1y,
        p2x - offset, p2y,
        p2x, p2y
    );

    if ( colorB !== null && colorA !== colorB ) {

        const gradient = ctx.createLinearGradient( p1x, p1y, p2x, p2y );
        gradient.addColorStop( 0, colorA );
        gradient.addColorStop( 1, colorB );

        ctx.strokeStyle = gradient;

    } else {

        ctx.strokeStyle = colorA;

    }

    ctx.lineWidth = size;
    ctx.stroke();

}

Canvas.getBounds(): { x: number; y: number; width: number; height: number; }

Returns: { x: number; y: number; width: number; height: number; }

Calls:

  • node.getBound
  • Math.min
  • Math.max
  • Math.round
Code
getBounds() {

        const bounds = { x: Infinity, y: Infinity, width: - Infinity, height: - Infinity };

        for ( const node of this.nodes ) {

            const { x, y, width, height } = node.getBound();

            bounds.x = Math.min( bounds.x, x );
            bounds.y = Math.min( bounds.y, y );
            bounds.width = Math.max( bounds.width, x + width );
            bounds.height = Math.max( bounds.height, y + height );

        }

        bounds.x = Math.round( bounds.x );
        bounds.y = Math.round( bounds.y );
        bounds.width = Math.round( bounds.width );
        bounds.height = Math.round( bounds.height );

        return bounds;

    }

Canvas.updateMozTransform(): void

Returns: void

Code
updateMozTransform() {

        if ( this.useTransform === false ) return;

        this.contentDOM.style[ '-moz-transform' ] = 'scale(' + this.zoom + ')';
        this.contentDOM.style[ '-moz-transform-origin' ] = '-' + this.contentDOM.style.left + ' -' + this.contentDOM.style.top;

    }

Canvas.onDrop(callback: any): this

Parameters:

  • callback any

Returns: this

Calls:

  • this.events.drop.push
Code
onDrop( callback ) {

        this.events.drop.push( callback );

        return this;

    }

Canvas.start(): void

Returns: void

Calls:

  • document.addEventListener
  • requestAnimationFrame
Code
start() {

        this.updating = true;

        document.addEventListener( 'wheel', this._onMoveEvent, true );

        document.addEventListener( 'mousedown', this._onMoveEvent, true );
        document.addEventListener( 'touchstart', this._onMoveEvent, true );

        document.addEventListener( 'mousemove', this._onMoveEvent, true );
        document.addEventListener( 'touchmove', this._onMoveEvent, true );

        document.addEventListener( 'dragover', this._onMoveEvent, true );

        requestAnimationFrame( this._onUpdate );

    }

Canvas.stop(): void

Returns: void

Calls:

  • document.removeEventListener
Code
stop() {

        this.updating = false;

        document.removeEventListener( 'wheel', this._onMoveEvent, true );

        document.removeEventListener( 'mousedown', this._onMoveEvent, true );
        document.removeEventListener( 'touchstart', this._onMoveEvent, true );

        document.removeEventListener( 'mousemove', this._onMoveEvent, true );
        document.removeEventListener( 'touchmove', this._onMoveEvent, true );

        document.removeEventListener( 'dragover', this._onMoveEvent, true );

    }

Canvas.add(node: any): this

Parameters:

  • node any

Returns: this

Calls:

  • this.nodes.push
  • this.contentDOM.append
Code
add( node ) {

        if ( node.canvas === this ) return;

        this.nodes.push( node );

        node.canvas = this;

        this.contentDOM.append( node.dom );

        return this;

    }

Canvas.remove(node: any): this

Parameters:

  • node any

Returns: this

Calls:

  • this.select
  • this.unlink
  • nodes.splice
  • nodes.indexOf
  • this.contentDOM.removeChild
  • node.dispatchEvent
Code
remove( node ) {

        if ( node === this.selected ) {

            this.select();

        }

        this.unlink( node );

        const nodes = this.nodes;

        nodes.splice( nodes.indexOf( node ), 1 );

        node.canvas = null;

        this.contentDOM.removeChild( node.dom );

        node.dispatchEvent( new Event( 'remove' ) );

        return this;

    }

Canvas.clear(): this

Returns: this

Calls:

  • this.remove
Code
clear() {

        const nodes = this.nodes;

        while ( nodes.length > 0 ) {

            this.remove( nodes[ 0 ] );

        }

        return this;

    }

Canvas.unlink(node: any): void

Parameters:

  • node any

Returns: void

Calls:

  • this.getLinks
  • link.inputElement.connect
Code
unlink( node ) {

        const links = this.getLinks();

        for ( const link of links ) {

            if ( link.inputElement && link.outputElement ) {

                if ( link.inputElement.node === node ) {

                    link.inputElement.connect();

                } else if ( link.outputElement.node === node ) {

                    link.inputElement.connect();

                }

            }

        }

    }

Returns: any[]

Calls:

  • links.push
  • node.getLinks
Code
getLinks() {

        const links = [];

        for ( const node of this.nodes ) {

            links.push( ...node.getLinks() );

        }

        return links;

    }

Canvas.centralize(): this

Returns: this

Calls:

  • this.getBounds
Code
centralize() {

        const bounds = this.getBounds();

        this.scrollLeft = ( this.canvas.width / 2 ) - ( ( - bounds.x + bounds.width ) / 2 );
        this.scrollTop = ( this.canvas.height / 2 ) - ( ( - bounds.y + bounds.height ) / 2 );

        return this;

    }

Canvas.setSize(width: any, height: any): this

Parameters:

  • width any
  • height any

Returns: this

Calls:

  • this.update
Code
setSize( width, height ) {

        this._width = width;
        this._height = height;

        this.update();

        return this;

    }

Canvas.select(node: any): void

Parameters:

  • node any

Returns: void

Calls:

  • previousNode.dom.classList.remove
  • dispatchEventList
  • node.dom.classList.add
Code
select( node = null ) {

        if ( node === this.selected ) return;

        const previousNode = this.selected;

        if ( previousNode !== null ) {

            this.focusSelected = false;

            previousNode.dom.classList.remove( 'selected' );

            this.selected = null;

            dispatchEventList( previousNode.events.blur, previousNode );

        }

        if ( node !== null ) {

            node.dom.classList.add( 'selected' );

            this.selected = node;

            dispatchEventList( node.events.focus, node );

        }

    }

Canvas.updateMap(): void

Returns: void

Calls:

  • this.getBounds
  • mapContext.clearRect
  • mapContext.fillRect
  • Math.min
  • node.getBound
  • node.getColor

Internal Comments:

// (x4)

Code
updateMap() {

        const { nodes, mapCanvas, mapContext, scrollLeft, scrollTop, canvas, zoom, _mapInfo } = this;

        const bounds = this.getBounds();

        mapCanvas.width = 300;
        mapCanvas.height = 200;

        mapContext.clearRect( 0, 0, mapCanvas.width, mapCanvas.height );

        mapContext.fillStyle = 'rgba( 0, 0, 0, 0 )';
        mapContext.fillRect( 0, 0, mapCanvas.width, mapCanvas.height );

        const boundsWidth = - bounds.x + bounds.width;
        const boundsHeight = - bounds.y + bounds.height;

        const mapScale = Math.min( mapCanvas.width / boundsWidth, mapCanvas.height / boundsHeight ) * .5;

        const boundsMapWidth = boundsWidth * mapScale;
        const boundsMapHeight = boundsHeight * mapScale;

        const boundsOffsetX = ( mapCanvas.width / 2 ) - ( boundsMapWidth / 2 );
        const boundsOffsetY = ( mapCanvas.height / 2 ) - ( boundsMapHeight / 2 );

        let selectedNode = null;

        for ( const node of nodes ) {

            const nodeBound = node.getBound();
            const nodeColor = node.getColor();

            nodeBound.x += - bounds.x;
            nodeBound.y += - bounds.y;

            nodeBound.x *= mapScale;
            nodeBound.y *= mapScale;
            nodeBound.width *= mapScale;
            nodeBound.height *= mapScale;

            nodeBound.x += boundsOffsetX;
            nodeBound.y += boundsOffsetY;

            if ( node !== this.selected ) {

                mapContext.fillStyle = nodeColor;
                mapContext.fillRect( nodeBound.x, nodeBound.y, nodeBound.width, nodeBound.height );

            } else {

                selectedNode = {
                    nodeBound,
                    nodeColor
                };

            }

        }

        if ( selectedNode !== null ) {

            const { nodeBound, nodeColor } = selectedNode;

            mapContext.fillStyle = nodeColor;
            mapContext.fillRect( nodeBound.x, nodeBound.y, nodeBound.width, nodeBound.height );

        }

        const screenMapX = ( - ( scrollLeft + bounds.x ) * mapScale ) + boundsOffsetX;
        const screenMapY = ( - ( scrollTop + bounds.y ) * mapScale ) + boundsOffsetY;
        const screenMapWidth = ( canvas.width * mapScale ) / zoom;
        const screenMapHeight = ( canvas.height * mapScale ) / zoom;

        mapContext.fillStyle = 'rgba( 200, 200, 200, 0.1 )';
        mapContext.fillRect( screenMapX, screenMapY, screenMapWidth, screenMapHeight );

        //

        _mapInfo.scale = mapScale;
        _mapInfo.left = ( - boundsOffsetX / mapScale ) + bounds.x;
        _mapInfo.top = ( - boundsOffsetY / mapScale ) + bounds.y;
        _mapInfo.width = mapCanvas.width / mapScale;
        _mapInfo.height = mapCanvas.height / mapScale;
        _mapInfo.screen.x = screenMapX;
        _mapInfo.screen.y = screenMapY;
        _mapInfo.screen.width = screenMapWidth;
        _mapInfo.screen.height = screenMapHeight;

    }

Canvas.updateLines(): void

Returns: void

Calls:

  • context.clearRect
  • frontContext.clearRect
  • this.getLinks
  • lioElement.dom.getBoundingClientRect
  • Math.max
  • rioElement.dom.getBoundingClientRect
  • lioElement.getRIOColor
  • rioElement.getLIOColor
  • drawLine
  • Math.min
  • context.fillRect
  • dom.classList.add
  • dom.classList.remove

Internal Comments:

// (x4)

Code
updateLines() {

        const { dom, zoom, canvas, frontCanvas, frontContext, context, _width, _height, useTransform } = this;

        const domRect = this.rect;

        if ( canvas.width !== _width || canvas.height !== _height ) {

            canvas.width = _width;
            canvas.height = _height;

            frontCanvas.width = _width;
            frontCanvas.height = _height;

        }

        context.clearRect( 0, 0, _width, _height );
        frontContext.clearRect( 0, 0, _width, _height );

        //

        context.globalCompositeOperation = 'lighter';
        frontContext.globalCompositeOperation = 'source-over';

        const links = this.getLinks();

        const aPos = { x: 0, y: 0 };
        const bPos = { x: 0, y: 0 };

        const offsetIORadius = 10;

        let dragging = '';

        for ( const link of links ) {

            const { lioElement, rioElement } = link;

            let draggingLink = '';
            let length = 0;

            if ( lioElement !== null ) {

                const rect = lioElement.dom.getBoundingClientRect();

                length = Math.max( length, lioElement.rioLength );

                aPos.x = rect.x + rect.width;
                aPos.y = rect.y + ( rect.height / 2 );

                if ( useTransform ) {

                    aPos.x /= zoom;
                    aPos.y /= zoom;

                }

            } else {

                aPos.x = this.clientX;
                aPos.y = this.clientY;

                draggingLink = 'lio';

            }

            if ( rioElement !== null ) {

                const rect = rioElement.dom.getBoundingClientRect();

                length = Math.max( length, rioElement.lioLength );

                bPos.x = rect.x;
                bPos.y = rect.y + ( rect.height / 2 );

                if ( useTransform ) {

                    bPos.x /= zoom;
                    bPos.y /= zoom;

                }

            } else {

                bPos.x = this.clientX;
                bPos.y = this.clientY;

                draggingLink = 'rio';

            }

            dragging = dragging || draggingLink;

            const drawContext = draggingLink ? frontContext : context;

            if ( draggingLink || length === 1 ) {

                let colorA = null,
                    colorB = null;

                if ( draggingLink === 'rio' ) {

                    colorA = colorB = lioElement.getRIOColor();

                    aPos.x += offsetIORadius;
                    bPos.x /= zoom;
                    bPos.y /= zoom;

                } else if ( draggingLink === 'lio' ) {

                    colorA = colorB = rioElement.getLIOColor();

                    bPos.x -= offsetIORadius;
                    aPos.x /= zoom;
                    aPos.y /= zoom;

                } else {

                    colorA = lioElement.getRIOColor();
                    colorB = rioElement.getLIOColor();

                }

                drawLine(
                    aPos.x * zoom, aPos.y * zoom,
                    bPos.x * zoom, bPos.y * zoom,
                    false, 2, colorA || '#ffffff', drawContext, colorB || '#ffffff'
                );

            } else {

                length = Math.min( length, 4 );

                for ( let i = 0; i < length; i ++ ) {

                    const color = colors[ i ] || '#ffffff';

                    const marginY = 4;

                    const rioLength = Math.min( lioElement.rioLength, length );
                    const lioLength = Math.min( rioElement.lioLength, length );

                    const colorA = lioElement.getRIOColor() || color;
                    const colorB = rioElement.getLIOColor() || color;

                    const aCenterY = ( ( rioLength * marginY ) * .5 ) - ( marginY / 2 );
                    const bCenterY = ( ( lioLength * marginY ) * .5 ) - ( marginY / 2 );

                    const aIndex = Math.min( i, rioLength - 1 );
                    const bIndex = Math.min( i, lioLength - 1 );

                    const aPosY = ( aIndex * marginY ) - 1;
                    const bPosY = ( bIndex * marginY ) - 1;

                    drawLine(
                        aPos.x * zoom, ( ( aPos.y + aPosY ) - aCenterY ) * zoom,
                        bPos.x * zoom, ( ( bPos.y + bPosY ) - bCenterY ) * zoom,
                        false, 2, colorA, drawContext, colorB
                    );

                }

            }

        }

        context.globalCompositeOperation = 'destination-in';

        context.fillRect( domRect.x, domRect.y, domRect.width, domRect.height );

        if ( dragging !== '' ) {

            dom.classList.add( 'dragging-' + dragging );

        } else {

            dom.classList.remove( 'dragging-lio' );
            dom.classList.remove( 'dragging-rio' );

        }

    }

Canvas.update(): void

Returns: void

Calls:

  • requestAnimationFrame
  • this.updateLines
  • this.updateMap
Code
update() {

        if ( this.updating === false ) return;

        requestAnimationFrame( this._onUpdate );

        this.updateLines();
        this.updateMap();

    }

Canvas.serialize(data: any): void

Parameters:

  • data any

Returns: void

Calls:

  • this.nodes.sort
  • nodes.push
  • node.toJSON
Code
serialize( data ) {

        const nodes = [];
        const serializeNodes = this.nodes.sort( ( a, b ) => a.serializePriority > b.serializePriority ? - 1 : 1 );

        for ( const node of serializeNodes ) {

            nodes.push( node.toJSON( data ).id );

        }

        data.nodes = nodes;

    }

Canvas.deserialize(data: any): void

Parameters:

  • data any

Returns: void

Calls:

  • this.add
Code
deserialize( data ) {

        for ( const id of data.nodes ) {

            this.add( data.objects[ id ] );

        }

    }

zoomTo(zoom: any, clientX: number, clientY: number): void

Parameters:

  • zoom any
  • clientX number
  • clientY number

Returns: void

Calls:

  • Math.min
  • Math.max
Code
( zoom, clientX = this.clientX, clientY = this.clientY ) => {

            zoom = Math.min( Math.max( zoom, .2 ), 1 );

            this.scrollLeft -= ( clientX / this.zoom ) - ( clientX / zoom );
            this.scrollTop -= ( clientY / this.zoom ) - ( clientY / zoom );
            this.zoom = zoom;

        }

onTouchStart(): void

Returns: void

Code
() => {

            touchData = null;

        }

classInElements(element: any, className: any): boolean

Parameters:

  • element any
  • className any

Returns: boolean

Calls:

  • element.classList.contains
Code
( element, className ) => {

            do {

                if ( element.classList ? element.classList.contains( className ) : false ) {

                    return true;

                }

            } while ( ( element = element.parentElement ) && element !== dom );

            return false;

        }

onMouseZoom(e: any): void

Parameters:

  • e any

Returns: void

Calls:

  • classInElements
  • e.preventDefault
  • e.stopImmediatePropagation
  • zoomTo
Code
( e ) => {

            if ( classInElements( e.srcElement, 'f-scroll' ) ) return;

            e.preventDefault();

            e.stopImmediatePropagation();

            const delta = e.deltaY * .003;

            zoomTo( this.zoom - delta );

        }

onTouchZoom(e: any): void

Parameters:

  • e any

Returns: void

Calls:

  • e.preventDefault
  • e.stopImmediatePropagation
  • Math.hypot
  • zoomTo
Code
( e ) => {

            if ( e.touches && e.touches.length === 2 ) {

                e.preventDefault();

                e.stopImmediatePropagation();

                const clientX = ( e.touches[ 0 ].clientX + e.touches[ 1 ].clientX ) / 2;
                const clientY = ( e.touches[ 0 ].clientY + e.touches[ 1 ].clientY ) / 2;

                const distance = Math.hypot(
                    e.touches[ 0 ].clientX - e.touches[ 1 ].clientX,
                    e.touches[ 0 ].clientY - e.touches[ 1 ].clientY
                );

                if ( touchData === null ) {

                    touchData = {
                        distance
                    };

                }

                const delta = ( touchData.distance - distance ) * .003;
                touchData.distance = distance;

                zoomTo( this.zoom - delta, clientX, clientY );

            }

        }

onTouchMove(e: any): void

Parameters:

  • e any

Returns: void

Calls:

  • e.preventDefault
  • e.stopImmediatePropagation
Code
( e ) => {

            if ( e.touches && e.touches.length === 1 ) {

                e.preventDefault();

                e.stopImmediatePropagation();

                const clientX = e.touches[ 0 ].clientX;
                const clientY = e.touches[ 0 ].clientY;

                if ( touchData === null ) {

                    const { scrollLeft, scrollTop } = this;

                    touchData = {
                        scrollLeft,
                        scrollTop,
                        clientX,
                        clientY
                    };

                }

                const zoom = this.zoom;

                this.scrollLeft = touchData.scrollLeft + ( ( clientX - touchData.clientX ) / zoom );
                this.scrollTop = touchData.scrollTop + ( ( clientY - touchData.clientY ) / zoom );

            }

        }

dragState(enter: any): void

Parameters:

  • enter any

Returns: void

Calls:

  • dropDOM.classList.add
  • this.add
  • dropDOM.classList.remove
  • this.remove
Code
( enter ) => {

            if ( enter ) {

                if ( dropEnterCount ++ === 0 ) {

                    this.droppedItems = [];

                    dropDOM.classList.add( 'visible' );

                    this.add( dropNode );

                }

            } else if ( -- dropEnterCount === 0 ) {

                dropDOM.classList.remove( 'visible' );

                this.remove( dropNode );

            }

        }

Parameters:

  • callback any

Returns: this

Calls:

  • this.events.context.push
Code
onContext( callback ) {

        this.events.context.push( callback );

        return this;

    }

Parameters:

  • align any

Returns: this

Calls:

  • removeDOMClass
  • addDOMClass
Code
setAlign( align ) {

        const dom = this.dom;

        removeDOMClass( dom, this.align );
        addDOMClass( dom, align );

        this.align = align;

        return this;

    }

Returns: string

Code
getAlign() {

        return this.align;

    }

Returns: this

Calls:

  • this.dom.classList.remove
  • this.dispatchEvent
Code
show() {

        this.dom.classList.remove( 'hidden' );

        this.visible = true;

        this.dispatchEvent( new Event( 'show' ) );

        return this;

    }

Returns: void

Calls:

  • this.dom.classList.add
  • this.dispatchEvent
Code
hide() {

        this.dom.classList.add( 'hidden' );

        this.dispatchEvent( new Event( 'hide' ) );

        this.visible = false;

    }

Parameters:

  • button any
  • submenu any

Returns: this

Calls:

  • document.createElement
  • liDOM.classList.add
  • liDOM.append
  • this.subMenus.set
  • button.dom.addEventListener
  • submenu.show
  • submenu.hide
  • this.buttons.push
  • this.listDOM.append
  • this.domButtons.set
Code
add( button, submenu = null ) {

        const liDOM = document.createElement( 'f-item' );

        if ( submenu !== null ) {

            liDOM.classList.add( 'submenu' );

            liDOM.append( submenu.dom );

            this.subMenus.set( button, submenu );

            button.dom.addEventListener( 'mouseover', () => submenu.show() );
            button.dom.addEventListener( 'mouseout', () => submenu.hide() );

        }

        liDOM.append( button.dom );

        this.buttons.push( button );

        this.listDOM.append( liDOM );

        this.domButtons.set( button, liDOM );

        return this;

    }

Returns: void

Calls:

  • this.listDOM.firstChild.remove
Code
clear() {

        this.buttons = [];

        this.subMenus = new WeakMap();
        this.domButtons = new WeakMap();

        while ( this.listDOM.firstChild ) {

            this.listDOM.firstChild.remove();

        }

    }

onCloseLastContext(e: any): void

Parameters:

  • e any

Returns: void

Calls:

  • e.target.closest
  • lastContext.hide
Code
( e ) => {

    if ( lastContext && lastContext.visible === true && e.target.closest( 'f-menu.context' ) === null ) {

        lastContext.hide();

    }

}

ContextMenu.openFrom(dom: any): this

Parameters:

  • dom any

Returns: this

Calls:

  • dom.getBoundingClientRect
  • this.open
Code
openFrom( dom ) {

        const rect = dom.getBoundingClientRect();

        return this.open( rect.x + ( rect.width / 2 ), rect.y + ( rect.height / 2 ) );

    }

ContextMenu.open(x: number, y: number): this

Parameters:

  • x number
  • y number

Returns: this

Calls:

  • lastContext.hide
  • this.setPosition
  • document.body.append
  • this.show
Code
open( x = pointer.x, y = pointer.y ) {

        if ( lastContext !== null ) {

            lastContext.hide();

        }

        lastContext = this;

        this.setPosition( x, y );

        document.body.append( this.dom );

        return this.show();

    }

ContextMenu.setWidth(width: any): this

Parameters:

  • width any

Returns: this

Calls:

  • numberToPX
Code
setWidth( width ) {

        this.dom.style.width = numberToPX( width );

        return this;

    }

ContextMenu.setPosition(x: any, y: any): this

Parameters:

  • x any
  • y any

Returns: this

Calls:

  • numberToPX
Code
setPosition( x, y ) {

        const dom = this.dom;

        dom.style.left = numberToPX( x );
        dom.style.top = numberToPX( y );

        return this;

    }

ContextMenu.setTarget(target: any): this

Parameters:

  • target any

Returns: this

Calls:

  • e.preventDefault
  • this.dispatchEvent
  • this.open
  • target.addEventListener
Code
setTarget( target = null ) {

        if ( target !== null ) {

            const onContextMenu = ( e ) => {

                e.preventDefault();

                if ( e.pointerType !== 'mouse' || ( e.pageX === 0 && e.pageY === 0 ) ) return;

                this.dispatchEvent( new Event( 'context' ) );

                this.open();

            };

            this.target = target;

            target.addEventListener( 'contextmenu', onContextMenu, false );

        }

        return this;

    }

ContextMenu.show(): this

Returns: this

Calls:

  • this.dom.getBoundingClientRect
  • Math.min
  • this.setPosition
  • super.show

Internal Comments:

// flip submenus

Code
show() {

        if ( ! this.opened ) {

            this.dom.style.left = '';
            this.dom.style.transform = '';

        }

        const domRect = this.dom.getBoundingClientRect();

        let offsetX = Math.min( window.innerWidth - ( domRect.x + domRect.width + 10 ), 0 );
        let offsetY = Math.min( window.innerHeight - ( domRect.y + domRect.height + 10 ), 0 );

        if ( this.opened ) {

            if ( offsetX < 0 ) offsetX = - domRect.width;
            if ( offsetY < 0 ) offsetY = - domRect.height;

            this.setPosition( domRect.x + offsetX, domRect.y + offsetY );

        } else {

            // flip submenus

            if ( offsetX < 0 ) this.dom.style.left = '-100%';
            if ( offsetY < 0 ) this.dom.style.transform = 'translateY( calc( 32px - 100% ) )';

        }

        return super.show();

    }

ContextMenu.hide(): void

Returns: void

Calls:

  • super.hide
Code
hide() {

        if ( this.opened ) {

            lastContext = null;

        }

        return super.hide();

    }

ContextMenu.add(button: any, submenu: any): this

Parameters:

  • button any
  • submenu any

Returns: this

Calls:

  • button.addEventListener
  • super.add
Code
add( button, submenu = null ) {

        button.addEventListener( 'click', this._onButtonClick );
        button.addEventListener( 'mouseover', this._onButtonMouseOver );

        return super.add( button, submenu );

    }

onContextMenu(e: any): void

Parameters:

  • e any

Returns: void

Calls:

  • e.preventDefault
  • this.dispatchEvent
  • this.open
Code
( e ) => {

                e.preventDefault();

                if ( e.pointerType !== 'mouse' || ( e.pageX === 0 && e.pageY === 0 ) ) return;

                this.dispatchEvent( new Event( 'context' ) );

                this.open();

            }

Tips.message(str: any): this

Parameters:

  • str any

Returns: this

Calls:

  • this.tip
Code
message( str ) {

        return this.tip( str );

    }

Tips.error(str: any): this

Parameters:

  • str any

Returns: this

Calls:

  • this.tip
Code
error( str ) {

        return this.tip( str, 'error' );

    }

Tips.tip(html: any, className: string): this

Parameters:

  • html any
  • className string

Returns: this

Calls:

  • document.createElement
  • this.dom.prepend
  • Math.min
  • setTimeout
  • Math.max
  • dom.remove

Internal Comments:

//requestAnimationFrame( () => dom.style.opacity = 1 ); (x4)

Code
tip( html, className = '' ) {

        const dom = document.createElement( 'f-tip' );
        dom.className = className;
        dom.innerHTML = html;

        this.dom.prepend( dom );

        //requestAnimationFrame( () => dom.style.opacity = 1 );

        this.time = Math.min( this.time + this.duration, this.duration );

        setTimeout( () => {

            this.time = Math.max( this.time - this.duration, 0 );

            dom.style.opacity = 0;

            setTimeout( () => dom.remove(), 250 );

        }, this.time );

        return this;

    }

filterString(str: any): any

Parameters:

  • str any

Returns: any

Calls:

  • str.trim().toLowerCase().replace
Code
( str ) => {

    return str.trim().toLowerCase().replace( /\s\s+/g, ' ' );

}

Search.submit(): this

Returns: this

Calls:

  • this.dispatchEvent
  • this.setValue
Code
submit() {

        this.dispatchEvent( new Event( 'submit' ) );

        return this.setValue( '' );

    }

Search.setValue(value: any): this

Parameters:

  • value any

Returns: this

Calls:

  • this.filter
Code
setValue( value ) {

        this.inputDOM.value = value;

        this.filter( value );

        return this;

    }

Search.getValue(): string

Returns: string

Code
getValue() {

        return this.value;

    }

Search.onFilter(callback: any): this

Parameters:

  • callback any

Returns: this

Calls:

  • this.events.filter.push
Code
onFilter( callback ) {

        this.events.filter.push( callback );

        return this;

    }

Search.onSubmit(callback: any): this

Parameters:

  • callback any

Returns: this

Calls:

  • this.events.submit.push
Code
onSubmit( callback ) {

        this.events.submit.push( callback );

        return this;

    }

Search.getFilterByButton(button: any): any

Parameters:

  • button any

Returns: any

Code
getFilterByButton( button ) {

        for ( const filter of this.filtered ) {

            if ( filter.button === button ) {

                return filter;

            }

        }

        return null;

    }

Search.add(button: any): this

Parameters:

  • button any

Returns: this

Calls:

  • super.add
  • this.getFilterByButton
  • this.filtered.indexOf
  • button.getValue
  • this.submit
  • button.dom.addEventListener
  • this.domButtons.get( button ).remove
Code
add( button ) {

        super.add( button );

        const onDown = () => {

            const filter = this.getFilterByButton( button );

            this.filteredIndex = this.filtered.indexOf( filter );
            this.value = button.getValue();

            this.submit();

        };

        button.dom.addEventListener( 'mousedown', onDown );
        button.dom.addEventListener( 'touchstart', onDown );

        this.domButtons.get( button ).remove();

        return this;

    }

Search.setTag(button: any, tags: any): void

Parameters:

  • button any
  • tags any

Returns: void

Calls:

  • this.tags.set
Code
setTag( button, tags ) {

        this.tags.set( button, tags );

    }

Search.filter(text: any): void

Parameters:

  • text any

Returns: void

Calls:

  • filterString
  • this.domButtons.get
  • buttonDOM.remove
  • tags.has
  • tags.get
  • button.getValue
  • label.includes
  • filtered.push
  • filtered.sort
Code
filter( text ) {

        text = filterString( text );

        const tags = this.tags;
        const filtered = [];

        for ( const button of this.buttons ) {

            const buttonDOM = this.domButtons.get( button );

            buttonDOM.remove();

            const buttonTags = tags.has( button ) ? ' ' + tags.get( button ) : '';

            const label = filterString( button.getValue() + buttonTags );

            if ( text && label.includes( text ) === true ) {

                const score = text.length / label.length;

                filtered.push( {
                    button,
                    score
                } );

            }

        }

        filtered.sort( ( a, b ) => b.score - a.score );

        this.filtered = filtered;
        this.filteredIndex = this.forceAutoComplete ? 0 : null;

    }

Search.updateFilter(): void

Returns: void

Calls:

  • Math.min
  • this.domButtons.get
  • buttonDOM.remove
  • this.listDOM.append
Code
updateFilter() {

        const filteredIndex = Math.min( this.filteredIndex, this.filteredIndex - 3 );

        for ( let i = 0; i < this.filtered.length; i ++ ) {

            const button = this.filtered[ i ].button;
            const buttonDOM = this.domButtons.get( button );

            buttonDOM.remove();

            if ( i >= filteredIndex ) {

                this.listDOM.append( buttonDOM );

            }

        }

    }

onDown(): void

Returns: void

Calls:

  • this.getFilterByButton
  • this.filtered.indexOf
  • button.getValue
  • this.submit
Code
() => {

            const filter = this.getFilterByButton( button );

            this.filteredIndex = this.filtered.indexOf( filter );
            this.value = button.getValue();

            this.submit();

        }

LabelElement.setIcon(value: any): this

Parameters:

  • value any

Returns: this

Calls:

  • document.createElement
  • this.labelDOM.prepend
  • this.iconDOM.remove
Code
setIcon( value ) {

        this.iconDOM = this.iconDOM || document.createElement( 'i' );
        this.iconDOM.className = value;

        if ( value ) this.labelDOM.prepend( this.iconDOM );
        else this.iconDOM.remove();

        return this;

    }

LabelElement.getIcon(): any

Returns: any

Code
getIcon() {

        return this.iconDOM ? this.iconDOM.className : null;

    }

LabelElement.setAlign(align: any): void

Parameters:

  • align any

Returns: void

Code
setAlign( align ) {

        this.labelDOM.className = align;

    }

LabelElement.setLabel(val: any): void

Parameters:

  • val any

Returns: void

Code
setLabel( val ) {

        this.spanDOM.innerText = val;

    }

LabelElement.getLabel(): string

Returns: string

Code
getLabel() {

        return this.spanDOM.innerText;

    }

LabelElement.serialize(data: any): void

Parameters:

  • data any

Returns: void

Calls:

  • super.serialize
  • this.getLabel
  • this.getIcon
Code
serialize( data ) {

        super.serialize( data );

        if ( this.serializeLabel ) {

            const label = this.getLabel();
            const icon = this.getIcon();

            data.label = label;

            if ( icon !== '' ) {

                data.icon = icon;

            }

        }

    }

LabelElement.deserialize(data: any): void

Parameters:

  • data any

Returns: void

Calls:

  • super.deserialize
  • this.setLabel
  • this.setIcon
Code
deserialize( data ) {

        super.deserialize( data );

        if ( this.serializeLabel ) {

            this.setLabel( data.label );

            if ( data.icon !== undefined ) {

                this.setIcon( data.icon );

            }

        }

    }

ButtonInput.setIcon(className: any): this

Parameters:

  • className any

Returns: this

Code
setIcon( className ) {

        this.iconDOM.className = className;

        return this;

    }

ButtonInput.getIcon(): string

Returns: string

Code
getIcon() {

        return this.iconDOM.className;

    }

ButtonInput.setValue(val: any): this

Parameters:

  • val any

Returns: this

Code
setValue( val ) {

        this.spanDOM.innerText = val;

        return this;

    }

ButtonInput.getValue(): string

Returns: string

Code
getValue() {

        return this.spanDOM.innerText;

    }

ColorInput.setValue(value: any, dispatch: boolean): this

Parameters:

  • value any
  • dispatch boolean

Returns: this

Calls:

  • super.setValue
  • numberToHex
Code
setValue( value, dispatch = true ) {

        return super.setValue( numberToHex( value ), dispatch );

    }

ColorInput.getValue(): number

Returns: number

Calls:

  • parseInt
  • super.getValue().substr
Code
getValue() {

        return parseInt( super.getValue().substr( 1 ), 16 );

    }

NumberInput.setStep(step: any): this

Parameters:

  • step any

Returns: this

Code
setStep( step ) {

        this.step = step;

        return this;

    }

NumberInput.setRange(min: any, max: any, step: any): this

Parameters:

  • min any
  • max any
  • step any

Returns: this

Calls:

  • this.dispatchEvent
  • this.setValue
  • this.getValue
Code
setRange( min, max, step ) {

        this.min = min;
        this.max = max;
        this.step = step;

        this.dispatchEvent( new Event( 'range' ) );

        return this.setValue( this.getValue() );

    }

NumberInput.setInterger(bool: any): this

Parameters:

  • bool any

Returns: this

Calls:

  • this.setValue
  • this.getValue
Code
setInterger( bool ) {

        this.integer = bool;
        this.step = .1;

        return this.setValue( this.getValue() );

    }

NumberInput.setValue(val: any, dispatch: boolean): this

Parameters:

  • val any
  • dispatch boolean

Returns: this

Calls:

  • super.setValue
  • this._getString
Code
setValue( val, dispatch = true ) {

        return super.setValue( this._getString( val ), dispatch );

    }

NumberInput.getValue(): number

Returns: number

Calls:

  • Number
Code
getValue() {

        return Number( this.dom.value );

    }

NumberInput.serialize(data: any): void

Parameters:

  • data any

Returns: void

Calls:

  • super.serialize
Code
serialize( data ) {

        const { min, max } = this;

        if ( min !== - Infinity && max !== Infinity ) {

            data.min = this.min;
            data.max = this.max;
            data.step = this.step;

        }

        super.serialize( data );

    }

NumberInput.deserialize(data: any): void

Parameters:

  • data any

Returns: void

Calls:

  • this.setRange
  • super.deserialize
Code
deserialize( data ) {

        if ( data.min !== undefined ) {

            const { min, max, step } = this;

            this.setRange( min, max, step );

        }

        super.deserialize( data );

    }

NumberInput._getString(value: any): string | number

Parameters:

  • value any

Returns: string | number

Calls:

  • Math.min
  • Math.max
  • Number
  • Math.floor
Code
_getString( value ) {

        const num = Math.min( Math.max( Number( value ), this.min ), this.max );

        if ( this.integer === true ) {

            return Math.floor( num );

        } else {

            return num + ( num % 1 ? '' : '.0' );

        }

    }

SelectInput.setOptions(options: any, value: any): this

Parameters:

  • options any
  • value any

Returns: this

Calls:

  • document.createElement
  • dom.append
Code
setOptions( options, value = null ) {

        const dom = this.dom;
        const defaultValue = dom.value;

        let containsDefaultValue = false;

        this.options = options;
        dom.innerHTML = '';

        for ( let index = 0; index < options.length; index ++ ) {

            let opt = options[ index ];

            if ( typeof opt === 'string' ) {

                opt = { name: opt, value: index };

            }

            const option = document.createElement( 'option' );
            option.innerText = opt.name;
            option.value = opt.value;

            if ( containsDefaultValue === false && defaultValue === opt.value ) {

                containsDefaultValue = true;

            }

            dom.append( option );

        }

        dom.value = value !== null ? value : containsDefaultValue ? defaultValue : '';

        return this;

    }

SelectInput.getOptions(): any

Returns: any

Code
getOptions() {

        return this._options;

    }

SelectInput.serialize(data: any): void

Parameters:

  • data any

Returns: void

Calls:

  • super.serialize
Code
serialize( data ) {

        data.options = [ ...this.options ];

        super.serialize( data );

    }

SelectInput.deserialize(data: any): void

Parameters:

  • data any

Returns: void

Calls:

  • this.setOptions
  • super.deserialize
Code
deserialize( data ) {

        const currentOptions = this.options;

        if ( currentOptions.length === 0 ) {

            this.setOptions( data.options );

        }

        super.deserialize( data );

    }

getStep(min: any, max: any): number

Parameters:

  • min any
  • max any

Returns: number

Code
( min, max ) => {

    const sensibility = .001;

    return ( max - min ) * sensibility;

}

SliderInput.setRange(min: any, max: any): this

Parameters:

  • min any
  • max any

Returns: this

Calls:

  • this.field.setRange
  • getStep
  • this.dispatchEvent
Code
setRange( min, max ) {

        this.field.setRange( min, max, getStep( min, max ) );

        this.dispatchEvent( new Event( 'range' ) );
        this.dispatchEvent( new Event( 'change' ) );

        return this;

    }

SliderInput.setValue(val: any, dispatch: boolean): this

Parameters:

  • val any
  • dispatch boolean

Returns: this

Calls:

  • this.field.setValue
  • this.dispatchEvent
Code
setValue( val, dispatch = true ) {

        this.field.setValue( val );
        this.rangeDOM.value = val;

        if ( dispatch ) this.dispatchEvent( new Event( 'change' ) );

        return this;

    }

SliderInput.getValue(): number

Returns: number

Calls:

  • this.field.getValue
Code
getValue() {

        return this.field.getValue();

    }

SliderInput.serialize(data: any): void

Parameters:

  • data any

Returns: void

Calls:

  • super.serialize
Code
serialize( data ) {

        data.min = this.min;
        data.max = this.max;

        super.serialize( data );

    }

SliderInput.deserialize(data: any): void

Parameters:

  • data any

Returns: void

Calls:

  • this.setRange
  • super.deserialize
Code
deserialize( data ) {

        const { min, max } = data;

        this.setRange( min, max );

        super.deserialize( data );

    }

updateRangeValue(): void

Returns: void

Calls:

  • Number
  • this.field.setValue

Internal Comments:

// fix not end range fraction (x4)

Code
() => {

            let value = Number( rangeDOM.value );

            if ( value !== this.max && value + this.step >= this.max ) {

                // fix not end range fraction

                rangeDOM.value = value = this.max;

            }

            this.field.setValue( value );

        }

StringInput.setPlaceHolder(text: any): this

Parameters:

  • text any

Returns: this

Code
setPlaceHolder( text ) {

        this.inputDOM.placeholder = text;

        return this;

    }

StringInput.setIcon(value: any): this

Parameters:

  • value any

Returns: this

Calls:

  • document.createElement
  • this.iconDOM.setAttribute
  • this.dom.prepend
  • this.iconDOM.remove
Code
setIcon( value ) {

        this.iconDOM = this.iconDOM || document.createElement( 'i' );
        this.iconDOM.setAttribute( 'type', 'icon' );
        this.iconDOM.className = value;

        if ( value ) this.dom.prepend( this.iconDOM );
        else this.iconDOM.remove();

        return this;

    }

StringInput.getIcon(): any

Returns: any

Calls:

  • this.iconInput.getIcon
Code
getIcon() {

        return this.iconInput ? this.iconInput.getIcon() : '';

    }

StringInput.addButton(button: any): this

Parameters:

  • button any

Returns: this

Calls:

  • this.buttonsDOM.prepend
  • this.buttons.push
Code
addButton( button ) {

        this.buttonsDOM.prepend( button.iconDOM );

        this.buttons.push( button );

        return this;

    }

StringInput.addOption(value: any): this

Parameters:

  • value any

Returns: this

Calls:

  • document.createElement
  • this.datalistDOM.append
Code
addOption( value ) {

        const option = document.createElement( 'option' );
        option.value = value;

        this.datalistDOM.append( option );

        return this;

    }

StringInput.clearOptions(): void

Returns: void

Calls:

  • this.datalistDOM.remove
Code
clearOptions() {

        this.datalistDOM.remove();

    }

StringInput.getInput(): HTMLInputElement

Returns: HTMLInputElement

Code
getInput() {

        return this.inputDOM;

    }

ToggleInput.setValue(val: any): this

Parameters:

  • val any

Returns: this

Calls:

  • this.dispatchEvent
Code
setValue( val ) {

        this.dom.checked = val;

        this.dispatchEvent( new Event( 'change' ) );

        return this;

    }

ToggleInput.getValue(): any

Returns: any

Code
getValue() {

        return this.dom.checked;

    }

TreeViewNode.setLabel(value: any): this

Parameters:

  • value any

Returns: this

Code
setLabel( value ) {

        this.labelSpam.innerText = value;

        return this;

    }

TreeViewNode.getLabel(): string

Returns: string

Code
getLabel() {

        return this.labelSpam.innerText;

    }

TreeViewNode.add(node: any): this

Parameters:

  • node any

Returns: this

Calls:

  • document.createElement
  • dom.append
  • this.children.push
  • childrenDOM.append
Code
add( node ) {

        let childrenDOM = this.childrenDOM;

        if ( this.childrenDOM === null ) {

            const dom = this.dom;

            const arrowDOM = document.createElement( 'f-arrow' );
            childrenDOM = document.createElement( 'f-treeview-children' );

            dom.append( arrowDOM );
            dom.append( childrenDOM );

            this.childrenDOM = childrenDOM;

        }

        this.children.push( node );
        childrenDOM.append( node.dom );

        node.parent = this;

        return this;

    }

TreeViewNode.setOpened(value: any): this

Parameters:

  • value any

Returns: this

Code
setOpened( value ) {

        this.inputDOM.checked = value;

        return this;

    }

TreeViewNode.getOpened(): any

Returns: any

Code
getOpened() {

        return this.inputDOM.checkbox;

    }

TreeViewNode.setIcon(value: any): this

Parameters:

  • value any

Returns: this

Calls:

  • document.createElement
  • this.labelDOM.prepend
  • this.iconDOM.remove
Code
setIcon( value ) {

        this.iconDOM = this.iconDOM || document.createElement( 'i' );
        this.iconDOM.className = value;

        if ( value ) this.labelDOM.prepend( this.iconDOM );
        else this.iconDOM.remove();

        return this;

    }

TreeViewNode.getIcon(): any

Returns: any

Code
getIcon() {

        return this.iconDOM ? this.iconDOM.className : null;

    }

TreeViewNode.setVisible(value: any): this

Parameters:

  • value any

Returns: this

Code
setVisible( value ) {

        this.dom.style.display = value ? '' : 'none';

        return this;

    }

TreeViewNode.setSelected(value: any): this

Parameters:

  • value any

Returns: this

Calls:

  • this.dom.classList.add
  • this.dom.classList.remove
Code
setSelected( value ) {

        if ( this.selected === value ) return this;

        if ( value ) this.dom.classList.add( 'selected' );
        else this.dom.classList.remove( 'selected' );

        this.selected = value;

        return this;

    }

TreeViewNode.onClick(callback: any): this

Parameters:

  • callback any

Returns: this

Calls:

  • this.events.click.push
Code
onClick( callback ) {

        this.events.click.push( callback );

        return this;

    }

TreeViewInput.add(node: any): this

Parameters:

  • node any

Returns: this

Calls:

  • this.children.push
  • this.childrenDOM.append
Code
add( node ) {

        this.children.push( node );
        this.childrenDOM.append( node.dom );

        return this;

    }

TreeViewInput.serialize(data: any): void

Parameters:

  • data any

Returns: void

Calls:

  • super.serialize

Internal Comments:

//data.options = [ ...this.options ]; (x4)

Code
serialize( data ) {

        //data.options = [ ...this.options ];

        super.serialize( data );

    }

TreeViewInput.deserialize(data: any): void

Parameters:

  • data any

Returns: void

Calls:

  • super.deserialize

Internal Comments:

/*const currentOptions = this.options;

        if ( currentOptions.length === 0 ) {

            this.setOptions( data.options );

        }*/ (x4)

Code
deserialize( data ) {

        /*const currentOptions = this.options;

        if ( currentOptions.length === 0 ) {

            this.setOptions( data.options );

        }*/

        super.deserialize( data );

    }

Loader.setParseType(type: any): this

Parameters:

  • type any

Returns: this

Code
setParseType( type ) {

        this.parseType = type;

        return this;

    }

Loader.getParseType(): string

Returns: string

Code
getParseType() {

        return this.parseType;

    }

Loader.onLoad(callback: any): this

Parameters:

  • callback any

Returns: this

Calls:

  • this.events.load.push
Code
onLoad( callback ) {

        this.events.load.push( callback );

        return this;

    }

Loader.load(url: any, lib: {}): Promise<any>

Parameters:

  • url any
  • lib {}

Returns: Promise<any>

Calls:

  • `fetch( url ) .then( response => response.json() ) .then( result => {
            this.data = this.parse( result, lib );
    
            dispatchEventList( this.events.load, this );
    
            return this.data;
    
        } )
        .catch`
    
    • console.error
Code
async load( url, lib = {} ) {

        return await fetch( url )
            .then( response => response.json() )
            .then( result => {

                this.data = this.parse( result, lib );

                dispatchEventList( this.events.load, this );

                return this.data;

            } )
            .catch( err => {

                console.error( 'Loader:', err );

            } );

    }

Loader.parse(json: any, lib: {}): any

Parameters:

  • json any
  • lib {}

Returns: any

Calls:

  • this._parseObjects
  • flowObj.getSerializable
  • flowObj.deserialize
Code
parse( json, lib = {} ) {

        json = this._parseObjects( json, lib );

        const parseType = this.parseType;

        if ( parseType === Loader.DEFAULT ) {

            const type = json.type;

            const flowClass = lib[ type ] ? lib[ type ] : ( LoaderLib[ type ] || Flow[ type ] );
            const flowObj = new flowClass();

            if ( flowObj.getSerializable() ) {

                flowObj.deserialize( json );

            }

            return flowObj;

        } else if ( parseType === Loader.OBJECTS ) {

            return json;

        }

    }

Loader._parseObjects(json: any, lib: {}): any

Parameters:

  • json any
  • lib {}

Returns: any

Calls:

  • console.error
  • objects[ id ].deserializeLib
  • ref.has
  • ref.set
  • newObject.getSerializable
  • newObject.deserialize
  • deserializePass
Code
_parseObjects( json, lib = {} ) {

        json = { ...json };

        const objects = {};

        for ( const id in json.objects ) {

            const obj = json.objects[ id ];
            obj.objects = objects;

            const type = obj.type;
            const flowClass = lib[ type ] ? lib[ type ] : ( LoaderLib[ type ] || Flow[ type ] );

            if ( ! flowClass ) {

                console.error( `Class "${ type }" not found!` );

            }

            objects[ id ] = new flowClass();
            objects[ id ].deserializeLib( json.objects[ id ], lib );

        }

        const ref = new Map();

        const deserializePass = ( prop = null ) => {

            for ( const id in json.objects ) {

                const newObject = objects[ id ];

                if ( ref.has( newObject ) === false && ( prop === null || newObject[ prop ] === true ) ) {

                    ref.set( newObject, true );

                    if ( newObject.getSerializable() ) {

                        newObject.deserialize( json.objects[ id ] );

                    }

                }

            }

        };

        deserializePass( 'isNode' );
        deserializePass( 'isElement' );
        deserializePass( 'isInput' );
        deserializePass();

        json.objects = objects;

        return json;

    }

deserializePass(prop: any): void

Parameters:

  • prop any

Returns: void

Calls:

  • ref.has
  • ref.set
  • newObject.getSerializable
  • newObject.deserialize
Code
( prop = null ) => {

            for ( const id in json.objects ) {

                const newObject = objects[ id ];

                if ( ref.has( newObject ) === false && ( prop === null || newObject[ prop ] === true ) ) {

                    ref.set( newObject, true );

                    if ( newObject.getSerializable() ) {

                        newObject.deserialize( json.objects[ id ] );

                    }

                }

            }

        }

Classes

Serializer

Class Code
class Serializer extends EventTarget {

    static get type() {

        return 'Serializer';

    }

    constructor() {

        super();

        this._id = _id ++;

        this._serializable = true;

    }

    get id() {

        return this._id;

    }

    setSerializable( value ) {

        this._serializable = value;

        return this;

    }

    getSerializable() {

        return this._serializable;

    }

    serialize( /*data*/ ) {

        console.warn( 'Serializer: Abstract function.' );

    }

    deserialize( /*data*/ ) {

        console.warn( 'Serializer: Abstract function.' );

    }

    deserializeLib( /*data, lib*/ ) {

        // Abstract function.

    }

    get className() {

        return this.constructor.type || this.constructor.name;

    }

    toJSON( data = null ) {

        let object = null;

        const id = this.id;

        if ( data !== null ) {

            const objects = data.objects;

            object = objects[ id ];

            if ( object === undefined ) {

                object = { objects };

                this.serialize( object );

                delete object.objects;

                objects[ id ] = object;

            }

        } else {

            object = { objects: {} };

            this.serialize( object );

        }

        object.id = id;
        object.type = this.className;

        return object;

    }

}

Methods

setSerializable(value: any): this
Code
setSerializable( value ) {

        this._serializable = value;

        return this;

    }
getSerializable(): boolean
Code
getSerializable() {

        return this._serializable;

    }
serialize(): void
Code
serialize( /*data*/ ) {

        console.warn( 'Serializer: Abstract function.' );

    }
deserialize(): void
Code
deserialize( /*data*/ ) {

        console.warn( 'Serializer: Abstract function.' );

    }
deserializeLib(): void
Code
deserializeLib( /*data, lib*/ ) {

        // Abstract function.

    }
toJSON(data: any): any
Code
toJSON( data = null ) {

        let object = null;

        const id = this.id;

        if ( data !== null ) {

            const objects = data.objects;

            object = objects[ id ];

            if ( object === undefined ) {

                object = { objects };

                this.serialize( object );

                delete object.objects;

                objects[ id ] = object;

            }

        } else {

            object = { objects: {} };

            this.serialize( object );

        }

        object.id = id;
        object.type = this.className;

        return object;

    }

PointerMonitor

Class Code
class PointerMonitor {

    constructor() {

        this.x = 0;
        this.y = 0;
        this.started = false;

        this._onMoveEvent = ( e ) => {

            const event = e.touches ? e.touches[ 0 ] : e;

            this.x = event.clientX;
            this.y = event.clientY;

        };

    }

    start() {

        if ( this.started ) return;

        this.started = true;

        window.addEventListener( 'wheel', this._onMoveEvent, true );

        window.addEventListener( 'mousedown', this._onMoveEvent, true );
        window.addEventListener( 'touchstart', this._onMoveEvent, true );

        window.addEventListener( 'mousemove', this._onMoveEvent, true );
        window.addEventListener( 'touchmove', this._onMoveEvent, true );

        window.addEventListener( 'dragover', this._onMoveEvent, true );

        return this;

    }

}

Methods

start(): this
Code
start() {

        if ( this.started ) return;

        this.started = true;

        window.addEventListener( 'wheel', this._onMoveEvent, true );

        window.addEventListener( 'mousedown', this._onMoveEvent, true );
        window.addEventListener( 'touchstart', this._onMoveEvent, true );

        window.addEventListener( 'mousemove', this._onMoveEvent, true );
        window.addEventListener( 'touchmove', this._onMoveEvent, true );

        window.addEventListener( 'dragover', this._onMoveEvent, true );

        return this;

    }
Class Code
class Link {

    constructor( inputElement = null, outputElement = null ) {

        this.inputElement = inputElement;
        this.outputElement = outputElement;

    }

    get lioElement() {

        if ( Link.InputDirection === 'left' ) {

            return this.outputElement;

        } else {

            return this.inputElement;

        }

    }

    get rioElement() {

        if ( Link.InputDirection === 'left' ) {

            return this.inputElement;

        } else {

            return this.outputElement;

        }

    }

}

Element

Class Code
class Element extends Serializer {

    static get type() {

        return 'Element';

    }

    constructor( draggable = false ) {

        super();

        this.isElement = true;

        const dom = document.createElement( 'f-element' );
        dom.element = this;

        const onSelect = ( e ) => {

            let element = this;

            if ( e.changedTouches && e.changedTouches.length > 0 ) {

                const touch = e.changedTouches[ 0 ];

                let overDOM = document.elementFromPoint( touch.clientX, touch.clientY );

                while ( overDOM && ( ! overDOM.element || ! overDOM.element.isElement ) ) {

                    overDOM = overDOM.parentNode;

                }

                element = overDOM ? overDOM.element : null;

            }

            const type = e.type;

            if ( ( type === 'mouseout' ) && selected === element ) {

                selected = null;

            } else {

                selected = element;

            }

        };

        if ( draggable === false ) {

            dom.ontouchstart = dom.onmousedown = ( e ) => {

                e.stopPropagation();

            };

        }

        dom.addEventListener( 'mouseup', onSelect, true );
        dom.addEventListener( 'mouseover', onSelect );
        dom.addEventListener( 'mouseout', onSelect );
        dom.addEventListener( 'touchmove', onSelect );
        dom.addEventListener( 'touchend', onSelect );

        this.inputs = [];

        this.links = [];

        this.dom = dom;

        this.lioLength = 0;
        this.rioLength = 0;

        this.events = {
            'connect': [],
            'connectChildren': [],
            'valid': []
        };

        this.node = null;

        this.style = '';
        this.color = null;

        this.object = null;
        this.objectCallback = null;

        this.enabledInputs = true;

        this.visible = true;

        this.inputsDOM = dom;

        this.disconnectDOM = null;

        this.lioDOM = this._createIO( 'lio' );
        this.rioDOM = this._createIO( 'rio' );

        this.dom.classList.add( `input-${ Link.InputDirection }` );

        this.addEventListener( 'connect', ( ) => {

            dispatchEventList( this.events.connect, this );

        } );

        this.addEventListener( 'connectChildren', ( ) => {

            dispatchEventList( this.events.connectChildren, this );

        } );

    }

    setAttribute( name, value ) {

        this.dom.setAttribute( name, value );

        return this;

    }

    onValid( callback ) {

        this.events.valid.push( callback );

        return this;

    }

    onConnect( callback, childrens = false ) {

        this.events.connect.push( callback );

        if ( childrens ) {

            this.events.connectChildren.push( callback );

        }

        return this;

    }

    setObjectCallback( callback ) {

        this.objectCallback = callback;

        return this;

    }

    setObject( value ) {

        this.object = value;

        return this;

    }

    getObject( output = null ) {

        return this.objectCallback ? this.objectCallback( output ) : this.object;

    }

    setVisible( value ) {

        this.visible = value;

        this.dom.style.display = value ? '' : 'none';

        return this;

    }

    getVisible() {

        return this.visible;

    }

    setEnabledInputs( value ) {

        const dom = this.dom;

        if ( ! this.enabledInputs ) dom.classList.remove( 'inputs-disable' );

        if ( ! value ) dom.classList.add( 'inputs-disable' );

        this.enabledInputs = value;

        return this;

    }

    getEnabledInputs() {

        return this.enabledInputs;

    }

    setColor( color ) {

        this.dom.style[ 'background-color' ] = numberToHex( color );
        this.color = null;

        return this;

    }

    getColor() {

        if ( this.color === null ) {

            const css = window.getComputedStyle( this.dom );

            this.color = css.getPropertyValue( 'background-color' );

        }

        return this.color;

    }

    setStyle( style ) {

        const dom = this.dom;

        if ( this.style ) dom.classList.remove( this.style );

        if ( style ) dom.classList.add( style );

        this.style = style;
        this.color = null;

        return this;

    }

    setInput( length ) {

        if ( Link.InputDirection === 'left' ) {

            return this.setLIO( length );

        } else {

            return this.setRIO( length );

        }

    }

    setInputColor( color ) {

        if ( Link.InputDirection === 'left' ) {

            return this.setLIOColor( color );

        } else {

            return this.setRIOColor( color );

        }

    }

    setOutput( length ) {

        if ( Link.InputDirection === 'left' ) {

            return this.setRIO( length );

        } else {

            return this.setLIO( length );

        }

    }

    setOutputColor( color ) {

        if ( Link.InputDirection === 'left' ) {

            return this.setRIOColor( color );

        } else {

            return this.setLIOColor( color );

        }

    }

    get inputLength() {

        if ( Link.InputDirection === 'left' ) {

            return this.lioLength;

        } else {

            return this.rioLength;

        }

    }

    get outputLength() {

        if ( Link.InputDirection === 'left' ) {

            return this.rioLength;

        } else {

            return this.lioLength;

        }

    }

    setLIOColor( color ) {

        this.lioDOM.style[ 'border-color' ] = numberToHex( color );

        return this;

    }

    setLIO( length ) {

        this.lioLength = length;

        this.lioDOM.style.visibility = length > 0 ? '' : 'hidden';

        if ( length > 0 ) {

            this.dom.classList.add( 'lio' );
            this.dom.prepend( this.lioDOM );

        } else {

            this.dom.classList.remove( 'lio' );
            this.lioDOM.remove();

        }

        return this;

    }

    getLIOColor() {

        return this.lioDOM.style[ 'border-color' ];

    }

    setRIOColor( color ) {

        this.rioDOM.style[ 'border-color' ] = numberToHex( color );

        return this;

    }

    getRIOColor() {

        return this.rioDOM.style[ 'border-color' ];

    }

    setRIO( length ) {

        this.rioLength = length;

        this.rioDOM.style.visibility = length > 0 ? '' : 'hidden';

        if ( length > 0 ) {

            this.dom.classList.add( 'rio' );
            this.dom.prepend( this.rioDOM );

        } else {

            this.dom.classList.remove( 'rio' );
            this.rioDOM.remove();

        }

        return this;

    }

    add( input ) {

        this.inputs.push( input );

        input.element = this;

        this.inputsDOM.append( input.dom );

        return this;

    }

    setHeight( val ) {

        this.dom.style.height = numberToPX( val );

        return this;

    }

    getHeight() {

        return parseInt( this.dom.style.height );

    }

    connect( element = null ) {

        if ( this.disconnectDOM !== null ) {

            // remove the current input

            this.disconnectDOM.dispatchEvent( new Event( 'disconnect' ) );

        }

        if ( element !== null ) {

            element = element.baseElement || element;

            if ( dispatchEventList( this.events.valid, this, element, 'connect' ) === false ) {

                return false;

            }

            const link = new Link( this, element );

            this.links.push( link );

            if ( this.disconnectDOM === null ) {

                this.disconnectDOM = document.createElement( 'f-disconnect' );
                this.disconnectDOM.innerHTML = Element.icons.unlink ? `<i class='${ Element.icons.unlink }'></i>` : '✖';

                this.dom.append( this.disconnectDOM );

                const onDisconnect = () => {

                    this.links = [];
                    this.dom.removeChild( this.disconnectDOM );

                    this.disconnectDOM.removeEventListener( 'mousedown', onClick, true );
                    this.disconnectDOM.removeEventListener( 'touchstart', onClick, true );
                    this.disconnectDOM.removeEventListener( 'disconnect', onDisconnect, true );

                    element.removeEventListener( 'connect', onConnect );
                    element.removeEventListener( 'connectChildren', onConnect );
                    element.removeEventListener( 'nodeConnect', onConnect );
                    element.removeEventListener( 'nodeConnectChildren', onConnect );
                    element.removeEventListener( 'dispose', onDispose );

                    this.disconnectDOM = null;

                };

                const onConnect = () => {

                    this.dispatchEvent( new Event( 'connectChildren' ) );

                };

                const onDispose = () => {

                    this.connect();

                };

                const onClick = ( e ) => {

                    e.stopPropagation();

                    this.connect();

                };

                this.disconnectDOM.addEventListener( 'mousedown', onClick, true );
                this.disconnectDOM.addEventListener( 'touchstart', onClick, true );
                this.disconnectDOM.addEventListener( 'disconnect', onDisconnect, true );

                element.addEventListener( 'connect', onConnect );
                element.addEventListener( 'connectChildren', onConnect );
                element.addEventListener( 'nodeConnect', onConnect );
                element.addEventListener( 'nodeConnectChildren', onConnect );
                element.addEventListener( 'dispose', onDispose );

            }

        }

        this.dispatchEvent( new Event( 'connect' ) );

        return true;

    }

    dispose() {

        this.dispatchEvent( new Event( 'dispose' ) );

    }

    getInputByProperty( property ) {

        for ( const input of this.inputs ) {

            if ( input.getProperty() === property ) {

                return input;

            }

        }

    }

    serialize( data ) {

        const inputs = [];
        const properties = [];
        const links = [];

        for ( const input of this.inputs ) {

            const id = input.toJSON( data ).id;
            const property = input.getProperty();

            inputs.push( id );

            if ( property !== null ) {

                properties.push( { property, id } );

            }

        }

        for ( const link of this.links ) {

            if ( link.inputElement !== null && link.outputElement !== null ) {

                links.push( link.outputElement.toJSON( data ).id );

            }

        }

        if ( this.inputLength > 0 ) data.inputLength = this.inputLength;
        if ( this.outputLength > 0 ) data.outputLength = this.outputLength;

        if ( inputs.length > 0 ) data.inputs = inputs;
        if ( properties.length > 0 ) data.properties = properties;
        if ( links.length > 0 ) data.links = links;

        if ( this.style !== '' ) {

            data.style = this.style;

        }

        data.height = this.getHeight();

    }

    deserialize( data ) {

        if ( data.inputLength !== undefined ) this.setInput( data.inputLength );
        if ( data.outputLength !== undefined ) this.setOutput( data.outputLength );

        if ( data.properties !== undefined ) {

            for ( const { id, property } of data.properties ) {

                data.objects[ id ] = this.getInputByProperty( property );

            }

        } else if ( data.inputs !== undefined ) {

            const inputs = this.inputs;

            if ( inputs.length > 0 ) {

                let index = 0;

                for ( const id of data.inputs ) {

                    data.objects[ id ] = inputs[ index ++ ];

                }

            } else {

                for ( const id of data.inputs ) {

                    this.add( data.objects[ id ] );

                }

            }

        }

        if ( data.links !== undefined ) {

            for ( const id of data.links ) {

                this.connect( data.objects[ id ] );

            }

        }

        if ( data.style !== undefined ) {

            this.setStyle( data.style );

        }

        if ( data.height !== undefined ) {

            this.setHeight( data.height );

        }

    }

    getLinkedObject( output = null ) {

        const linkedElement = this.getLinkedElement();

        return linkedElement ? linkedElement.getObject( output ) : null;

    }

    getLinkedElement() {

        const link = this.getLink();

        return link ? link.outputElement : null;

    }

    getLink() {

        return this.links[ 0 ];

    }

    _createIO( type ) {

        const { dom } = this;

        const ioDOM = document.createElement( 'f-io' );
        ioDOM.style.visibility = 'hidden';
        ioDOM.className = type;

        const onConnectEvent = ( e ) => {

            e.preventDefault();

            e.stopPropagation();

            selected = null;

            const nodeDOM = this.node.dom;

            nodeDOM.classList.add( 'io-connect' );

            ioDOM.classList.add( 'connect' );
            dom.classList.add( 'select' );

            const defaultOutput = Link.InputDirection === 'left' ? 'lio' : 'rio';

            const link = type === defaultOutput ? new Link( this ) : new Link( null, this );
            const previewLink = new Link( link.inputElement, link.outputElement );

            this.links.push( link );

            draggableDOM( e, ( data ) => {

                if ( previewLink.outputElement )
                    previewLink.outputElement.dom.classList.remove( 'invalid' );

                if ( previewLink.inputElement )
                    previewLink.inputElement.dom.classList.remove( 'invalid' );

                previewLink.inputElement = link.inputElement;
                previewLink.outputElement = link.outputElement;

                if ( type === defaultOutput ) {

                    previewLink.outputElement = selected;

                } else {

                    previewLink.inputElement = selected;

                }

                const isInvalid = previewLink.inputElement !== null && previewLink.outputElement !== null &&
                    previewLink.inputElement.inputLength > 0 && previewLink.outputElement.outputLength > 0 &&
                    dispatchEventList( previewLink.inputElement.events.valid, previewLink.inputElement, previewLink.outputElement, data.dragging ? 'dragging' : 'dragged' ) === false;

                if ( data.dragging && isInvalid ) {

                    if ( type === defaultOutput ) {

                        if ( previewLink.outputElement )
                            previewLink.outputElement.dom.classList.add( 'invalid' );

                    } else {

                        if ( previewLink.inputElement )
                            previewLink.inputElement.dom.classList.add( 'invalid' );

                    }

                    return;

                }

                if ( ! data.dragging ) {

                    nodeDOM.classList.remove( 'io-connect' );

                    ioDOM.classList.remove( 'connect' );
                    dom.classList.remove( 'select' );

                    this.links.splice( this.links.indexOf( link ), 1 );

                    if ( selected !== null && ! isInvalid ) {

                        link.inputElement = previewLink.inputElement;
                        link.outputElement = previewLink.outputElement;

                        // check if is an is circular link

                        if ( link.outputElement.node.isCircular( link.inputElement.node ) ) {

                            return;

                        }

                        //

                        if ( link.inputElement.inputLength > 0 && link.outputElement.outputLength > 0 ) {

                            link.inputElement.connect( link.outputElement );

                        }

                    }

                }

            }, { className: 'connecting' } );

        };

        ioDOM.addEventListener( 'mousedown', onConnectEvent, true );
        ioDOM.addEventListener( 'touchstart', onConnectEvent, true );

        return ioDOM;

    }

}

Methods

setAttribute(name: any, value: any): this
Code
setAttribute( name, value ) {

        this.dom.setAttribute( name, value );

        return this;

    }
onValid(callback: any): this
Code
onValid( callback ) {

        this.events.valid.push( callback );

        return this;

    }
onConnect(callback: any, childrens: boolean): this
Code
onConnect( callback, childrens = false ) {

        this.events.connect.push( callback );

        if ( childrens ) {

            this.events.connectChildren.push( callback );

        }

        return this;

    }
setObjectCallback(callback: any): this
Code
setObjectCallback( callback ) {

        this.objectCallback = callback;

        return this;

    }
setObject(value: any): this
Code
setObject( value ) {

        this.object = value;

        return this;

    }
getObject(output: any): any
Code
getObject( output = null ) {

        return this.objectCallback ? this.objectCallback( output ) : this.object;

    }
setVisible(value: any): this
Code
setVisible( value ) {

        this.visible = value;

        this.dom.style.display = value ? '' : 'none';

        return this;

    }
getVisible(): boolean
Code
getVisible() {

        return this.visible;

    }
setEnabledInputs(value: any): this
Code
setEnabledInputs( value ) {

        const dom = this.dom;

        if ( ! this.enabledInputs ) dom.classList.remove( 'inputs-disable' );

        if ( ! value ) dom.classList.add( 'inputs-disable' );

        this.enabledInputs = value;

        return this;

    }
getEnabledInputs(): boolean
Code
getEnabledInputs() {

        return this.enabledInputs;

    }
setColor(color: any): this
Code
setColor( color ) {

        this.dom.style[ 'background-color' ] = numberToHex( color );
        this.color = null;

        return this;

    }
getColor(): string
Code
getColor() {

        if ( this.color === null ) {

            const css = window.getComputedStyle( this.dom );

            this.color = css.getPropertyValue( 'background-color' );

        }

        return this.color;

    }
setStyle(style: any): this
Code
setStyle( style ) {

        const dom = this.dom;

        if ( this.style ) dom.classList.remove( this.style );

        if ( style ) dom.classList.add( style );

        this.style = style;
        this.color = null;

        return this;

    }
setInput(length: any): this
Code
setInput( length ) {

        if ( Link.InputDirection === 'left' ) {

            return this.setLIO( length );

        } else {

            return this.setRIO( length );

        }

    }
setInputColor(color: any): this
Code
setInputColor( color ) {

        if ( Link.InputDirection === 'left' ) {

            return this.setLIOColor( color );

        } else {

            return this.setRIOColor( color );

        }

    }
setOutput(length: any): this
Code
setOutput( length ) {

        if ( Link.InputDirection === 'left' ) {

            return this.setRIO( length );

        } else {

            return this.setLIO( length );

        }

    }
setOutputColor(color: any): this
Code
setOutputColor( color ) {

        if ( Link.InputDirection === 'left' ) {

            return this.setRIOColor( color );

        } else {

            return this.setLIOColor( color );

        }

    }
setLIOColor(color: any): this
Code
setLIOColor( color ) {

        this.lioDOM.style[ 'border-color' ] = numberToHex( color );

        return this;

    }
setLIO(length: any): this
Code
setLIO( length ) {

        this.lioLength = length;

        this.lioDOM.style.visibility = length > 0 ? '' : 'hidden';

        if ( length > 0 ) {

            this.dom.classList.add( 'lio' );
            this.dom.prepend( this.lioDOM );

        } else {

            this.dom.classList.remove( 'lio' );
            this.lioDOM.remove();

        }

        return this;

    }
getLIOColor(): any
Code
getLIOColor() {

        return this.lioDOM.style[ 'border-color' ];

    }
setRIOColor(color: any): this
Code
setRIOColor( color ) {

        this.rioDOM.style[ 'border-color' ] = numberToHex( color );

        return this;

    }
getRIOColor(): any
Code
getRIOColor() {

        return this.rioDOM.style[ 'border-color' ];

    }
setRIO(length: any): this
Code
setRIO( length ) {

        this.rioLength = length;

        this.rioDOM.style.visibility = length > 0 ? '' : 'hidden';

        if ( length > 0 ) {

            this.dom.classList.add( 'rio' );
            this.dom.prepend( this.rioDOM );

        } else {

            this.dom.classList.remove( 'rio' );
            this.rioDOM.remove();

        }

        return this;

    }
add(input: any): this
Code
add( input ) {

        this.inputs.push( input );

        input.element = this;

        this.inputsDOM.append( input.dom );

        return this;

    }
setHeight(val: any): this
Code
setHeight( val ) {

        this.dom.style.height = numberToPX( val );

        return this;

    }
getHeight(): number
Code
getHeight() {

        return parseInt( this.dom.style.height );

    }
connect(element: any): boolean
Code
connect( element = null ) {

        if ( this.disconnectDOM !== null ) {

            // remove the current input

            this.disconnectDOM.dispatchEvent( new Event( 'disconnect' ) );

        }

        if ( element !== null ) {

            element = element.baseElement || element;

            if ( dispatchEventList( this.events.valid, this, element, 'connect' ) === false ) {

                return false;

            }

            const link = new Link( this, element );

            this.links.push( link );

            if ( this.disconnectDOM === null ) {

                this.disconnectDOM = document.createElement( 'f-disconnect' );
                this.disconnectDOM.innerHTML = Element.icons.unlink ? `<i class='${ Element.icons.unlink }'></i>` : '✖';

                this.dom.append( this.disconnectDOM );

                const onDisconnect = () => {

                    this.links = [];
                    this.dom.removeChild( this.disconnectDOM );

                    this.disconnectDOM.removeEventListener( 'mousedown', onClick, true );
                    this.disconnectDOM.removeEventListener( 'touchstart', onClick, true );
                    this.disconnectDOM.removeEventListener( 'disconnect', onDisconnect, true );

                    element.removeEventListener( 'connect', onConnect );
                    element.removeEventListener( 'connectChildren', onConnect );
                    element.removeEventListener( 'nodeConnect', onConnect );
                    element.removeEventListener( 'nodeConnectChildren', onConnect );
                    element.removeEventListener( 'dispose', onDispose );

                    this.disconnectDOM = null;

                };

                const onConnect = () => {

                    this.dispatchEvent( new Event( 'connectChildren' ) );

                };

                const onDispose = () => {

                    this.connect();

                };

                const onClick = ( e ) => {

                    e.stopPropagation();

                    this.connect();

                };

                this.disconnectDOM.addEventListener( 'mousedown', onClick, true );
                this.disconnectDOM.addEventListener( 'touchstart', onClick, true );
                this.disconnectDOM.addEventListener( 'disconnect', onDisconnect, true );

                element.addEventListener( 'connect', onConnect );
                element.addEventListener( 'connectChildren', onConnect );
                element.addEventListener( 'nodeConnect', onConnect );
                element.addEventListener( 'nodeConnectChildren', onConnect );
                element.addEventListener( 'dispose', onDispose );

            }

        }

        this.dispatchEvent( new Event( 'connect' ) );

        return true;

    }
dispose(): void
Code
dispose() {

        this.dispatchEvent( new Event( 'dispose' ) );

    }
getInputByProperty(property: any): any
Code
getInputByProperty( property ) {

        for ( const input of this.inputs ) {

            if ( input.getProperty() === property ) {

                return input;

            }

        }

    }
serialize(data: any): void
Code
serialize( data ) {

        const inputs = [];
        const properties = [];
        const links = [];

        for ( const input of this.inputs ) {

            const id = input.toJSON( data ).id;
            const property = input.getProperty();

            inputs.push( id );

            if ( property !== null ) {

                properties.push( { property, id } );

            }

        }

        for ( const link of this.links ) {

            if ( link.inputElement !== null && link.outputElement !== null ) {

                links.push( link.outputElement.toJSON( data ).id );

            }

        }

        if ( this.inputLength > 0 ) data.inputLength = this.inputLength;
        if ( this.outputLength > 0 ) data.outputLength = this.outputLength;

        if ( inputs.length > 0 ) data.inputs = inputs;
        if ( properties.length > 0 ) data.properties = properties;
        if ( links.length > 0 ) data.links = links;

        if ( this.style !== '' ) {

            data.style = this.style;

        }

        data.height = this.getHeight();

    }
deserialize(data: any): void
Code
deserialize( data ) {

        if ( data.inputLength !== undefined ) this.setInput( data.inputLength );
        if ( data.outputLength !== undefined ) this.setOutput( data.outputLength );

        if ( data.properties !== undefined ) {

            for ( const { id, property } of data.properties ) {

                data.objects[ id ] = this.getInputByProperty( property );

            }

        } else if ( data.inputs !== undefined ) {

            const inputs = this.inputs;

            if ( inputs.length > 0 ) {

                let index = 0;

                for ( const id of data.inputs ) {

                    data.objects[ id ] = inputs[ index ++ ];

                }

            } else {

                for ( const id of data.inputs ) {

                    this.add( data.objects[ id ] );

                }

            }

        }

        if ( data.links !== undefined ) {

            for ( const id of data.links ) {

                this.connect( data.objects[ id ] );

            }

        }

        if ( data.style !== undefined ) {

            this.setStyle( data.style );

        }

        if ( data.height !== undefined ) {

            this.setHeight( data.height );

        }

    }
getLinkedObject(output: any): any
Code
getLinkedObject( output = null ) {

        const linkedElement = this.getLinkedElement();

        return linkedElement ? linkedElement.getObject( output ) : null;

    }
getLinkedElement(): any
Code
getLinkedElement() {

        const link = this.getLink();

        return link ? link.outputElement : null;

    }
Code
getLink() {

        return this.links[ 0 ];

    }
_createIO(type: any): HTMLElement
Code
_createIO( type ) {

        const { dom } = this;

        const ioDOM = document.createElement( 'f-io' );
        ioDOM.style.visibility = 'hidden';
        ioDOM.className = type;

        const onConnectEvent = ( e ) => {

            e.preventDefault();

            e.stopPropagation();

            selected = null;

            const nodeDOM = this.node.dom;

            nodeDOM.classList.add( 'io-connect' );

            ioDOM.classList.add( 'connect' );
            dom.classList.add( 'select' );

            const defaultOutput = Link.InputDirection === 'left' ? 'lio' : 'rio';

            const link = type === defaultOutput ? new Link( this ) : new Link( null, this );
            const previewLink = new Link( link.inputElement, link.outputElement );

            this.links.push( link );

            draggableDOM( e, ( data ) => {

                if ( previewLink.outputElement )
                    previewLink.outputElement.dom.classList.remove( 'invalid' );

                if ( previewLink.inputElement )
                    previewLink.inputElement.dom.classList.remove( 'invalid' );

                previewLink.inputElement = link.inputElement;
                previewLink.outputElement = link.outputElement;

                if ( type === defaultOutput ) {

                    previewLink.outputElement = selected;

                } else {

                    previewLink.inputElement = selected;

                }

                const isInvalid = previewLink.inputElement !== null && previewLink.outputElement !== null &&
                    previewLink.inputElement.inputLength > 0 && previewLink.outputElement.outputLength > 0 &&
                    dispatchEventList( previewLink.inputElement.events.valid, previewLink.inputElement, previewLink.outputElement, data.dragging ? 'dragging' : 'dragged' ) === false;

                if ( data.dragging && isInvalid ) {

                    if ( type === defaultOutput ) {

                        if ( previewLink.outputElement )
                            previewLink.outputElement.dom.classList.add( 'invalid' );

                    } else {

                        if ( previewLink.inputElement )
                            previewLink.inputElement.dom.classList.add( 'invalid' );

                    }

                    return;

                }

                if ( ! data.dragging ) {

                    nodeDOM.classList.remove( 'io-connect' );

                    ioDOM.classList.remove( 'connect' );
                    dom.classList.remove( 'select' );

                    this.links.splice( this.links.indexOf( link ), 1 );

                    if ( selected !== null && ! isInvalid ) {

                        link.inputElement = previewLink.inputElement;
                        link.outputElement = previewLink.outputElement;

                        // check if is an is circular link

                        if ( link.outputElement.node.isCircular( link.inputElement.node ) ) {

                            return;

                        }

                        //

                        if ( link.inputElement.inputLength > 0 && link.outputElement.outputLength > 0 ) {

                            link.inputElement.connect( link.outputElement );

                        }

                    }

                }

            }, { className: 'connecting' } );

        };

        ioDOM.addEventListener( 'mousedown', onConnectEvent, true );
        ioDOM.addEventListener( 'touchstart', onConnectEvent, true );

        return ioDOM;

    }

Input

Class Code
class Input extends Serializer {

    static get type() {

        return 'Input';

    }

    constructor( dom ) {

        super();

        this.dom = dom;

        this.element = null;

        this.extra = null;

        this.tagColor = null;

        this.property = null;

        this.events = {
            'change': [],
            'click': []
        };

        this.addEventListener( 'change', ( ) => {

            dispatchEventList( this.events.change, this );

        } );

        this.addEventListener( 'click', ( ) => {

            dispatchEventList( this.events.click, this );

        } );

    }

    setExtra( value ) {

        this.extra = value;

        return this;

    }

    getExtra() {

        return this.extra;

    }

    setProperty( name ) {

        this.property = name;

        return this;

    }

    getProperty() {

        return this.property;

    }

    setTagColor( color ) {

        this.tagColor = color;

        this.dom.style[ 'border-left' ] = `2px solid ${color}`;

        return this;

    }

    getTagColor() {

        return this.tagColor;

    }

    setToolTip( text ) {

        const div = document.createElement( 'f-tooltip' );
        div.innerText = text;

        this.dom.append( div );

        return this;

    }

    onChange( callback ) {

        this.events.change.push( callback );

        return this;

    }

    onClick( callback ) {

        this.events.click.push( callback );

        return this;

    }

    setReadOnly( value ) {

        this.getInput().readOnly = value;

        return this;

    }

    getReadOnly() {

        return this.getInput().readOnly;

    }

    setValue( value, dispatch = true ) {

        this.getInput().value = value;

        if ( dispatch ) this.dispatchEvent( new Event( 'change' ) );

        return this;

    }

    getValue() {

        return this.getInput().value;

    }

    getInput() {

        return this.dom;

    }

    serialize( data ) {

        data.value = this.getValue();

    }

    deserialize( data ) {

        this.setValue( data.value );

    }

}

Methods

setExtra(value: any): this
Code
setExtra( value ) {

        this.extra = value;

        return this;

    }
getExtra(): any
Code
getExtra() {

        return this.extra;

    }
setProperty(name: any): this
Code
setProperty( name ) {

        this.property = name;

        return this;

    }
getProperty(): any
Code
getProperty() {

        return this.property;

    }
setTagColor(color: any): this
Code
setTagColor( color ) {

        this.tagColor = color;

        this.dom.style[ 'border-left' ] = `2px solid ${color}`;

        return this;

    }
getTagColor(): any
Code
getTagColor() {

        return this.tagColor;

    }
setToolTip(text: any): this
Code
setToolTip( text ) {

        const div = document.createElement( 'f-tooltip' );
        div.innerText = text;

        this.dom.append( div );

        return this;

    }
onChange(callback: any): this
Code
onChange( callback ) {

        this.events.change.push( callback );

        return this;

    }
onClick(callback: any): this
Code
onClick( callback ) {

        this.events.click.push( callback );

        return this;

    }
setReadOnly(value: any): this
Code
setReadOnly( value ) {

        this.getInput().readOnly = value;

        return this;

    }
getReadOnly(): any
Code
getReadOnly() {

        return this.getInput().readOnly;

    }
setValue(value: any, dispatch: boolean): this
Code
setValue( value, dispatch = true ) {

        this.getInput().value = value;

        if ( dispatch ) this.dispatchEvent( new Event( 'change' ) );

        return this;

    }
getValue(): any
Code
getValue() {

        return this.getInput().value;

    }
getInput(): any
Code
getInput() {

        return this.dom;

    }
serialize(data: any): void
Code
serialize( data ) {

        data.value = this.getValue();

    }
deserialize(data: any): void
Code
deserialize( data ) {

        this.setValue( data.value );

    }

Node

Class Code
class Node extends Serializer {

    static get type() {

        return 'Node';

    }

    constructor() {

        super();

        const dom = document.createElement( 'f-node' );

        const onDown = () => {

            const canvas = this.canvas;

            if ( canvas !== null ) {

                canvas.select( this );

            }

        };

        dom.addEventListener( 'mousedown', onDown, true );
        dom.addEventListener( 'touchstart', onDown, true );

        this._onConnect = ( e ) => {

            const { target } = e;

            for ( const element of this.elements ) {

                if ( element !== target ) {

                    element.dispatchEvent( new Event( 'nodeConnect' ) );

                }

            }

        };

        this._onConnectChildren = ( e ) => {

            const { target } = e;

            for ( const element of this.elements ) {

                if ( element !== target ) {

                    element.dispatchEvent( new Event( 'nodeConnectChildren' ) );

                }

            }

        };

        this.dom = dom;

        this.style = '';

        this.canvas = null;
        this.resizable = false;
        this.serializePriority = 0;

        this.elements = [];

        this.events = {
            'focus': [],
            'blur': []
        };

        this.setWidth( 300 ).setPosition( 0, 0 );

    }

    get baseElement() {

        return this.elements[ 0 ];

    }

    setAlign( align ) {

        const dom = this.dom;
        const style = dom.style;

        style.left = '';
        style.top = '';
        style.animation = 'none';

        if ( typeof align === 'string' ) {

            dom.classList.add( align );

        } else if ( align ) {

            for ( const name in align ) {

                style[ name ] = align[ name ];

            }

        }

        return this;

    }

    setResizable( val ) {

        this.resizable = val === true;

        if ( this.resizable ) {

            this.dom.classList.add( 'resizable' );

        } else {

            this.dom.classList.remove( 'resizable' );

        }

        this.updateSize();

        return this;

    }

    onFocus( callback ) {

        this.events.focus.push( callback );

        return this;

    }

    onBlur( callback ) {

        this.events.blur.push( callback );

        return this;

    }

    setStyle( style ) {

        const dom = this.dom;

        if ( this.style ) dom.classList.remove( this.style );

        if ( style ) dom.classList.add( style );

        this.style = style;

        return this;

    }

    setPosition( x, y ) {

        const dom = this.dom;

        dom.style.left = numberToPX( x );
        dom.style.top = numberToPX( y );

        return this;

    }

    getPosition() {

        const dom = this.dom;

        return {
            x: parseInt( dom.style.left ),
            y: parseInt( dom.style.top )
        };

    }

    setWidth( val ) {

        this.dom.style.width = numberToPX( val );

        this.updateSize();

        return this;

    }

    getWidth() {

        return parseInt( this.dom.style.width );

    }

    getHeight() {

        return this.dom.offsetHeight;

    }

    getBound() {

        const { x, y } = this.getPosition();
        const { width, height } = this.dom.getBoundingClientRect();

        return { x, y, width, height };

    }

    add( element ) {

        this.elements.push( element );

        element.node = this;
        element.addEventListener( 'connect', this._onConnect );
        element.addEventListener( 'connectChildren', this._onConnectChildren );

        this.dom.append( element.dom );

        this.updateSize();

        return this;

    }

    remove( element ) {

        this.elements.splice( this.elements.indexOf( element ), 1 );

        element.node = null;
        element.removeEventListener( 'connect', this._onConnect );
        element.removeEventListener( 'connectChildren', this._onConnectChildren );

        this.dom.removeChild( element.dom );

        this.updateSize();

        return this;

    }

    dispose() {

        const canvas = this.canvas;

        if ( canvas !== null ) canvas.remove( this );

        for ( const element of this.elements ) {

            element.dispose();

        }

        this.dispatchEvent( new Event( 'dispose' ) );

    }

    isCircular( node ) {

        if ( node === this ) return true;

        const links = this.getLinks();

        for ( const link of links ) {

            if ( link.outputElement.node.isCircular( node ) ) {

                return true;

            }

        }

        return false;

    }

    getLinks() {

        const links = [];

        for ( const element of this.elements ) {

            links.push( ...element.links );

        }

        return links;

    }

    getColor() {

        return this.elements.length > 0 ? this.elements[ 0 ].getColor() : null;

    }

    updateSize() {

        for ( const element of this.elements ) {

            element.dom.style.width = '';

        }

        if ( this.resizable === true ) {

            const element = this.elements[ this.elements.length - 1 ];

            if ( element !== undefined ) {

                element.dom.style.width = this.dom.style.width;

            }

        }

    }

    serialize( data ) {

        const { x, y } = this.getPosition();

        const elements = [];

        for ( const element of this.elements ) {

            elements.push( element.toJSON( data ).id );

        }

        data.x = x;
        data.y = y;
        data.width = this.getWidth();
        data.elements = elements;
        data.autoResize = this.resizable;

        if ( this.style !== '' ) {

            data.style = this.style;

        }

    }

    deserialize( data ) {

        this.setPosition( data.x, data.y );
        this.setWidth( data.width );
        this.setResizable( data.autoResize );

        if ( data.style !== undefined ) {

            this.setStyle( data.style );

        }

        const elements = this.elements;

        if ( elements.length > 0 ) {

            let index = 0;

            for ( const id of data.elements ) {

                data.objects[ id ] = elements[ index ++ ];

            }

        } else {

            for ( const id of data.elements ) {

                this.add( data.objects[ id ] );

            }

        }

    }

}

Methods

setAlign(align: any): this
Code
setAlign( align ) {

        const dom = this.dom;
        const style = dom.style;

        style.left = '';
        style.top = '';
        style.animation = 'none';

        if ( typeof align === 'string' ) {

            dom.classList.add( align );

        } else if ( align ) {

            for ( const name in align ) {

                style[ name ] = align[ name ];

            }

        }

        return this;

    }
setResizable(val: any): this
Code
setResizable( val ) {

        this.resizable = val === true;

        if ( this.resizable ) {

            this.dom.classList.add( 'resizable' );

        } else {

            this.dom.classList.remove( 'resizable' );

        }

        this.updateSize();

        return this;

    }
onFocus(callback: any): this
Code
onFocus( callback ) {

        this.events.focus.push( callback );

        return this;

    }
onBlur(callback: any): this
Code
onBlur( callback ) {

        this.events.blur.push( callback );

        return this;

    }
setStyle(style: any): this
Code
setStyle( style ) {

        const dom = this.dom;

        if ( this.style ) dom.classList.remove( this.style );

        if ( style ) dom.classList.add( style );

        this.style = style;

        return this;

    }
setPosition(x: any, y: any): this
Code
setPosition( x, y ) {

        const dom = this.dom;

        dom.style.left = numberToPX( x );
        dom.style.top = numberToPX( y );

        return this;

    }
getPosition(): { x: number; y: number; }
Code
getPosition() {

        const dom = this.dom;

        return {
            x: parseInt( dom.style.left ),
            y: parseInt( dom.style.top )
        };

    }
setWidth(val: any): this
Code
setWidth( val ) {

        this.dom.style.width = numberToPX( val );

        this.updateSize();

        return this;

    }
getWidth(): number
Code
getWidth() {

        return parseInt( this.dom.style.width );

    }
getHeight(): number
Code
getHeight() {

        return this.dom.offsetHeight;

    }
getBound(): { x: number; y: number; width: number; height: number; }
Code
getBound() {

        const { x, y } = this.getPosition();
        const { width, height } = this.dom.getBoundingClientRect();

        return { x, y, width, height };

    }
add(element: any): this
Code
add( element ) {

        this.elements.push( element );

        element.node = this;
        element.addEventListener( 'connect', this._onConnect );
        element.addEventListener( 'connectChildren', this._onConnectChildren );

        this.dom.append( element.dom );

        this.updateSize();

        return this;

    }
remove(element: any): this
Code
remove( element ) {

        this.elements.splice( this.elements.indexOf( element ), 1 );

        element.node = null;
        element.removeEventListener( 'connect', this._onConnect );
        element.removeEventListener( 'connectChildren', this._onConnectChildren );

        this.dom.removeChild( element.dom );

        this.updateSize();

        return this;

    }
dispose(): void
Code
dispose() {

        const canvas = this.canvas;

        if ( canvas !== null ) canvas.remove( this );

        for ( const element of this.elements ) {

            element.dispose();

        }

        this.dispatchEvent( new Event( 'dispose' ) );

    }
isCircular(node: any): boolean
Code
isCircular( node ) {

        if ( node === this ) return true;

        const links = this.getLinks();

        for ( const link of links ) {

            if ( link.outputElement.node.isCircular( node ) ) {

                return true;

            }

        }

        return false;

    }
Code
getLinks() {

        const links = [];

        for ( const element of this.elements ) {

            links.push( ...element.links );

        }

        return links;

    }
getColor(): any
Code
getColor() {

        return this.elements.length > 0 ? this.elements[ 0 ].getColor() : null;

    }
updateSize(): void
Code
updateSize() {

        for ( const element of this.elements ) {

            element.dom.style.width = '';

        }

        if ( this.resizable === true ) {

            const element = this.elements[ this.elements.length - 1 ];

            if ( element !== undefined ) {

                element.dom.style.width = this.dom.style.width;

            }

        }

    }
serialize(data: any): void
Code
serialize( data ) {

        const { x, y } = this.getPosition();

        const elements = [];

        for ( const element of this.elements ) {

            elements.push( element.toJSON( data ).id );

        }

        data.x = x;
        data.y = y;
        data.width = this.getWidth();
        data.elements = elements;
        data.autoResize = this.resizable;

        if ( this.style !== '' ) {

            data.style = this.style;

        }

    }
deserialize(data: any): void
Code
deserialize( data ) {

        this.setPosition( data.x, data.y );
        this.setWidth( data.width );
        this.setResizable( data.autoResize );

        if ( data.style !== undefined ) {

            this.setStyle( data.style );

        }

        const elements = this.elements;

        if ( elements.length > 0 ) {

            let index = 0;

            for ( const id of data.elements ) {

                data.objects[ id ] = elements[ index ++ ];

            }

        } else {

            for ( const id of data.elements ) {

                this.add( data.objects[ id ] );

            }

        }

    }

DraggableElement

Class Code
class DraggableElement extends Element {

    static get type() {

        return 'DraggableElement';

    }

    constructor( draggable = true ) {

        super( true );

        this.draggable = draggable;

        const onDrag = ( e ) => {

            e.preventDefault();

            if ( this.draggable === true ) {

                draggableDOM( this.node.dom, null, { className: 'dragging node' } );

            }

        };

        const { dom } = this;

        dom.addEventListener( 'mousedown', onDrag, true );
        dom.addEventListener( 'touchstart', onDrag, true );

    }

}

TitleElement

Class Code
class TitleElement extends DraggableElement {

    static get type() {

        return 'TitleElement';

    }

    constructor( title, draggable = true ) {

        super( draggable );

        const { dom } = this;

        dom.classList.add( 'title' );

        const dbClick = () => {

            this.node.canvas.focusSelected = ! this.node.canvas.focusSelected;

        };

        dom.addEventListener( 'dblclick', dbClick );

        const titleDOM = document.createElement( 'f-title' );
        titleDOM.innerText = title;

        const iconDOM = document.createElement( 'i' );

        const toolbarDOM = document.createElement( 'f-toolbar' );

        this.buttons = [];

        this.titleDOM = titleDOM;
        this.iconDOM = iconDOM;
        this.toolbarDOM = toolbarDOM;

        dom.append( titleDOM );
        dom.append( iconDOM );
        dom.append( toolbarDOM );

    }

    setIcon( value ) {

        this.iconDOM.className = value;

        return this;

    }

    getIcon() {

        return this.iconDOM.className;

    }

    setTitle( value ) {

        this.titleDOM.innerText = value;

        return this;

    }

    getTitle() {

        return this.titleDOM.innerText;

    }

    addButton( button ) {

        this.buttons.push( button );

        this.toolbarDOM.append( button.dom );

        return this;

    }

    serialize( data ) {

        super.serialize( data );

        const title = this.getTitle();
        const icon = this.getIcon();

        data.title = title;

        if ( icon !== '' ) {

            data.icon = icon;

        }

    }

    deserialize( data ) {

        super.deserialize( data );

        this.setTitle( data.title );

        if ( data.icon !== undefined ) {

            this.setIcon( data.icon );

        }

    }

}

Methods

setIcon(value: any): this
Code
setIcon( value ) {

        this.iconDOM.className = value;

        return this;

    }
getIcon(): string
Code
getIcon() {

        return this.iconDOM.className;

    }
setTitle(value: any): this
Code
setTitle( value ) {

        this.titleDOM.innerText = value;

        return this;

    }
getTitle(): string
Code
getTitle() {

        return this.titleDOM.innerText;

    }
addButton(button: any): this
Code
addButton( button ) {

        this.buttons.push( button );

        this.toolbarDOM.append( button.dom );

        return this;

    }
serialize(data: any): void
Code
serialize( data ) {

        super.serialize( data );

        const title = this.getTitle();
        const icon = this.getIcon();

        data.title = title;

        if ( icon !== '' ) {

            data.icon = icon;

        }

    }
deserialize(data: any): void
Code
deserialize( data ) {

        super.deserialize( data );

        this.setTitle( data.title );

        if ( data.icon !== undefined ) {

            this.setIcon( data.icon );

        }

    }

Canvas

Class Code
class Canvas extends Serializer {

    static get type() {

        return 'Canvas';

    }

    constructor() {

        super();

        const dom = document.createElement( 'f-canvas' );
        const contentDOM = document.createElement( 'f-content' );
        const areaDOM = document.createElement( 'f-area' );
        const dropDOM = document.createElement( 'f-drop' );

        const canvas = document.createElement( 'canvas' );
        const frontCanvas = document.createElement( 'canvas' );
        const mapCanvas = document.createElement( 'canvas' );

        const context = canvas.getContext( '2d' );
        const frontContext = frontCanvas.getContext( '2d' );
        const mapContext = mapCanvas.getContext( '2d' );

        this.dom = dom;

        this.contentDOM = contentDOM;
        this.areaDOM = areaDOM;
        this.dropDOM = dropDOM;

        this.canvas = canvas;
        this.frontCanvas = frontCanvas;
        this.mapCanvas = mapCanvas;

        this.context = context;
        this.frontContext = frontContext;
        this.mapContext = mapContext;

        this.clientX = 0;
        this.clientY = 0;

        this.relativeClientX = 0;
        this.relativeClientY = 0;

        this.nodes = [];

        this.selected = null;

        this.updating = false;

        this.droppedItems = [];

        this.events = {
            'drop': []
        };

        this._scrollLeft = 0;
        this._scrollTop = 0;
        this._zoom = 1;
        this._width = 0;
        this._height = 0;
        this._focusSelected = false;
        this._mapInfo = {
            scale: 1,
            screen: {}
        };

        canvas.className = 'background';
        frontCanvas.className = 'frontground';
        mapCanvas.className = 'map';

        dropDOM.innerHTML = '<span>drop your file</span>';

        dom.append( dropDOM );
        dom.append( canvas );
        dom.append( frontCanvas );
        dom.append( contentDOM );
        dom.append( areaDOM );
        dom.append( mapCanvas );

        const zoomTo = ( zoom, clientX = this.clientX, clientY = this.clientY ) => {

            zoom = Math.min( Math.max( zoom, .2 ), 1 );

            this.scrollLeft -= ( clientX / this.zoom ) - ( clientX / zoom );
            this.scrollTop -= ( clientY / this.zoom ) - ( clientY / zoom );
            this.zoom = zoom;

        };

        let touchData = null;

        const onTouchStart = () => {

            touchData = null;

        };

        const classInElements = ( element, className ) => {

            do {

                if ( element.classList ? element.classList.contains( className ) : false ) {

                    return true;

                }

            } while ( ( element = element.parentElement ) && element !== dom );

            return false;

        };

        const onMouseZoom = ( e ) => {

            if ( classInElements( e.srcElement, 'f-scroll' ) ) return;

            e.preventDefault();

            e.stopImmediatePropagation();

            const delta = e.deltaY * .003;

            zoomTo( this.zoom - delta );

        };

        const onTouchZoom = ( e ) => {

            if ( e.touches && e.touches.length === 2 ) {

                e.preventDefault();

                e.stopImmediatePropagation();

                const clientX = ( e.touches[ 0 ].clientX + e.touches[ 1 ].clientX ) / 2;
                const clientY = ( e.touches[ 0 ].clientY + e.touches[ 1 ].clientY ) / 2;

                const distance = Math.hypot(
                    e.touches[ 0 ].clientX - e.touches[ 1 ].clientX,
                    e.touches[ 0 ].clientY - e.touches[ 1 ].clientY
                );

                if ( touchData === null ) {

                    touchData = {
                        distance
                    };

                }

                const delta = ( touchData.distance - distance ) * .003;
                touchData.distance = distance;

                zoomTo( this.zoom - delta, clientX, clientY );

            }

        };

        const onTouchMove = ( e ) => {

            if ( e.touches && e.touches.length === 1 ) {

                e.preventDefault();

                e.stopImmediatePropagation();

                const clientX = e.touches[ 0 ].clientX;
                const clientY = e.touches[ 0 ].clientY;

                if ( touchData === null ) {

                    const { scrollLeft, scrollTop } = this;

                    touchData = {
                        scrollLeft,
                        scrollTop,
                        clientX,
                        clientY
                    };

                }

                const zoom = this.zoom;

                this.scrollLeft = touchData.scrollLeft + ( ( clientX - touchData.clientX ) / zoom );
                this.scrollTop = touchData.scrollTop + ( ( clientY - touchData.clientY ) / zoom );

            }

        };

        dom.addEventListener( 'wheel', onMouseZoom );
        dom.addEventListener( 'touchmove', onTouchZoom );
        dom.addEventListener( 'touchstart', onTouchStart );
        canvas.addEventListener( 'touchmove', onTouchMove );

        let dropEnterCount = 0;

        const dragState = ( enter ) => {

            if ( enter ) {

                if ( dropEnterCount ++ === 0 ) {

                    this.droppedItems = [];

                    dropDOM.classList.add( 'visible' );

                    this.add( dropNode );

                }

            } else if ( -- dropEnterCount === 0 ) {

                dropDOM.classList.remove( 'visible' );

                this.remove( dropNode );

            }

        };

        dom.addEventListener( 'dragenter', () => {

            dragState( true );

        } );

        dom.addEventListener( 'dragleave', () => {

            dragState( false );

        } );

        dom.addEventListener( 'dragover', ( e ) => {

            e.preventDefault();

            const { relativeClientX, relativeClientY } = this;

            const centerNodeX = dropNode.getWidth() / 2;

            dropNode.setPosition( relativeClientX - centerNodeX, relativeClientY - 20 );

        } );

        dom.addEventListener( 'drop', ( e ) => {

            e.preventDefault();

            dragState( false );

            this.droppedItems = e.dataTransfer.items;

            dispatchEventList( this.events.drop, this );

        } );

        draggableDOM( dom, ( data ) => {

            const { delta, isTouch } = data;

            if ( ! isTouch ) {

                if ( data.scrollTop === undefined ) {

                    data.scrollLeft = this.scrollLeft;
                    data.scrollTop = this.scrollTop;

                }

                const zoom = this.zoom;

                this.scrollLeft = data.scrollLeft + ( delta.x / zoom );
                this.scrollTop = data.scrollTop + ( delta.y / zoom );

            }

            if ( data.dragging ) {

                dom.classList.add( 'grabbing' );

            } else {

                dom.classList.remove( 'grabbing' );

            }

        }, { className: 'dragging-canvas' } );


        draggableDOM( mapCanvas, ( data ) => {

            const { scale, screen } = this._mapInfo;

            if ( data.scrollLeft === undefined ) {

                const rect = this.mapCanvas.getBoundingClientRect();

                const clientMapX = data.client.x - rect.left;
                const clientMapY = data.client.y - rect.top;

                const overMapScreen =
                    clientMapX > screen.x && clientMapY > screen.y &&
                    clientMapX < screen.x + screen.width && clientMapY < screen.y + screen.height;

                if ( overMapScreen === false ) {

                    const scaleX = this._mapInfo.width / this.mapCanvas.width;

                    let scrollLeft = - this._mapInfo.left - ( clientMapX * scaleX );
                    let scrollTop = - this._mapInfo.top - ( clientMapY * ( this._mapInfo.height / this.mapCanvas.height ) );

                    scrollLeft += ( screen.width / 2 ) / scale;
                    scrollTop += ( screen.height / 2 ) / scale;

                    this.scrollLeft = scrollLeft;
                    this.scrollTop = scrollTop;

                }

                data.scrollLeft = this.scrollLeft;
                data.scrollTop = this.scrollTop;

            }

            this.scrollLeft = data.scrollLeft - ( data.delta.x / scale );
            this.scrollTop = data.scrollTop - ( data.delta.y / scale );

        }, { click: true } );

        this._onMoveEvent = ( e ) => {

            const event = e.touches ? e.touches[ 0 ] : e;
            const { zoom, rect } = this;

            this.clientX = event.clientX;
            this.clientY = event.clientY;

            const rectClientX = ( this.clientX - rect.left ) / zoom;
            const rectClientY = ( this.clientY - rect.top ) / zoom;

            this.relativeClientX = rectClientX - this.scrollLeft;
            this.relativeClientY = rectClientY - this.scrollTop;

        };

        this._onUpdate = () => {

            this.update();

        };

        this.start();

    }

    getBounds() {

        const bounds = { x: Infinity, y: Infinity, width: - Infinity, height: - Infinity };

        for ( const node of this.nodes ) {

            const { x, y, width, height } = node.getBound();

            bounds.x = Math.min( bounds.x, x );
            bounds.y = Math.min( bounds.y, y );
            bounds.width = Math.max( bounds.width, x + width );
            bounds.height = Math.max( bounds.height, y + height );

        }

        bounds.x = Math.round( bounds.x );
        bounds.y = Math.round( bounds.y );
        bounds.width = Math.round( bounds.width );
        bounds.height = Math.round( bounds.height );

        return bounds;

    }

    get width() {

        return this._width;

    }

    get height() {

        return this._height;

    }

    get rect() {

        return this.dom.getBoundingClientRect();

    }

    get zoom() {

        return this._zoom;

    }

    set zoom( val ) {

        this._zoom = val;
        this.contentDOM.style.zoom = val;

        val === 1 ? this.dom.classList.remove( 'zoom' ) : this.dom.classList.add( 'zoom' );

        this.updateMozTransform();

    }

    set scrollLeft( val ) {

        this._scrollLeft = val;
        this.contentDOM.style.left = numberToPX( val );

        this.updateMozTransform();

    }

    get scrollLeft() {

        return this._scrollLeft;

    }

    set scrollTop( val ) {

        this._scrollTop = val;
        this.contentDOM.style.top = numberToPX( val );

        this.updateMozTransform();

    }

    get scrollTop() {

        return this._scrollTop;

    }

    set focusSelected( value ) {

        if ( this._focusSelected === value ) return;

        const classList = this.dom.classList;

        this._focusSelected = value;

        if ( value ) {

            classList.add( 'focusing' );

        } else {

            classList.remove( 'focusing' );

        }

    }

    get focusSelected() {

        return this._focusSelected;

    }

    get useTransform() {

        const userAgent = navigator.userAgent;
        const isSafari = /Safari/.test( userAgent ) && ! /Chrome/.test( userAgent );

        return ! isSafari;

    }

    updateMozTransform() {

        if ( this.useTransform === false ) return;

        this.contentDOM.style[ '-moz-transform' ] = 'scale(' + this.zoom + ')';
        this.contentDOM.style[ '-moz-transform-origin' ] = '-' + this.contentDOM.style.left + ' -' + this.contentDOM.style.top;

    }

    onDrop( callback ) {

        this.events.drop.push( callback );

        return this;

    }

    start() {

        this.updating = true;

        document.addEventListener( 'wheel', this._onMoveEvent, true );

        document.addEventListener( 'mousedown', this._onMoveEvent, true );
        document.addEventListener( 'touchstart', this._onMoveEvent, true );

        document.addEventListener( 'mousemove', this._onMoveEvent, true );
        document.addEventListener( 'touchmove', this._onMoveEvent, true );

        document.addEventListener( 'dragover', this._onMoveEvent, true );

        requestAnimationFrame( this._onUpdate );

    }

    stop() {

        this.updating = false;

        document.removeEventListener( 'wheel', this._onMoveEvent, true );

        document.removeEventListener( 'mousedown', this._onMoveEvent, true );
        document.removeEventListener( 'touchstart', this._onMoveEvent, true );

        document.removeEventListener( 'mousemove', this._onMoveEvent, true );
        document.removeEventListener( 'touchmove', this._onMoveEvent, true );

        document.removeEventListener( 'dragover', this._onMoveEvent, true );

    }

    add( node ) {

        if ( node.canvas === this ) return;

        this.nodes.push( node );

        node.canvas = this;

        this.contentDOM.append( node.dom );

        return this;

    }

    remove( node ) {

        if ( node === this.selected ) {

            this.select();

        }

        this.unlink( node );

        const nodes = this.nodes;

        nodes.splice( nodes.indexOf( node ), 1 );

        node.canvas = null;

        this.contentDOM.removeChild( node.dom );

        node.dispatchEvent( new Event( 'remove' ) );

        return this;

    }

    clear() {

        const nodes = this.nodes;

        while ( nodes.length > 0 ) {

            this.remove( nodes[ 0 ] );

        }

        return this;

    }

    unlink( node ) {

        const links = this.getLinks();

        for ( const link of links ) {

            if ( link.inputElement && link.outputElement ) {

                if ( link.inputElement.node === node ) {

                    link.inputElement.connect();

                } else if ( link.outputElement.node === node ) {

                    link.inputElement.connect();

                }

            }

        }

    }

    getLinks() {

        const links = [];

        for ( const node of this.nodes ) {

            links.push( ...node.getLinks() );

        }

        return links;

    }

    centralize() {

        const bounds = this.getBounds();

        this.scrollLeft = ( this.canvas.width / 2 ) - ( ( - bounds.x + bounds.width ) / 2 );
        this.scrollTop = ( this.canvas.height / 2 ) - ( ( - bounds.y + bounds.height ) / 2 );

        return this;

    }

    setSize( width, height ) {

        this._width = width;
        this._height = height;

        this.update();

        return this;

    }

    select( node = null ) {

        if ( node === this.selected ) return;

        const previousNode = this.selected;

        if ( previousNode !== null ) {

            this.focusSelected = false;

            previousNode.dom.classList.remove( 'selected' );

            this.selected = null;

            dispatchEventList( previousNode.events.blur, previousNode );

        }

        if ( node !== null ) {

            node.dom.classList.add( 'selected' );

            this.selected = node;

            dispatchEventList( node.events.focus, node );

        }

    }

    updateMap() {

        const { nodes, mapCanvas, mapContext, scrollLeft, scrollTop, canvas, zoom, _mapInfo } = this;

        const bounds = this.getBounds();

        mapCanvas.width = 300;
        mapCanvas.height = 200;

        mapContext.clearRect( 0, 0, mapCanvas.width, mapCanvas.height );

        mapContext.fillStyle = 'rgba( 0, 0, 0, 0 )';
        mapContext.fillRect( 0, 0, mapCanvas.width, mapCanvas.height );

        const boundsWidth = - bounds.x + bounds.width;
        const boundsHeight = - bounds.y + bounds.height;

        const mapScale = Math.min( mapCanvas.width / boundsWidth, mapCanvas.height / boundsHeight ) * .5;

        const boundsMapWidth = boundsWidth * mapScale;
        const boundsMapHeight = boundsHeight * mapScale;

        const boundsOffsetX = ( mapCanvas.width / 2 ) - ( boundsMapWidth / 2 );
        const boundsOffsetY = ( mapCanvas.height / 2 ) - ( boundsMapHeight / 2 );

        let selectedNode = null;

        for ( const node of nodes ) {

            const nodeBound = node.getBound();
            const nodeColor = node.getColor();

            nodeBound.x += - bounds.x;
            nodeBound.y += - bounds.y;

            nodeBound.x *= mapScale;
            nodeBound.y *= mapScale;
            nodeBound.width *= mapScale;
            nodeBound.height *= mapScale;

            nodeBound.x += boundsOffsetX;
            nodeBound.y += boundsOffsetY;

            if ( node !== this.selected ) {

                mapContext.fillStyle = nodeColor;
                mapContext.fillRect( nodeBound.x, nodeBound.y, nodeBound.width, nodeBound.height );

            } else {

                selectedNode = {
                    nodeBound,
                    nodeColor
                };

            }

        }

        if ( selectedNode !== null ) {

            const { nodeBound, nodeColor } = selectedNode;

            mapContext.fillStyle = nodeColor;
            mapContext.fillRect( nodeBound.x, nodeBound.y, nodeBound.width, nodeBound.height );

        }

        const screenMapX = ( - ( scrollLeft + bounds.x ) * mapScale ) + boundsOffsetX;
        const screenMapY = ( - ( scrollTop + bounds.y ) * mapScale ) + boundsOffsetY;
        const screenMapWidth = ( canvas.width * mapScale ) / zoom;
        const screenMapHeight = ( canvas.height * mapScale ) / zoom;

        mapContext.fillStyle = 'rgba( 200, 200, 200, 0.1 )';
        mapContext.fillRect( screenMapX, screenMapY, screenMapWidth, screenMapHeight );

        //

        _mapInfo.scale = mapScale;
        _mapInfo.left = ( - boundsOffsetX / mapScale ) + bounds.x;
        _mapInfo.top = ( - boundsOffsetY / mapScale ) + bounds.y;
        _mapInfo.width = mapCanvas.width / mapScale;
        _mapInfo.height = mapCanvas.height / mapScale;
        _mapInfo.screen.x = screenMapX;
        _mapInfo.screen.y = screenMapY;
        _mapInfo.screen.width = screenMapWidth;
        _mapInfo.screen.height = screenMapHeight;

    }

    updateLines() {

        const { dom, zoom, canvas, frontCanvas, frontContext, context, _width, _height, useTransform } = this;

        const domRect = this.rect;

        if ( canvas.width !== _width || canvas.height !== _height ) {

            canvas.width = _width;
            canvas.height = _height;

            frontCanvas.width = _width;
            frontCanvas.height = _height;

        }

        context.clearRect( 0, 0, _width, _height );
        frontContext.clearRect( 0, 0, _width, _height );

        //

        context.globalCompositeOperation = 'lighter';
        frontContext.globalCompositeOperation = 'source-over';

        const links = this.getLinks();

        const aPos = { x: 0, y: 0 };
        const bPos = { x: 0, y: 0 };

        const offsetIORadius = 10;

        let dragging = '';

        for ( const link of links ) {

            const { lioElement, rioElement } = link;

            let draggingLink = '';
            let length = 0;

            if ( lioElement !== null ) {

                const rect = lioElement.dom.getBoundingClientRect();

                length = Math.max( length, lioElement.rioLength );

                aPos.x = rect.x + rect.width;
                aPos.y = rect.y + ( rect.height / 2 );

                if ( useTransform ) {

                    aPos.x /= zoom;
                    aPos.y /= zoom;

                }

            } else {

                aPos.x = this.clientX;
                aPos.y = this.clientY;

                draggingLink = 'lio';

            }

            if ( rioElement !== null ) {

                const rect = rioElement.dom.getBoundingClientRect();

                length = Math.max( length, rioElement.lioLength );

                bPos.x = rect.x;
                bPos.y = rect.y + ( rect.height / 2 );

                if ( useTransform ) {

                    bPos.x /= zoom;
                    bPos.y /= zoom;

                }

            } else {

                bPos.x = this.clientX;
                bPos.y = this.clientY;

                draggingLink = 'rio';

            }

            dragging = dragging || draggingLink;

            const drawContext = draggingLink ? frontContext : context;

            if ( draggingLink || length === 1 ) {

                let colorA = null,
                    colorB = null;

                if ( draggingLink === 'rio' ) {

                    colorA = colorB = lioElement.getRIOColor();

                    aPos.x += offsetIORadius;
                    bPos.x /= zoom;
                    bPos.y /= zoom;

                } else if ( draggingLink === 'lio' ) {

                    colorA = colorB = rioElement.getLIOColor();

                    bPos.x -= offsetIORadius;
                    aPos.x /= zoom;
                    aPos.y /= zoom;

                } else {

                    colorA = lioElement.getRIOColor();
                    colorB = rioElement.getLIOColor();

                }

                drawLine(
                    aPos.x * zoom, aPos.y * zoom,
                    bPos.x * zoom, bPos.y * zoom,
                    false, 2, colorA || '#ffffff', drawContext, colorB || '#ffffff'
                );

            } else {

                length = Math.min( length, 4 );

                for ( let i = 0; i < length; i ++ ) {

                    const color = colors[ i ] || '#ffffff';

                    const marginY = 4;

                    const rioLength = Math.min( lioElement.rioLength, length );
                    const lioLength = Math.min( rioElement.lioLength, length );

                    const colorA = lioElement.getRIOColor() || color;
                    const colorB = rioElement.getLIOColor() || color;

                    const aCenterY = ( ( rioLength * marginY ) * .5 ) - ( marginY / 2 );
                    const bCenterY = ( ( lioLength * marginY ) * .5 ) - ( marginY / 2 );

                    const aIndex = Math.min( i, rioLength - 1 );
                    const bIndex = Math.min( i, lioLength - 1 );

                    const aPosY = ( aIndex * marginY ) - 1;
                    const bPosY = ( bIndex * marginY ) - 1;

                    drawLine(
                        aPos.x * zoom, ( ( aPos.y + aPosY ) - aCenterY ) * zoom,
                        bPos.x * zoom, ( ( bPos.y + bPosY ) - bCenterY ) * zoom,
                        false, 2, colorA, drawContext, colorB
                    );

                }

            }

        }

        context.globalCompositeOperation = 'destination-in';

        context.fillRect( domRect.x, domRect.y, domRect.width, domRect.height );

        if ( dragging !== '' ) {

            dom.classList.add( 'dragging-' + dragging );

        } else {

            dom.classList.remove( 'dragging-lio' );
            dom.classList.remove( 'dragging-rio' );

        }

    }

    update() {

        if ( this.updating === false ) return;

        requestAnimationFrame( this._onUpdate );

        this.updateLines();
        this.updateMap();

    }

    serialize( data ) {

        const nodes = [];
        const serializeNodes = this.nodes.sort( ( a, b ) => a.serializePriority > b.serializePriority ? - 1 : 1 );

        for ( const node of serializeNodes ) {

            nodes.push( node.toJSON( data ).id );

        }

        data.nodes = nodes;

    }

    deserialize( data ) {

        for ( const id of data.nodes ) {

            this.add( data.objects[ id ] );

        }

    }

}

Methods

getBounds(): { x: number; y: number; width: number; height: number; }
Code
getBounds() {

        const bounds = { x: Infinity, y: Infinity, width: - Infinity, height: - Infinity };

        for ( const node of this.nodes ) {

            const { x, y, width, height } = node.getBound();

            bounds.x = Math.min( bounds.x, x );
            bounds.y = Math.min( bounds.y, y );
            bounds.width = Math.max( bounds.width, x + width );
            bounds.height = Math.max( bounds.height, y + height );

        }

        bounds.x = Math.round( bounds.x );
        bounds.y = Math.round( bounds.y );
        bounds.width = Math.round( bounds.width );
        bounds.height = Math.round( bounds.height );

        return bounds;

    }
updateMozTransform(): void
Code
updateMozTransform() {

        if ( this.useTransform === false ) return;

        this.contentDOM.style[ '-moz-transform' ] = 'scale(' + this.zoom + ')';
        this.contentDOM.style[ '-moz-transform-origin' ] = '-' + this.contentDOM.style.left + ' -' + this.contentDOM.style.top;

    }
onDrop(callback: any): this
Code
onDrop( callback ) {

        this.events.drop.push( callback );

        return this;

    }
start(): void
Code
start() {

        this.updating = true;

        document.addEventListener( 'wheel', this._onMoveEvent, true );

        document.addEventListener( 'mousedown', this._onMoveEvent, true );
        document.addEventListener( 'touchstart', this._onMoveEvent, true );

        document.addEventListener( 'mousemove', this._onMoveEvent, true );
        document.addEventListener( 'touchmove', this._onMoveEvent, true );

        document.addEventListener( 'dragover', this._onMoveEvent, true );

        requestAnimationFrame( this._onUpdate );

    }
stop(): void
Code
stop() {

        this.updating = false;

        document.removeEventListener( 'wheel', this._onMoveEvent, true );

        document.removeEventListener( 'mousedown', this._onMoveEvent, true );
        document.removeEventListener( 'touchstart', this._onMoveEvent, true );

        document.removeEventListener( 'mousemove', this._onMoveEvent, true );
        document.removeEventListener( 'touchmove', this._onMoveEvent, true );

        document.removeEventListener( 'dragover', this._onMoveEvent, true );

    }
add(node: any): this
Code
add( node ) {

        if ( node.canvas === this ) return;

        this.nodes.push( node );

        node.canvas = this;

        this.contentDOM.append( node.dom );

        return this;

    }
remove(node: any): this
Code
remove( node ) {

        if ( node === this.selected ) {

            this.select();

        }

        this.unlink( node );

        const nodes = this.nodes;

        nodes.splice( nodes.indexOf( node ), 1 );

        node.canvas = null;

        this.contentDOM.removeChild( node.dom );

        node.dispatchEvent( new Event( 'remove' ) );

        return this;

    }
clear(): this
Code
clear() {

        const nodes = this.nodes;

        while ( nodes.length > 0 ) {

            this.remove( nodes[ 0 ] );

        }

        return this;

    }
unlink(node: any): void
Code
unlink( node ) {

        const links = this.getLinks();

        for ( const link of links ) {

            if ( link.inputElement && link.outputElement ) {

                if ( link.inputElement.node === node ) {

                    link.inputElement.connect();

                } else if ( link.outputElement.node === node ) {

                    link.inputElement.connect();

                }

            }

        }

    }
Code
getLinks() {

        const links = [];

        for ( const node of this.nodes ) {

            links.push( ...node.getLinks() );

        }

        return links;

    }
centralize(): this
Code
centralize() {

        const bounds = this.getBounds();

        this.scrollLeft = ( this.canvas.width / 2 ) - ( ( - bounds.x + bounds.width ) / 2 );
        this.scrollTop = ( this.canvas.height / 2 ) - ( ( - bounds.y + bounds.height ) / 2 );

        return this;

    }
setSize(width: any, height: any): this
Code
setSize( width, height ) {

        this._width = width;
        this._height = height;

        this.update();

        return this;

    }
select(node: any): void
Code
select( node = null ) {

        if ( node === this.selected ) return;

        const previousNode = this.selected;

        if ( previousNode !== null ) {

            this.focusSelected = false;

            previousNode.dom.classList.remove( 'selected' );

            this.selected = null;

            dispatchEventList( previousNode.events.blur, previousNode );

        }

        if ( node !== null ) {

            node.dom.classList.add( 'selected' );

            this.selected = node;

            dispatchEventList( node.events.focus, node );

        }

    }
updateMap(): void
Code
updateMap() {

        const { nodes, mapCanvas, mapContext, scrollLeft, scrollTop, canvas, zoom, _mapInfo } = this;

        const bounds = this.getBounds();

        mapCanvas.width = 300;
        mapCanvas.height = 200;

        mapContext.clearRect( 0, 0, mapCanvas.width, mapCanvas.height );

        mapContext.fillStyle = 'rgba( 0, 0, 0, 0 )';
        mapContext.fillRect( 0, 0, mapCanvas.width, mapCanvas.height );

        const boundsWidth = - bounds.x + bounds.width;
        const boundsHeight = - bounds.y + bounds.height;

        const mapScale = Math.min( mapCanvas.width / boundsWidth, mapCanvas.height / boundsHeight ) * .5;

        const boundsMapWidth = boundsWidth * mapScale;
        const boundsMapHeight = boundsHeight * mapScale;

        const boundsOffsetX = ( mapCanvas.width / 2 ) - ( boundsMapWidth / 2 );
        const boundsOffsetY = ( mapCanvas.height / 2 ) - ( boundsMapHeight / 2 );

        let selectedNode = null;

        for ( const node of nodes ) {

            const nodeBound = node.getBound();
            const nodeColor = node.getColor();

            nodeBound.x += - bounds.x;
            nodeBound.y += - bounds.y;

            nodeBound.x *= mapScale;
            nodeBound.y *= mapScale;
            nodeBound.width *= mapScale;
            nodeBound.height *= mapScale;

            nodeBound.x += boundsOffsetX;
            nodeBound.y += boundsOffsetY;

            if ( node !== this.selected ) {

                mapContext.fillStyle = nodeColor;
                mapContext.fillRect( nodeBound.x, nodeBound.y, nodeBound.width, nodeBound.height );

            } else {

                selectedNode = {
                    nodeBound,
                    nodeColor
                };

            }

        }

        if ( selectedNode !== null ) {

            const { nodeBound, nodeColor } = selectedNode;

            mapContext.fillStyle = nodeColor;
            mapContext.fillRect( nodeBound.x, nodeBound.y, nodeBound.width, nodeBound.height );

        }

        const screenMapX = ( - ( scrollLeft + bounds.x ) * mapScale ) + boundsOffsetX;
        const screenMapY = ( - ( scrollTop + bounds.y ) * mapScale ) + boundsOffsetY;
        const screenMapWidth = ( canvas.width * mapScale ) / zoom;
        const screenMapHeight = ( canvas.height * mapScale ) / zoom;

        mapContext.fillStyle = 'rgba( 200, 200, 200, 0.1 )';
        mapContext.fillRect( screenMapX, screenMapY, screenMapWidth, screenMapHeight );

        //

        _mapInfo.scale = mapScale;
        _mapInfo.left = ( - boundsOffsetX / mapScale ) + bounds.x;
        _mapInfo.top = ( - boundsOffsetY / mapScale ) + bounds.y;
        _mapInfo.width = mapCanvas.width / mapScale;
        _mapInfo.height = mapCanvas.height / mapScale;
        _mapInfo.screen.x = screenMapX;
        _mapInfo.screen.y = screenMapY;
        _mapInfo.screen.width = screenMapWidth;
        _mapInfo.screen.height = screenMapHeight;

    }
updateLines(): void
Code
updateLines() {

        const { dom, zoom, canvas, frontCanvas, frontContext, context, _width, _height, useTransform } = this;

        const domRect = this.rect;

        if ( canvas.width !== _width || canvas.height !== _height ) {

            canvas.width = _width;
            canvas.height = _height;

            frontCanvas.width = _width;
            frontCanvas.height = _height;

        }

        context.clearRect( 0, 0, _width, _height );
        frontContext.clearRect( 0, 0, _width, _height );

        //

        context.globalCompositeOperation = 'lighter';
        frontContext.globalCompositeOperation = 'source-over';

        const links = this.getLinks();

        const aPos = { x: 0, y: 0 };
        const bPos = { x: 0, y: 0 };

        const offsetIORadius = 10;

        let dragging = '';

        for ( const link of links ) {

            const { lioElement, rioElement } = link;

            let draggingLink = '';
            let length = 0;

            if ( lioElement !== null ) {

                const rect = lioElement.dom.getBoundingClientRect();

                length = Math.max( length, lioElement.rioLength );

                aPos.x = rect.x + rect.width;
                aPos.y = rect.y + ( rect.height / 2 );

                if ( useTransform ) {

                    aPos.x /= zoom;
                    aPos.y /= zoom;

                }

            } else {

                aPos.x = this.clientX;
                aPos.y = this.clientY;

                draggingLink = 'lio';

            }

            if ( rioElement !== null ) {

                const rect = rioElement.dom.getBoundingClientRect();

                length = Math.max( length, rioElement.lioLength );

                bPos.x = rect.x;
                bPos.y = rect.y + ( rect.height / 2 );

                if ( useTransform ) {

                    bPos.x /= zoom;
                    bPos.y /= zoom;

                }

            } else {

                bPos.x = this.clientX;
                bPos.y = this.clientY;

                draggingLink = 'rio';

            }

            dragging = dragging || draggingLink;

            const drawContext = draggingLink ? frontContext : context;

            if ( draggingLink || length === 1 ) {

                let colorA = null,
                    colorB = null;

                if ( draggingLink === 'rio' ) {

                    colorA = colorB = lioElement.getRIOColor();

                    aPos.x += offsetIORadius;
                    bPos.x /= zoom;
                    bPos.y /= zoom;

                } else if ( draggingLink === 'lio' ) {

                    colorA = colorB = rioElement.getLIOColor();

                    bPos.x -= offsetIORadius;
                    aPos.x /= zoom;
                    aPos.y /= zoom;

                } else {

                    colorA = lioElement.getRIOColor();
                    colorB = rioElement.getLIOColor();

                }

                drawLine(
                    aPos.x * zoom, aPos.y * zoom,
                    bPos.x * zoom, bPos.y * zoom,
                    false, 2, colorA || '#ffffff', drawContext, colorB || '#ffffff'
                );

            } else {

                length = Math.min( length, 4 );

                for ( let i = 0; i < length; i ++ ) {

                    const color = colors[ i ] || '#ffffff';

                    const marginY = 4;

                    const rioLength = Math.min( lioElement.rioLength, length );
                    const lioLength = Math.min( rioElement.lioLength, length );

                    const colorA = lioElement.getRIOColor() || color;
                    const colorB = rioElement.getLIOColor() || color;

                    const aCenterY = ( ( rioLength * marginY ) * .5 ) - ( marginY / 2 );
                    const bCenterY = ( ( lioLength * marginY ) * .5 ) - ( marginY / 2 );

                    const aIndex = Math.min( i, rioLength - 1 );
                    const bIndex = Math.min( i, lioLength - 1 );

                    const aPosY = ( aIndex * marginY ) - 1;
                    const bPosY = ( bIndex * marginY ) - 1;

                    drawLine(
                        aPos.x * zoom, ( ( aPos.y + aPosY ) - aCenterY ) * zoom,
                        bPos.x * zoom, ( ( bPos.y + bPosY ) - bCenterY ) * zoom,
                        false, 2, colorA, drawContext, colorB
                    );

                }

            }

        }

        context.globalCompositeOperation = 'destination-in';

        context.fillRect( domRect.x, domRect.y, domRect.width, domRect.height );

        if ( dragging !== '' ) {

            dom.classList.add( 'dragging-' + dragging );

        } else {

            dom.classList.remove( 'dragging-lio' );
            dom.classList.remove( 'dragging-rio' );

        }

    }
update(): void
Code
update() {

        if ( this.updating === false ) return;

        requestAnimationFrame( this._onUpdate );

        this.updateLines();
        this.updateMap();

    }
serialize(data: any): void
Code
serialize( data ) {

        const nodes = [];
        const serializeNodes = this.nodes.sort( ( a, b ) => a.serializePriority > b.serializePriority ? - 1 : 1 );

        for ( const node of serializeNodes ) {

            nodes.push( node.toJSON( data ).id );

        }

        data.nodes = nodes;

    }
deserialize(data: any): void
Code
deserialize( data ) {

        for ( const id of data.nodes ) {

            this.add( data.objects[ id ] );

        }

    }
Class Code
class Menu extends EventTarget {

    constructor( className ) {

        super();

        const dom = document.createElement( 'f-menu' );
        dom.className = className + ' bottom left hidden';

        const listDOM = document.createElement( 'f-list' );

        dom.append( listDOM );

        this.dom = dom;
        this.listDOM = listDOM;

        this.visible = false;

        this.align = 'bottom left';

        this.subMenus = new WeakMap();
        this.domButtons = new WeakMap();

        this.buttons = [];

        this.events = {};

    }

    onContext( callback ) {

        this.events.context.push( callback );

        return this;

    }

    setAlign( align ) {

        const dom = this.dom;

        removeDOMClass( dom, this.align );
        addDOMClass( dom, align );

        this.align = align;

        return this;

    }

    getAlign() {

        return this.align;

    }

    show() {

        this.dom.classList.remove( 'hidden' );

        this.visible = true;

        this.dispatchEvent( new Event( 'show' ) );

        return this;

    }

    hide() {

        this.dom.classList.add( 'hidden' );

        this.dispatchEvent( new Event( 'hide' ) );

        this.visible = false;

    }

    add( button, submenu = null ) {

        const liDOM = document.createElement( 'f-item' );

        if ( submenu !== null ) {

            liDOM.classList.add( 'submenu' );

            liDOM.append( submenu.dom );

            this.subMenus.set( button, submenu );

            button.dom.addEventListener( 'mouseover', () => submenu.show() );
            button.dom.addEventListener( 'mouseout', () => submenu.hide() );

        }

        liDOM.append( button.dom );

        this.buttons.push( button );

        this.listDOM.append( liDOM );

        this.domButtons.set( button, liDOM );

        return this;

    }

    clear() {

        this.buttons = [];

        this.subMenus = new WeakMap();
        this.domButtons = new WeakMap();

        while ( this.listDOM.firstChild ) {

            this.listDOM.firstChild.remove();

        }

    }

}

Methods

onContext(callback: any): this
Code
onContext( callback ) {

        this.events.context.push( callback );

        return this;

    }
setAlign(align: any): this
Code
setAlign( align ) {

        const dom = this.dom;

        removeDOMClass( dom, this.align );
        addDOMClass( dom, align );

        this.align = align;

        return this;

    }
getAlign(): string
Code
getAlign() {

        return this.align;

    }
show(): this
Code
show() {

        this.dom.classList.remove( 'hidden' );

        this.visible = true;

        this.dispatchEvent( new Event( 'show' ) );

        return this;

    }
hide(): void
Code
hide() {

        this.dom.classList.add( 'hidden' );

        this.dispatchEvent( new Event( 'hide' ) );

        this.visible = false;

    }
add(button: any, submenu: any): this
Code
add( button, submenu = null ) {

        const liDOM = document.createElement( 'f-item' );

        if ( submenu !== null ) {

            liDOM.classList.add( 'submenu' );

            liDOM.append( submenu.dom );

            this.subMenus.set( button, submenu );

            button.dom.addEventListener( 'mouseover', () => submenu.show() );
            button.dom.addEventListener( 'mouseout', () => submenu.hide() );

        }

        liDOM.append( button.dom );

        this.buttons.push( button );

        this.listDOM.append( liDOM );

        this.domButtons.set( button, liDOM );

        return this;

    }
clear(): void
Code
clear() {

        this.buttons = [];

        this.subMenus = new WeakMap();
        this.domButtons = new WeakMap();

        while ( this.listDOM.firstChild ) {

            this.listDOM.firstChild.remove();

        }

    }

ContextMenu

Class Code
class ContextMenu extends Menu {

    constructor( target = null ) {

        super( 'context' );

        this.events.context = [];

        this._lastButtonClick = null;

        this._onButtonClick = ( e = null ) => {

            const button = e ? e.target : null;

            if ( this._lastButtonClick ) {

                this._lastButtonClick.dom.parentElement.classList.remove( 'active' );

            }

            this._lastButtonClick = button;

            if ( button ) {

                if ( this.subMenus.has( button ) ) {

                    this.subMenus.get( button )._onButtonClick();

                }

                button.dom.parentElement.classList.add( 'active' );

            }

        };

        this._onButtonMouseOver = ( e ) => {

            const button = e.target;

            if ( this.subMenus.has( button ) && this._lastButtonClick !== button ) {

                this._onButtonClick();

            }

        };

        this.addEventListener( 'context', ( ) => {

            dispatchEventList( this.events.context, this );

        } );

        this.setTarget( target );

    }

    openFrom( dom ) {

        const rect = dom.getBoundingClientRect();

        return this.open( rect.x + ( rect.width / 2 ), rect.y + ( rect.height / 2 ) );

    }

    open( x = pointer.x, y = pointer.y ) {

        if ( lastContext !== null ) {

            lastContext.hide();

        }

        lastContext = this;

        this.setPosition( x, y );

        document.body.append( this.dom );

        return this.show();

    }

    setWidth( width ) {

        this.dom.style.width = numberToPX( width );

        return this;

    }

    setPosition( x, y ) {

        const dom = this.dom;

        dom.style.left = numberToPX( x );
        dom.style.top = numberToPX( y );

        return this;

    }

    setTarget( target = null ) {

        if ( target !== null ) {

            const onContextMenu = ( e ) => {

                e.preventDefault();

                if ( e.pointerType !== 'mouse' || ( e.pageX === 0 && e.pageY === 0 ) ) return;

                this.dispatchEvent( new Event( 'context' ) );

                this.open();

            };

            this.target = target;

            target.addEventListener( 'contextmenu', onContextMenu, false );

        }

        return this;

    }

    show() {

        if ( ! this.opened ) {

            this.dom.style.left = '';
            this.dom.style.transform = '';

        }

        const domRect = this.dom.getBoundingClientRect();

        let offsetX = Math.min( window.innerWidth - ( domRect.x + domRect.width + 10 ), 0 );
        let offsetY = Math.min( window.innerHeight - ( domRect.y + domRect.height + 10 ), 0 );

        if ( this.opened ) {

            if ( offsetX < 0 ) offsetX = - domRect.width;
            if ( offsetY < 0 ) offsetY = - domRect.height;

            this.setPosition( domRect.x + offsetX, domRect.y + offsetY );

        } else {

            // flip submenus

            if ( offsetX < 0 ) this.dom.style.left = '-100%';
            if ( offsetY < 0 ) this.dom.style.transform = 'translateY( calc( 32px - 100% ) )';

        }

        return super.show();

    }

    hide() {

        if ( this.opened ) {

            lastContext = null;

        }

        return super.hide();

    }

    add( button, submenu = null ) {

        button.addEventListener( 'click', this._onButtonClick );
        button.addEventListener( 'mouseover', this._onButtonMouseOver );

        return super.add( button, submenu );

    }

    get opened() {

        return lastContext === this;

    }

}

Methods

openFrom(dom: any): this
Code
openFrom( dom ) {

        const rect = dom.getBoundingClientRect();

        return this.open( rect.x + ( rect.width / 2 ), rect.y + ( rect.height / 2 ) );

    }
open(x: number, y: number): this
Code
open( x = pointer.x, y = pointer.y ) {

        if ( lastContext !== null ) {

            lastContext.hide();

        }

        lastContext = this;

        this.setPosition( x, y );

        document.body.append( this.dom );

        return this.show();

    }
setWidth(width: any): this
Code
setWidth( width ) {

        this.dom.style.width = numberToPX( width );

        return this;

    }
setPosition(x: any, y: any): this
Code
setPosition( x, y ) {

        const dom = this.dom;

        dom.style.left = numberToPX( x );
        dom.style.top = numberToPX( y );

        return this;

    }
setTarget(target: any): this
Code
setTarget( target = null ) {

        if ( target !== null ) {

            const onContextMenu = ( e ) => {

                e.preventDefault();

                if ( e.pointerType !== 'mouse' || ( e.pageX === 0 && e.pageY === 0 ) ) return;

                this.dispatchEvent( new Event( 'context' ) );

                this.open();

            };

            this.target = target;

            target.addEventListener( 'contextmenu', onContextMenu, false );

        }

        return this;

    }
show(): this
Code
show() {

        if ( ! this.opened ) {

            this.dom.style.left = '';
            this.dom.style.transform = '';

        }

        const domRect = this.dom.getBoundingClientRect();

        let offsetX = Math.min( window.innerWidth - ( domRect.x + domRect.width + 10 ), 0 );
        let offsetY = Math.min( window.innerHeight - ( domRect.y + domRect.height + 10 ), 0 );

        if ( this.opened ) {

            if ( offsetX < 0 ) offsetX = - domRect.width;
            if ( offsetY < 0 ) offsetY = - domRect.height;

            this.setPosition( domRect.x + offsetX, domRect.y + offsetY );

        } else {

            // flip submenus

            if ( offsetX < 0 ) this.dom.style.left = '-100%';
            if ( offsetY < 0 ) this.dom.style.transform = 'translateY( calc( 32px - 100% ) )';

        }

        return super.show();

    }
hide(): void
Code
hide() {

        if ( this.opened ) {

            lastContext = null;

        }

        return super.hide();

    }
add(button: any, submenu: any): this
Code
add( button, submenu = null ) {

        button.addEventListener( 'click', this._onButtonClick );
        button.addEventListener( 'mouseover', this._onButtonMouseOver );

        return super.add( button, submenu );

    }

CircleMenu

Class Code
class CircleMenu extends Menu {

    constructor() {

        super( 'circle' );

    }

}

Tips

Class Code
class Tips extends EventTarget {

    constructor() {

        super();

        const dom = document.createElement( 'f-tips' );

        this.dom = dom;

        this.time = 0;
        this.duration = 3000;

    }

    message( str ) {

        return this.tip( str );

    }

    error( str ) {

        return this.tip( str, 'error' );

    }

    tip( html, className = '' ) {

        const dom = document.createElement( 'f-tip' );
        dom.className = className;
        dom.innerHTML = html;

        this.dom.prepend( dom );

        //requestAnimationFrame( () => dom.style.opacity = 1 );

        this.time = Math.min( this.time + this.duration, this.duration );

        setTimeout( () => {

            this.time = Math.max( this.time - this.duration, 0 );

            dom.style.opacity = 0;

            setTimeout( () => dom.remove(), 250 );

        }, this.time );

        return this;

    }

}

Methods

message(str: any): this
Code
message( str ) {

        return this.tip( str );

    }
error(str: any): this
Code
error( str ) {

        return this.tip( str, 'error' );

    }
tip(html: any, className: string): this
Code
tip( html, className = '' ) {

        const dom = document.createElement( 'f-tip' );
        dom.className = className;
        dom.innerHTML = html;

        this.dom.prepend( dom );

        //requestAnimationFrame( () => dom.style.opacity = 1 );

        this.time = Math.min( this.time + this.duration, this.duration );

        setTimeout( () => {

            this.time = Math.max( this.time - this.duration, 0 );

            dom.style.opacity = 0;

            setTimeout( () => dom.remove(), 250 );

        }, this.time );

        return this;

    }
Class Code
class Search extends Menu {

    constructor() {

        super( 'search' );

        this.events.submit = [];
        this.events.filter = [];

        this.tags = new WeakMap();

        const inputDOM = document.createElement( 'input' );
        inputDOM.placeholder = 'Type here';

        let filter = true;
        let filterNeedUpdate = true;

        inputDOM.addEventListener( 'focusout', () => {

            filterNeedUpdate = true;

            this.setValue( '' );

        } );

        inputDOM.onkeydown = ( e ) => {

            const key = e.key;

            if ( key === 'ArrowUp' ) {

                const index = this.filteredIndex;

                if ( this.forceAutoComplete ) {

                    this.filteredIndex = index !== null ? ( index + 1 ) % ( this.filtered.length || 1 ) : 0;

                } else {

                    this.filteredIndex = index !== null ? Math.min( index + 1, this.filtered.length - 1 ) : 0;

                }

                e.preventDefault();

                filter = false;

            } else if ( key === 'ArrowDown' ) {

                const index = this.filteredIndex;

                if ( this.forceAutoComplete ) {

                    this.filteredIndex = index - 1;

                    if ( this.filteredIndex === null ) this.filteredIndex = this.filtered.length - 1;

                } else {

                    this.filteredIndex = index !== null ? index - 1 : null;

                }

                e.preventDefault();

                filter = false;

            } else if ( key === 'Enter' ) {

                this.value = this.currentFiltered ? this.currentFiltered.button.getValue() : inputDOM.value;

                this.submit();

                e.preventDefault();

                filter = false;

            } else {

                filter = true;

            }

        };

        inputDOM.onkeyup = () => {

            if ( filter ) {

                if ( filterNeedUpdate ) {

                    this.dispatchEvent( new Event( 'filter' ) );

                    filterNeedUpdate = false;

                }

                this.filter( inputDOM.value );

            }

        };

        this.filtered = [];
        this.currentFiltered = null;

        this.value = '';

        this.forceAutoComplete = false;

        this.dom.append( inputDOM );

        this.inputDOM = inputDOM;

        this.addEventListener( 'filter', ( ) => {

            dispatchEventList( this.events.filter, this );

        } );

        this.addEventListener( 'submit', ( ) => {

            dispatchEventList( this.events.submit, this );

        } );

    }

    submit() {

        this.dispatchEvent( new Event( 'submit' ) );

        return this.setValue( '' );

    }

    setValue( value ) {

        this.inputDOM.value = value;

        this.filter( value );

        return this;

    }

    getValue() {

        return this.value;

    }

    onFilter( callback ) {

        this.events.filter.push( callback );

        return this;

    }

    onSubmit( callback ) {

        this.events.submit.push( callback );

        return this;

    }

    getFilterByButton( button ) {

        for ( const filter of this.filtered ) {

            if ( filter.button === button ) {

                return filter;

            }

        }

        return null;

    }

    add( button ) {

        super.add( button );

        const onDown = () => {

            const filter = this.getFilterByButton( button );

            this.filteredIndex = this.filtered.indexOf( filter );
            this.value = button.getValue();

            this.submit();

        };

        button.dom.addEventListener( 'mousedown', onDown );
        button.dom.addEventListener( 'touchstart', onDown );

        this.domButtons.get( button ).remove();

        return this;

    }

    set filteredIndex( index ) {

        if ( this.currentFiltered ) {

            const buttonDOM = this.domButtons.get( this.currentFiltered.button );

            buttonDOM.classList.remove( 'active' );

            this.currentFiltered = null;

        }

        const filteredItem = this.filtered[ index ];

        if ( filteredItem ) {

            const buttonDOM = this.domButtons.get( filteredItem.button );

            buttonDOM.classList.add( 'active' );

            this.currentFiltered = filteredItem;

        }

        this.updateFilter();

    }

    get filteredIndex() {

        return this.currentFiltered ? this.filtered.indexOf( this.currentFiltered ) : null;

    }

    setTag( button, tags ) {

        this.tags.set( button, tags );

    }

    filter( text ) {

        text = filterString( text );

        const tags = this.tags;
        const filtered = [];

        for ( const button of this.buttons ) {

            const buttonDOM = this.domButtons.get( button );

            buttonDOM.remove();

            const buttonTags = tags.has( button ) ? ' ' + tags.get( button ) : '';

            const label = filterString( button.getValue() + buttonTags );

            if ( text && label.includes( text ) === true ) {

                const score = text.length / label.length;

                filtered.push( {
                    button,
                    score
                } );

            }

        }

        filtered.sort( ( a, b ) => b.score - a.score );

        this.filtered = filtered;
        this.filteredIndex = this.forceAutoComplete ? 0 : null;

    }

    updateFilter() {

        const filteredIndex = Math.min( this.filteredIndex, this.filteredIndex - 3 );

        for ( let i = 0; i < this.filtered.length; i ++ ) {

            const button = this.filtered[ i ].button;
            const buttonDOM = this.domButtons.get( button );

            buttonDOM.remove();

            if ( i >= filteredIndex ) {

                this.listDOM.append( buttonDOM );

            }

        }

    }

}

Methods

submit(): this
Code
submit() {

        this.dispatchEvent( new Event( 'submit' ) );

        return this.setValue( '' );

    }
setValue(value: any): this
Code
setValue( value ) {

        this.inputDOM.value = value;

        this.filter( value );

        return this;

    }
getValue(): string
Code
getValue() {

        return this.value;

    }
onFilter(callback: any): this
Code
onFilter( callback ) {

        this.events.filter.push( callback );

        return this;

    }
onSubmit(callback: any): this
Code
onSubmit( callback ) {

        this.events.submit.push( callback );

        return this;

    }
getFilterByButton(button: any): any
Code
getFilterByButton( button ) {

        for ( const filter of this.filtered ) {

            if ( filter.button === button ) {

                return filter;

            }

        }

        return null;

    }
add(button: any): this
Code
add( button ) {

        super.add( button );

        const onDown = () => {

            const filter = this.getFilterByButton( button );

            this.filteredIndex = this.filtered.indexOf( filter );
            this.value = button.getValue();

            this.submit();

        };

        button.dom.addEventListener( 'mousedown', onDown );
        button.dom.addEventListener( 'touchstart', onDown );

        this.domButtons.get( button ).remove();

        return this;

    }
setTag(button: any, tags: any): void
Code
setTag( button, tags ) {

        this.tags.set( button, tags );

    }
filter(text: any): void
Code
filter( text ) {

        text = filterString( text );

        const tags = this.tags;
        const filtered = [];

        for ( const button of this.buttons ) {

            const buttonDOM = this.domButtons.get( button );

            buttonDOM.remove();

            const buttonTags = tags.has( button ) ? ' ' + tags.get( button ) : '';

            const label = filterString( button.getValue() + buttonTags );

            if ( text && label.includes( text ) === true ) {

                const score = text.length / label.length;

                filtered.push( {
                    button,
                    score
                } );

            }

        }

        filtered.sort( ( a, b ) => b.score - a.score );

        this.filtered = filtered;
        this.filteredIndex = this.forceAutoComplete ? 0 : null;

    }
updateFilter(): void
Code
updateFilter() {

        const filteredIndex = Math.min( this.filteredIndex, this.filteredIndex - 3 );

        for ( let i = 0; i < this.filtered.length; i ++ ) {

            const button = this.filtered[ i ].button;
            const buttonDOM = this.domButtons.get( button );

            buttonDOM.remove();

            if ( i >= filteredIndex ) {

                this.listDOM.append( buttonDOM );

            }

        }

    }

LabelElement

Class Code
class LabelElement extends Element {

    static get type() {

        return 'LabelElement';

    }

    constructor( label = '', align = '' ) {

        super();

        this.labelDOM = document.createElement( 'f-label' );
        this.inputsDOM = document.createElement( 'f-inputs' );

        const spanDOM = document.createElement( 'span' );

        this.spanDOM = spanDOM;
        this.iconDOM = null;

        this.labelDOM.append( spanDOM );

        this.dom.append( this.labelDOM );
        this.dom.append( this.inputsDOM );

        this.serializeLabel = false;

        this.setLabel( label );
        this.setAlign( align );

    }

    setIcon( value ) {

        this.iconDOM = this.iconDOM || document.createElement( 'i' );
        this.iconDOM.className = value;

        if ( value ) this.labelDOM.prepend( this.iconDOM );
        else this.iconDOM.remove();

        return this;

    }

    getIcon() {

        return this.iconDOM ? this.iconDOM.className : null;

    }

    setAlign( align ) {

        this.labelDOM.className = align;

    }

    setLabel( val ) {

        this.spanDOM.innerText = val;

    }

    getLabel() {

        return this.spanDOM.innerText;

    }

    serialize( data ) {

        super.serialize( data );

        if ( this.serializeLabel ) {

            const label = this.getLabel();
            const icon = this.getIcon();

            data.label = label;

            if ( icon !== '' ) {

                data.icon = icon;

            }

        }

    }

    deserialize( data ) {

        super.deserialize( data );

        if ( this.serializeLabel ) {

            this.setLabel( data.label );

            if ( data.icon !== undefined ) {

                this.setIcon( data.icon );

            }

        }

    }

}

Methods

setIcon(value: any): this
Code
setIcon( value ) {

        this.iconDOM = this.iconDOM || document.createElement( 'i' );
        this.iconDOM.className = value;

        if ( value ) this.labelDOM.prepend( this.iconDOM );
        else this.iconDOM.remove();

        return this;

    }
getIcon(): any
Code
getIcon() {

        return this.iconDOM ? this.iconDOM.className : null;

    }
setAlign(align: any): void
Code
setAlign( align ) {

        this.labelDOM.className = align;

    }
setLabel(val: any): void
Code
setLabel( val ) {

        this.spanDOM.innerText = val;

    }
getLabel(): string
Code
getLabel() {

        return this.spanDOM.innerText;

    }
serialize(data: any): void
Code
serialize( data ) {

        super.serialize( data );

        if ( this.serializeLabel ) {

            const label = this.getLabel();
            const icon = this.getIcon();

            data.label = label;

            if ( icon !== '' ) {

                data.icon = icon;

            }

        }

    }
deserialize(data: any): void
Code
deserialize( data ) {

        super.deserialize( data );

        if ( this.serializeLabel ) {

            this.setLabel( data.label );

            if ( data.icon !== undefined ) {

                this.setIcon( data.icon );

            }

        }

    }

ButtonInput

Class Code
class ButtonInput extends Input {

    static get type() {

        return 'ButtonInput';

    }

    constructor( innterText = '' ) {

        const dom = document.createElement( 'button' );

        const spanDOM = document.createElement( 'span' );
        dom.append( spanDOM );

        const iconDOM = document.createElement( 'i' );
        dom.append( iconDOM );

        super( dom );

        this.spanDOM = spanDOM;
        this.iconDOM = iconDOM;

        spanDOM.innerText = innterText;

        dom.onmouseover = () => {

            this.dispatchEvent( new Event( 'mouseover' ) );

        };

        dom.onclick = dom.ontouchstart =
        iconDOM.onclick = iconDOM.ontouchstart = ( e ) => {

            e.preventDefault();

            e.stopPropagation();

            this.dispatchEvent( new Event( 'click' ) );

        };

    }

    setIcon( className ) {

        this.iconDOM.className = className;

        return this;

    }

    getIcon() {

        return this.iconDOM.className;

    }

    setValue( val ) {

        this.spanDOM.innerText = val;

        return this;

    }

    getValue() {

        return this.spanDOM.innerText;

    }

}

Methods

setIcon(className: any): this
Code
setIcon( className ) {

        this.iconDOM.className = className;

        return this;

    }
getIcon(): string
Code
getIcon() {

        return this.iconDOM.className;

    }
setValue(val: any): this
Code
setValue( val ) {

        this.spanDOM.innerText = val;

        return this;

    }
getValue(): string
Code
getValue() {

        return this.spanDOM.innerText;

    }

ColorInput

Class Code
class ColorInput extends Input {

    static get type() {

        return 'ColorInput';

    }

    constructor( value = 0x0099ff ) {

        const dom = document.createElement( 'input' );
        super( dom );

        dom.type = 'color';
        dom.value = numberToHex( value );

        dom.oninput = () => {

            this.dispatchEvent( new Event( 'change' ) );

        };

    }

    setValue( value, dispatch = true ) {

        return super.setValue( numberToHex( value ), dispatch );

    }

    getValue() {

        return parseInt( super.getValue().substr( 1 ), 16 );

    }

}

Methods

setValue(value: any, dispatch: boolean): this
Code
setValue( value, dispatch = true ) {

        return super.setValue( numberToHex( value ), dispatch );

    }
getValue(): number
Code
getValue() {

        return parseInt( super.getValue().substr( 1 ), 16 );

    }

NumberInput

Class Code
class NumberInput extends Input {

    static get type() {

        return 'NumberInput';

    }

    constructor( value = 0, min = - Infinity, max = Infinity, step = .01 ) {

        const dom = document.createElement( 'input' );
        super( dom );

        this.min = min;
        this.max = max;
        this.step = step;

        this.integer = false;

        dom.type = 'text';
        dom.className = 'number';
        dom.value = this._getString( value );
        dom.spellcheck = false;
        dom.autocomplete = 'off';

        dom.ondragstart = dom.oncontextmenu = ( e ) => {

            e.preventDefault();

            e.stopPropagation();

        };

        dom.onfocus = dom.onclick = () => {

            dom.select();

        };

        dom.onblur = () => {

            this.dom.value = this._getString( this.dom.value );

            this.dispatchEvent( new Event( 'blur' ) );

        };

        dom.onchange = () => {

            this.dispatchEvent( new Event( 'change' ) );

        };

        dom.onkeydown = ( e ) => {

            if ( e.key.length === 1 && /\d|\.|\-/.test( e.key ) !== true ) {

                return false;

            }

            if ( e.key === 'Enter' ) {

                e.target.blur();

            }

            e.stopPropagation();

        };

        draggableDOM( dom, ( data ) => {

            const { delta } = data;

            if ( dom.readOnly === true ) return;

            if ( data.value === undefined ) {

                data.value = this.getValue();

            }

            const diff = delta.x - delta.y;

            const value = data.value + ( diff * this.step );

            dom.value = this._getString( value.toFixed( this.precision ) );

            this.dispatchEvent( new Event( 'change' ) );

        } );

    }

    setStep( step ) {

        this.step = step;

        return this;

    }

    setRange( min, max, step ) {

        this.min = min;
        this.max = max;
        this.step = step;

        this.dispatchEvent( new Event( 'range' ) );

        return this.setValue( this.getValue() );

    }

    setInterger( bool ) {

        this.integer = bool;
        this.step = .1;

        return this.setValue( this.getValue() );

    }

    get precision() {

        if ( this.integer === true ) return 0;

        const fract = this.step % 1;

        return fract !== 0 ? fract.toString().split( '.' )[ 1 ].length : 1;

    }

    setValue( val, dispatch = true ) {

        return super.setValue( this._getString( val ), dispatch );

    }

    getValue() {

        return Number( this.dom.value );

    }

    serialize( data ) {

        const { min, max } = this;

        if ( min !== - Infinity && max !== Infinity ) {

            data.min = this.min;
            data.max = this.max;
            data.step = this.step;

        }

        super.serialize( data );

    }

    deserialize( data ) {

        if ( data.min !== undefined ) {

            const { min, max, step } = this;

            this.setRange( min, max, step );

        }

        super.deserialize( data );

    }

    _getString( value ) {

        const num = Math.min( Math.max( Number( value ), this.min ), this.max );

        if ( this.integer === true ) {

            return Math.floor( num );

        } else {

            return num + ( num % 1 ? '' : '.0' );

        }

    }

}

Methods

setStep(step: any): this
Code
setStep( step ) {

        this.step = step;

        return this;

    }
setRange(min: any, max: any, step: any): this
Code
setRange( min, max, step ) {

        this.min = min;
        this.max = max;
        this.step = step;

        this.dispatchEvent( new Event( 'range' ) );

        return this.setValue( this.getValue() );

    }
setInterger(bool: any): this
Code
setInterger( bool ) {

        this.integer = bool;
        this.step = .1;

        return this.setValue( this.getValue() );

    }
setValue(val: any, dispatch: boolean): this
Code
setValue( val, dispatch = true ) {

        return super.setValue( this._getString( val ), dispatch );

    }
getValue(): number
Code
getValue() {

        return Number( this.dom.value );

    }
serialize(data: any): void
Code
serialize( data ) {

        const { min, max } = this;

        if ( min !== - Infinity && max !== Infinity ) {

            data.min = this.min;
            data.max = this.max;
            data.step = this.step;

        }

        super.serialize( data );

    }
deserialize(data: any): void
Code
deserialize( data ) {

        if ( data.min !== undefined ) {

            const { min, max, step } = this;

            this.setRange( min, max, step );

        }

        super.deserialize( data );

    }
_getString(value: any): string | number
Code
_getString( value ) {

        const num = Math.min( Math.max( Number( value ), this.min ), this.max );

        if ( this.integer === true ) {

            return Math.floor( num );

        } else {

            return num + ( num % 1 ? '' : '.0' );

        }

    }

SelectInput

Class Code
class SelectInput extends Input {

    static get type() {

        return 'SelectInput';

    }

    constructor( options = [], value = null ) {

        const dom = document.createElement( 'select' );
        super( dom );

        dom.onchange = () => {

            this.dispatchEvent( new Event( 'change' ) );

        };

        dom.onmousedown = dom.ontouchstart = () => {

            this.dispatchEvent( new Event( 'click' ) );

        };

        this.setOptions( options, value );

    }

    setOptions( options, value = null ) {

        const dom = this.dom;
        const defaultValue = dom.value;

        let containsDefaultValue = false;

        this.options = options;
        dom.innerHTML = '';

        for ( let index = 0; index < options.length; index ++ ) {

            let opt = options[ index ];

            if ( typeof opt === 'string' ) {

                opt = { name: opt, value: index };

            }

            const option = document.createElement( 'option' );
            option.innerText = opt.name;
            option.value = opt.value;

            if ( containsDefaultValue === false && defaultValue === opt.value ) {

                containsDefaultValue = true;

            }

            dom.append( option );

        }

        dom.value = value !== null ? value : containsDefaultValue ? defaultValue : '';

        return this;

    }

    getOptions() {

        return this._options;

    }

    serialize( data ) {

        data.options = [ ...this.options ];

        super.serialize( data );

    }

    deserialize( data ) {

        const currentOptions = this.options;

        if ( currentOptions.length === 0 ) {

            this.setOptions( data.options );

        }

        super.deserialize( data );

    }

}

Methods

setOptions(options: any, value: any): this
Code
setOptions( options, value = null ) {

        const dom = this.dom;
        const defaultValue = dom.value;

        let containsDefaultValue = false;

        this.options = options;
        dom.innerHTML = '';

        for ( let index = 0; index < options.length; index ++ ) {

            let opt = options[ index ];

            if ( typeof opt === 'string' ) {

                opt = { name: opt, value: index };

            }

            const option = document.createElement( 'option' );
            option.innerText = opt.name;
            option.value = opt.value;

            if ( containsDefaultValue === false && defaultValue === opt.value ) {

                containsDefaultValue = true;

            }

            dom.append( option );

        }

        dom.value = value !== null ? value : containsDefaultValue ? defaultValue : '';

        return this;

    }
getOptions(): any
Code
getOptions() {

        return this._options;

    }
serialize(data: any): void
Code
serialize( data ) {

        data.options = [ ...this.options ];

        super.serialize( data );

    }
deserialize(data: any): void
Code
deserialize( data ) {

        const currentOptions = this.options;

        if ( currentOptions.length === 0 ) {

            this.setOptions( data.options );

        }

        super.deserialize( data );

    }

SliderInput

Class Code
class SliderInput extends Input {

    static get type() {

        return 'SliderInput';

    }

    constructor( value = 0, min = 0, max = 100 ) {

        const dom = document.createElement( 'f-subinputs' );
        super( dom );

        value = Math.min( Math.max( value, min ), max );

        const step = getStep( min, max );

        const rangeDOM = document.createElement( 'input' );
        rangeDOM.type = 'range';
        rangeDOM.min = min;
        rangeDOM.max = max;
        rangeDOM.step = step;
        rangeDOM.value = value;

        const field = new NumberInput( value, min, max, step );
        field.dom.className = 'range-value';
        field.onChange( () => {

            rangeDOM.value = field.getValue();

            this.dispatchEvent( new Event( 'change' ) );

        } );

        field.addEventListener( 'range', () => {

            rangeDOM.min = field.min;
            rangeDOM.max = field.max;
            rangeDOM.step = field.step;
            rangeDOM.value = field.getValue();

        } );

        dom.append( rangeDOM );
        dom.append( field.dom );

        this.rangeDOM = rangeDOM;
        this.field = field;

        const updateRangeValue = () => {

            let value = Number( rangeDOM.value );

            if ( value !== this.max && value + this.step >= this.max ) {

                // fix not end range fraction

                rangeDOM.value = value = this.max;

            }

            this.field.setValue( value );

        };

        draggableDOM( rangeDOM, () => {

            updateRangeValue();

            this.dispatchEvent( new Event( 'change' ) );

        }, { className: '' } );

    }

    get min() {

        return this.field.min;

    }

    get max() {

        return this.field.max;

    }

    get step() {

        return this.field.step;

    }

    setRange( min, max ) {

        this.field.setRange( min, max, getStep( min, max ) );

        this.dispatchEvent( new Event( 'range' ) );
        this.dispatchEvent( new Event( 'change' ) );

        return this;

    }

    setValue( val, dispatch = true ) {

        this.field.setValue( val );
        this.rangeDOM.value = val;

        if ( dispatch ) this.dispatchEvent( new Event( 'change' ) );

        return this;

    }

    getValue() {

        return this.field.getValue();

    }

    serialize( data ) {

        data.min = this.min;
        data.max = this.max;

        super.serialize( data );

    }

    deserialize( data ) {

        const { min, max } = data;

        this.setRange( min, max );

        super.deserialize( data );

    }

}

Methods

setRange(min: any, max: any): this
Code
setRange( min, max ) {

        this.field.setRange( min, max, getStep( min, max ) );

        this.dispatchEvent( new Event( 'range' ) );
        this.dispatchEvent( new Event( 'change' ) );

        return this;

    }
setValue(val: any, dispatch: boolean): this
Code
setValue( val, dispatch = true ) {

        this.field.setValue( val );
        this.rangeDOM.value = val;

        if ( dispatch ) this.dispatchEvent( new Event( 'change' ) );

        return this;

    }
getValue(): number
Code
getValue() {

        return this.field.getValue();

    }
serialize(data: any): void
Code
serialize( data ) {

        data.min = this.min;
        data.max = this.max;

        super.serialize( data );

    }
deserialize(data: any): void
Code
deserialize( data ) {

        const { min, max } = data;

        this.setRange( min, max );

        super.deserialize( data );

    }

StringInput

Class Code
class StringInput extends Input {

    static get type() {

        return 'StringInput';

    }

    constructor( value = '' ) {

        const dom = document.createElement( 'f-string' );
        super( dom );

        const inputDOM = document.createElement( 'input' );

        dom.append( inputDOM );

        inputDOM.type = 'text';
        inputDOM.value = value;
        inputDOM.spellcheck = false;
        inputDOM.autocomplete = 'off';

        this._buttonsDOM = null;
        this._datalistDOM = null;

        this.iconDOM = null;
        this.inputDOM = inputDOM;

        this.buttons = [];

        inputDOM.onblur = () => {

            this.dispatchEvent( new Event( 'blur' ) );

        };

        inputDOM.onchange = () => {

            this.dispatchEvent( new Event( 'change' ) );

        };

        let keyDownStr = '';

        inputDOM.onkeydown = () => keyDownStr = inputDOM.value;

        inputDOM.onkeyup = ( e ) => {

            if ( e.key === 'Enter' ) {

                e.target.blur();

            }

            e.stopPropagation();

            if ( keyDownStr !== inputDOM.value ) {

                this.dispatchEvent( new Event( 'change' ) );

            }

        };

    }

    setPlaceHolder( text ) {

        this.inputDOM.placeholder = text;

        return this;

    }

    setIcon( value ) {

        this.iconDOM = this.iconDOM || document.createElement( 'i' );
        this.iconDOM.setAttribute( 'type', 'icon' );
        this.iconDOM.className = value;

        if ( value ) this.dom.prepend( this.iconDOM );
        else this.iconDOM.remove();

        return this;

    }

    getIcon() {

        return this.iconInput ? this.iconInput.getIcon() : '';

    }

    addButton( button ) {

        this.buttonsDOM.prepend( button.iconDOM );

        this.buttons.push( button );

        return this;

    }

    addOption( value ) {

        const option = document.createElement( 'option' );
        option.value = value;

        this.datalistDOM.append( option );

        return this;

    }

    clearOptions() {

        this.datalistDOM.remove();

    }

    get datalistDOM() {

        let dom = this._datalistDOM;

        if ( dom === null ) {

            const datalistId = 'input-dt-' + this.id;

            dom = document.createElement( 'datalist' );
            dom.id = datalistId;

            this._datalistDOM = dom;

            this.inputDOM.autocomplete = 'on';
            this.inputDOM.setAttribute( 'list', datalistId );

            this.dom.prepend( dom );

        }

        return dom;

    }

    get buttonsDOM() {

        let dom = this._buttonsDOM;

        if ( dom === null ) {

            dom = document.createElement( 'f-buttons' );

            this._buttonsDOM = dom;

            this.dom.prepend( dom );

        }

        return dom;

    }

    getInput() {

        return this.inputDOM;

    }

}

Methods

setPlaceHolder(text: any): this
Code
setPlaceHolder( text ) {

        this.inputDOM.placeholder = text;

        return this;

    }
setIcon(value: any): this
Code
setIcon( value ) {

        this.iconDOM = this.iconDOM || document.createElement( 'i' );
        this.iconDOM.setAttribute( 'type', 'icon' );
        this.iconDOM.className = value;

        if ( value ) this.dom.prepend( this.iconDOM );
        else this.iconDOM.remove();

        return this;

    }
getIcon(): any
Code
getIcon() {

        return this.iconInput ? this.iconInput.getIcon() : '';

    }
addButton(button: any): this
Code
addButton( button ) {

        this.buttonsDOM.prepend( button.iconDOM );

        this.buttons.push( button );

        return this;

    }
addOption(value: any): this
Code
addOption( value ) {

        const option = document.createElement( 'option' );
        option.value = value;

        this.datalistDOM.append( option );

        return this;

    }
clearOptions(): void
Code
clearOptions() {

        this.datalistDOM.remove();

    }
getInput(): HTMLInputElement
Code
getInput() {

        return this.inputDOM;

    }

TextInput

Class Code
class TextInput extends Input {

    static get type() {

        return 'TextInput';

    }

    constructor( innerText = '' ) {

        const dom = document.createElement( 'textarea' );
        super( dom );

        dom.innerText = innerText;

        dom.classList.add( 'f-scroll' );

        dom.onblur = () => {

            this.dispatchEvent( new Event( 'blur' ) );

        };

        dom.onchange = () => {

            this.dispatchEvent( new Event( 'change' ) );

        };

        dom.onkeyup = ( e ) => {

            if ( e.key === 'Enter' ) {

                e.target.blur();

            }

            e.stopPropagation();

            this.dispatchEvent( new Event( 'change' ) );

        };

    }

}

ToggleInput

Class Code
class ToggleInput extends Input {

    static get type() {

        return 'ToggleInput';

    }

    constructor( value = false ) {

        const dom = document.createElement( 'input' );
        super( dom );

        dom.type = 'checkbox';
        dom.className = 'toggle';
        dom.checked = value;

        dom.onclick = () => this.dispatchEvent( new Event( 'click' ) );
        dom.onchange = () => this.dispatchEvent( new Event( 'change' ) );

    }

    setValue( val ) {

        this.dom.checked = val;

        this.dispatchEvent( new Event( 'change' ) );

        return this;

    }

    getValue() {

        return this.dom.checked;

    }

}

Methods

setValue(val: any): this
Code
setValue( val ) {

        this.dom.checked = val;

        this.dispatchEvent( new Event( 'change' ) );

        return this;

    }
getValue(): any
Code
getValue() {

        return this.dom.checked;

    }

TreeViewNode

Class Code
class TreeViewNode {

    constructor( name = '' ) {

        const dom = document.createElement( 'f-treeview-node' );
        const labelDOM = document.createElement( 'f-treeview-label' );
        const inputDOM = document.createElement( 'input' );

        const labelSpam = document.createElement( 'spam' );
        labelDOM.append( labelSpam );

        labelSpam.innerText = name;

        inputDOM.type = 'checkbox';

        dom.append( inputDOM );
        dom.append( labelDOM );

        this.dom = dom;
        this.childrenDOM = null;
        this.labelSpam = labelSpam;
        this.labelDOM = labelDOM;
        this.inputDOM = inputDOM;
        this.iconDOM = null;

        this.parent = null;
        this.children = [];

        this.selected = false;

        this.events = {
            'change': [],
            'click': []
        };

        dom.addEventListener( 'click', ( ) => {

            dispatchEventList( this.events.click, this );

        } );

    }

    setLabel( value ) {

        this.labelSpam.innerText = value;

        return this;

    }

    getLabel() {

        return this.labelSpam.innerText;

    }

    add( node ) {

        let childrenDOM = this.childrenDOM;

        if ( this.childrenDOM === null ) {

            const dom = this.dom;

            const arrowDOM = document.createElement( 'f-arrow' );
            childrenDOM = document.createElement( 'f-treeview-children' );

            dom.append( arrowDOM );
            dom.append( childrenDOM );

            this.childrenDOM = childrenDOM;

        }

        this.children.push( node );
        childrenDOM.append( node.dom );

        node.parent = this;

        return this;

    }

    setOpened( value ) {

        this.inputDOM.checked = value;

        return this;

    }

    getOpened() {

        return this.inputDOM.checkbox;

    }

    setIcon( value ) {

        this.iconDOM = this.iconDOM || document.createElement( 'i' );
        this.iconDOM.className = value;

        if ( value ) this.labelDOM.prepend( this.iconDOM );
        else this.iconDOM.remove();

        return this;

    }

    getIcon() {

        return this.iconDOM ? this.iconDOM.className : null;

    }

    setVisible( value ) {

        this.dom.style.display = value ? '' : 'none';

        return this;

    }

    setSelected( value ) {

        if ( this.selected === value ) return this;

        if ( value ) this.dom.classList.add( 'selected' );
        else this.dom.classList.remove( 'selected' );

        this.selected = value;

        return this;

    }

    onClick( callback ) {

        this.events.click.push( callback );

        return this;

    }

}

Methods

setLabel(value: any): this
Code
setLabel( value ) {

        this.labelSpam.innerText = value;

        return this;

    }
getLabel(): string
Code
getLabel() {

        return this.labelSpam.innerText;

    }
add(node: any): this
Code
add( node ) {

        let childrenDOM = this.childrenDOM;

        if ( this.childrenDOM === null ) {

            const dom = this.dom;

            const arrowDOM = document.createElement( 'f-arrow' );
            childrenDOM = document.createElement( 'f-treeview-children' );

            dom.append( arrowDOM );
            dom.append( childrenDOM );

            this.childrenDOM = childrenDOM;

        }

        this.children.push( node );
        childrenDOM.append( node.dom );

        node.parent = this;

        return this;

    }
setOpened(value: any): this
Code
setOpened( value ) {

        this.inputDOM.checked = value;

        return this;

    }
getOpened(): any
Code
getOpened() {

        return this.inputDOM.checkbox;

    }
setIcon(value: any): this
Code
setIcon( value ) {

        this.iconDOM = this.iconDOM || document.createElement( 'i' );
        this.iconDOM.className = value;

        if ( value ) this.labelDOM.prepend( this.iconDOM );
        else this.iconDOM.remove();

        return this;

    }
getIcon(): any
Code
getIcon() {

        return this.iconDOM ? this.iconDOM.className : null;

    }
setVisible(value: any): this
Code
setVisible( value ) {

        this.dom.style.display = value ? '' : 'none';

        return this;

    }
setSelected(value: any): this
Code
setSelected( value ) {

        if ( this.selected === value ) return this;

        if ( value ) this.dom.classList.add( 'selected' );
        else this.dom.classList.remove( 'selected' );

        this.selected = value;

        return this;

    }
onClick(callback: any): this
Code
onClick( callback ) {

        this.events.click.push( callback );

        return this;

    }

TreeViewInput

Class Code
class TreeViewInput extends Input {

    static get type() {

        return 'TreeViewInput';

    }

    constructor( options = [] ) {

        const dom = document.createElement( 'f-treeview' );
        super( dom );

        const childrenDOM = document.createElement( 'f-treeview-children' );
        dom.append( childrenDOM );

        dom.setAttribute( 'type', 'tree' );

        this.childrenDOM = childrenDOM;

        this.children = [];

    }

    add( node ) {

        this.children.push( node );
        this.childrenDOM.append( node.dom );

        return this;

    }

    serialize( data ) {

        //data.options = [ ...this.options ];

        super.serialize( data );

    }

    deserialize( data ) {

        /*const currentOptions = this.options;

        if ( currentOptions.length === 0 ) {

            this.setOptions( data.options );

        }*/

        super.deserialize( data );

    }

}

Methods

add(node: any): this
Code
add( node ) {

        this.children.push( node );
        this.childrenDOM.append( node.dom );

        return this;

    }
serialize(data: any): void
Code
serialize( data ) {

        //data.options = [ ...this.options ];

        super.serialize( data );

    }
deserialize(data: any): void
Code
deserialize( data ) {

        /*const currentOptions = this.options;

        if ( currentOptions.length === 0 ) {

            this.setOptions( data.options );

        }*/

        super.deserialize( data );

    }

Loader

Class Code
class Loader extends EventTarget {

    static get type() {

        return 'Loader';

    }

    constructor( parseType = Loader.DEFAULT ) {

        super();

        this.parseType = parseType;

        this.events = {
            'load': []
        };

    }

    setParseType( type ) {

        this.parseType = type;

        return this;

    }

    getParseType() {

        return this.parseType;

    }

    onLoad( callback ) {

        this.events.load.push( callback );

        return this;

    }

    async load( url, lib = {} ) {

        return await fetch( url )
            .then( response => response.json() )
            .then( result => {

                this.data = this.parse( result, lib );

                dispatchEventList( this.events.load, this );

                return this.data;

            } )
            .catch( err => {

                console.error( 'Loader:', err );

            } );

    }

    parse( json, lib = {} ) {

        json = this._parseObjects( json, lib );

        const parseType = this.parseType;

        if ( parseType === Loader.DEFAULT ) {

            const type = json.type;

            const flowClass = lib[ type ] ? lib[ type ] : ( LoaderLib[ type ] || Flow[ type ] );
            const flowObj = new flowClass();

            if ( flowObj.getSerializable() ) {

                flowObj.deserialize( json );

            }

            return flowObj;

        } else if ( parseType === Loader.OBJECTS ) {

            return json;

        }

    }

    _parseObjects( json, lib = {} ) {

        json = { ...json };

        const objects = {};

        for ( const id in json.objects ) {

            const obj = json.objects[ id ];
            obj.objects = objects;

            const type = obj.type;
            const flowClass = lib[ type ] ? lib[ type ] : ( LoaderLib[ type ] || Flow[ type ] );

            if ( ! flowClass ) {

                console.error( `Class "${ type }" not found!` );

            }

            objects[ id ] = new flowClass();
            objects[ id ].deserializeLib( json.objects[ id ], lib );

        }

        const ref = new Map();

        const deserializePass = ( prop = null ) => {

            for ( const id in json.objects ) {

                const newObject = objects[ id ];

                if ( ref.has( newObject ) === false && ( prop === null || newObject[ prop ] === true ) ) {

                    ref.set( newObject, true );

                    if ( newObject.getSerializable() ) {

                        newObject.deserialize( json.objects[ id ] );

                    }

                }

            }

        };

        deserializePass( 'isNode' );
        deserializePass( 'isElement' );
        deserializePass( 'isInput' );
        deserializePass();

        json.objects = objects;

        return json;

    }

}

Methods

setParseType(type: any): this
Code
setParseType( type ) {

        this.parseType = type;

        return this;

    }
getParseType(): string
Code
getParseType() {

        return this.parseType;

    }
onLoad(callback: any): this
Code
onLoad( callback ) {

        this.events.load.push( callback );

        return this;

    }
load(url: any, lib: {}): Promise<any>
Code
async load( url, lib = {} ) {

        return await fetch( url )
            .then( response => response.json() )
            .then( result => {

                this.data = this.parse( result, lib );

                dispatchEventList( this.events.load, this );

                return this.data;

            } )
            .catch( err => {

                console.error( 'Loader:', err );

            } );

    }
parse(json: any, lib: {}): any
Code
parse( json, lib = {} ) {

        json = this._parseObjects( json, lib );

        const parseType = this.parseType;

        if ( parseType === Loader.DEFAULT ) {

            const type = json.type;

            const flowClass = lib[ type ] ? lib[ type ] : ( LoaderLib[ type ] || Flow[ type ] );
            const flowObj = new flowClass();

            if ( flowObj.getSerializable() ) {

                flowObj.deserialize( json );

            }

            return flowObj;

        } else if ( parseType === Loader.OBJECTS ) {

            return json;

        }

    }
_parseObjects(json: any, lib: {}): any
Code
_parseObjects( json, lib = {} ) {

        json = { ...json };

        const objects = {};

        for ( const id in json.objects ) {

            const obj = json.objects[ id ];
            obj.objects = objects;

            const type = obj.type;
            const flowClass = lib[ type ] ? lib[ type ] : ( LoaderLib[ type ] || Flow[ type ] );

            if ( ! flowClass ) {

                console.error( `Class "${ type }" not found!` );

            }

            objects[ id ] = new flowClass();
            objects[ id ].deserializeLib( json.objects[ id ], lib );

        }

        const ref = new Map();

        const deserializePass = ( prop = null ) => {

            for ( const id in json.objects ) {

                const newObject = objects[ id ];

                if ( ref.has( newObject ) === false && ( prop === null || newObject[ prop ] === true ) ) {

                    ref.set( newObject, true );

                    if ( newObject.getSerializable() ) {

                        newObject.deserialize( json.objects[ id ] );

                    }

                }

            }

        };

        deserializePass( 'isNode' );
        deserializePass( 'isElement' );
        deserializePass( 'isInput' );
        deserializePass();

        json.objects = objects;

        return json;

    }