Create Custom Nodes

How to register custom nodes on different engines:

Blackprint is registered on window object

If you're using TypeScript, you can also use Blackprint.registerInterface and Blackprint.registerNode as a decorator for your class.

@Blackprint.registerNode("Nodes/...")
class YourNodeName extends Blackprint.Node { ... }

@Blackprint.registerInterface("BPIC/Nodes/...")
class YourIFaceName extends Blackprint.Interface { ... }

Blackprint.Sketch.registerInterface("BPIC/Nodes/...", YourIFaceName);

Register Interface/Node

This can be exact same with the Deno/Node.js version. On browser, if you want to register interface you must also register the Sketch Interface.

// Define your node structure and place your code to handle the interface here
// With compatibility in mind (can be imported for Browser/Node.js)
Blackprint.registerNode('Example/Nodes/Button',
class YourNodeName extends Blackprint.Node {
    // Define output ports for this node
    static output = {
        Clicked: Function // This will be connected to other node's input port if exist
    };
    
    // Define input ports for this node
    // static input = { ThePortName: DataTypeHere };

    constructor(instance){
        super(instance);
        
	// Let's use 'BPIC/Nodes/Button interface for this node
	let iface = this.setInterface('BPIC/Nodes/Button');
	iface.title = "My Button"; // Custom node title
    }

    // Triggered from 'BPIC/Nodes/Button' interface
    clicked(ev){
        console.log('Example/Nodes/Button: got', ev);
        node.outputs.Clicked(ev);
    }
});

// Registering Interface is optional
// If you think you don't need a custom interface
// Then using registerNode is enough

// Place your code that interacting with your library or Node.js API here
class YourIFaceName extends Blackprint.Interface {
    constructor(node){
        super(node);
        this.yourCustomProperty = '...';
    }
    
    clicked(ev){
        console.log("Engine: 'Trigger' button clicked, going to run the handler");

        // Event route: iface.clicked -> node.clicked -> node.outputs.Clicked
        this.node.clicked && this.node.clicked(ev);
    }
};
Blackprint.registerInterface('BPIC/Nodes/Button', YourIFaceName);

// Register custom HTML interface for sketch
Blackprint.Sketch.Interface('BPIC/Nodes/Button', {
    html: `
    <div class="node {{ type || 'general' }}" style="transform: translate({{ x }}px, {{ y }}px)">
      <sf-template path="Blackprint/nodes/template/header.sf"></sf-template>

      <div class="content">
        <div class="left-port">
          <sf-template path="Blackprint/nodes/template/input-port.sf"></sf-template>
        </div>
	
        <div style="display: inline-block; color: yellow">
          {{ yourCustomProperty }}
        </div>
    
        <div class="right-port">
          <sf-template path="Blackprint/nodes/template/output-port.sf"></sf-template>
        </div>
      </div>
    </div>`
}, YourIFaceName);

// --- How to use it ---
var sketch = new Blackprint.Sketch();

let button = sketch.createNode('Example/Nodes/Button', {x: 100, y: 200});
// button is instance of (class YourIFaceName extends Blackprint.Interface)

button.clicked("'An event'"); // == iface.clicked(...)

// --- Console output ---
//> Engine: 'Trigger' button clicked, going to run the handler
//> Example/Nodes/SimpleButton: got 'An event'Blackprint is registered on window object

Before we get started, it would be better if you understand how we will implement the nodes.

There are 2 type of registration:

Register

Information

Role

Node

Data and type assignment

Will be used like an object to store and obtaining data from IFace

Interface/IFace

System/Browser interface

Will be used for User Interface on the browser or interface for interacting with System/Library/Browser API.

Below is the flow visualization and description:

Blackprint Engine will load JSON and create every Node from it by using the registered node (registerNode) in the engine. The created Node will just act like an object for storing data, validating, or do some data processing. The Node will search for it's interface assigned in iface.interface that was registered (registerInterface) on the engine, if it was undefined the default node interface will be used.

The IFace will bind itself with the Node, and both of them can interact from the object reference. Blackprint Engine will handle the data flow for every Node, and the custom IFace will handle the data events from both System or Node's data changes.

Maybe it's better to try with some example or experimenting if you still confused.

Alright, let's clone these repository.

$ git clone --depth 1 https://github.com/Blackprint/Blackprint.git .

# Create symbolic link to Blackprint's dist folder
# (Windows: may need administrator privileges)
$ mklink /D ".\example\dist" "..\dist" # For Windows

# For linux
$ ln -s dist ./example

# Install the dependency from ./Blackprint/package.json
$ npm i

Starting the server and the compiler

$ npm start

# The compiled engine-js and nodes will be placed on ./dist folder
# You will also need to add your new custom node's .js, .css on /example/public/index.html

Modifying the code

You can modify /gulpfile.js for customize the compiler. Currently I'm figuring a better way to make the development more easier, so this may be changed.

The /example/public folder have default index.html for getting started, and your css and js should be written into /example/src or /src folder and your browser should being refreshed automatically.

The compiler already have file timestamp versioning to avoid browser cache, so you don't need to press CTRL+F5 every time you modify your code in /src.

Compiling the code

The compilation process will minify your code and also run Babel transpiler to support low end browser.

$ npm run compile

Last updated