Git Product home page Git Product logo

firjs's Introduction

FirJs

⚠ī¸ Note: This project is in a state of heavy development, please don't be offended if some things might change between releases.

Demo 🚀ī¸

Vanilla JS https://sedax90.github.io/firjs-github-io/

About this project ℹī¸

I state that this project is freely inspired by the amazing Sequential Workflow Designer: https://github.com/nocode-js/sequential-workflow-designer

FirJs must not be intended as a ready to use project but as a simple, generic, dependencies free library for drawing workflows (the only dependencies you will find are for compiling and running the demo).

This library is written entirely in Typescript and was born to remain simple and as agnostic as possible, so that it can be easily integrated into projects where needed.

How to install đŸĨ‘ī¸

Vanilla JS npm i @sedax90/firjs
Angular npm i @sedax90/ngx-firjs

Concepts 💡ī¸

The main area is your Workspace. A workspace contains a Workflow, who is simply a nodes tree.

FirJs provides these basic nodes:

  • task (a generic operation)
  • map (for/while)
  • choice (if/switch)
  • terminator (indicates that the flow ended before its natural end)

The workflow tree has a basic hierarchical structure:

[
  {
    id: "123",
    type: "task",
    label: "Foo", // Optional
    icon: "star", // Optional
  },
  {
    id: "124",
    type: "map",
    props: {
      children: [],
    },
  },
];

You can attach all the data you need in the props field, they will always be kept.

Getting started đŸ”Ĩī¸

Import the library and create a simple JS object:

firjs.init({
    parent: document.getElementById('root'),
    tree: [],
}).then((ws) => { // Enjoy! });

The library does not do anything automatically (except node removal), you have to use the exposed functions and events to adapt the library to your needs:

onNodeAdd: (e: NodeAddeEvent) => void;
onNodeMove: (e: NodeMoveEvent) => void;
onNodeSelect: (e: NodeSelectEvent) => void;
onNodeDeselect: (e: NodeDeselectEvent) => void;
onNodeHover: (e: NodeHoverEvent) => void;
onNodeLeave: (e: NodeLeaveEvent) => void;
onNodeRemove: (e: NodeRemoveEvent) => void;
onTreeChange: (e: TreeChangeEvent) => void;
onWorkflowPan: (e: WorkflowPanEvent) => void;
onWorkflowScale: (e: WorkflowScaleEvent) => void;
onFlowModeChange: (e: FlowModeChangeEvent) => void;

You are not required to initialize event responses when creating the workflow as events are emitted as native Javascript events from the Workspace, so you can subscribe to them simply with:

ws.addEventListener("nodeAdd", (event) => {
  // Your logic
});

FirJS emits these events:

  • nodeAdd
  • nodeMove
  • nodeSelect
  • nodeDeselect
  • nodeRemove
  • nodeHover
  • nodeLeave
  • treeChange
  • workflowPan
  • workflowScale
  • flowModeChange

If you want to implement some logics during node drop or node remove, you can implement:

// This is triggered whenever you move a node over a placeholder and allows you to override whether a node can be added to that placeholder or not (you can also add a custom message for the user if you want).
canDropNode: (e) =>
  Promise<{
    allowed: boolean;
    label?: string;
  }>;

// This is triggered when you release a node over a placeholder. It's the last chance for you to implement a custom logic for allow/disallow node attachment.
canAttachNode: (e) => Promise<boolean>;

// This is triggered before removing a node (with context menu or Del button).
canRemoveNode: (e) => Promise<boolean>;

// This is triggered before selecting a node.
canSelectNode: (e) => Promise<boolean>;

// This is triggered before deselecting a node.
canDeselectNode: (e) => Promise<boolean>;

// This is triggered before rendering a node. You can return a boolean indicating that node has an error (an `has-error` class will be applied to the node).
hasError: (e) => Promise<boolean>;

When you implement your drag&drop logic, you have to inform the Workspace that you are going to drop a node on the tree:

event.dataTransfer.setData("text/plain", JSON.stringify(node));

ws.startDrag(
  event.target,
  {
    x: event.pageX,
    y: event.pageY,
  },
  {
    id: (i++).toString(),
    type: element.dataset.type,
    label: `New node ${i}`, // Optional
  }
);

After all, you can pass some options to init for customize some data:

flowMode: "vertical" | "horizontal", // Default to 'vertical'
style: {
  fontSize: string;
  fontFamily: string;
},
strings: {
  "context-menu.component.actions.remove.label": string,
  "context-menu.workspace.actions.fitandcenter.label": string,
  "placeholder.not-allowed-to-drop.label": string,
},
infinite: boolean;
events: {
    emitSelectedOnContextMenu: boolean;
}

Other useful functions:

  • fitAndCenter(): void: Fit and center the tree (omg really?)
  • setTree(tree: Node[], preservePositionAndScale: boolean = false): void: Set the tree programmatically.
  • async draw(): Promise<void>: Force a redraw without passing a new tree.
  • getFlowMode(): FlowMode: Get the current flow mode
  • setFlowMode(mode: FlowMode): void: Set a new flow flow mode

Customizations 🎨ī¸

Advanced customizations

FirJs provide this methods to override your data:

// to override your label.
overrideLabel(node: Node): Promise<string>;

// to assign a dynamic icon. If you return a string an <image> tag will be created with url as your string.
overrideIcon(node: Node): Promise<string>;

// to assign a label for each choice column.
overrideColumnLabel(node: Node, parent: Node | null, columnIndex: number): Promise<string | HtmlElement | SvgElement>;

/**
 * It allows you to override the view of a node type however you like.
 * To help you with the more complex views you can take advantage of the CreationHelper utility class.
 */
overrideView: {
    task?: (creationContext: TaskViewCreationContext, workspaceContext: Context) => Promise<ComponentView | null>;
    choice?: (creationContext: ChoiceViewCreationContext, workspaceContext: Context) => Promise<ComponentView | null>;
    map?: (creationContext: MapViewCreationContext, workspaceContext: Context) => Promise<ComponentView | null>;
    terminator?: (creationContext: TerminatorViewCreationContext, workspaceContext: Context) => Promise<ComponentView | null>;
};

/**
 * It allows you to override the public methods behavior (normally used combined with overrideView).
 */
overrideComponentMethods: {
    task?: PublicComponentInstanceOverridableFns,
    choice?: PublicComponentInstanceOverridableFns,
    map?: PublicComponentInstanceOverridableFns,
    terminator?: PublicComponentInstanceOverridableFns
}

All of this functions are executed when a node is drawed.

Style customizations

You can customize the entire Workspace with only a little bit of CSS. You will find that there are many implemented CSS variables that you can simply override.

Workspace tips ✨ī¸

  • Grab an empty point in the workspace with your left mouse button and move it to shift the workflow.
  • If you want to move the workflow from any position, before to click press and hold the Ctrl or the Space button.
  • Simply zoom with your mouse wheel.
  • If you are dragging a node and do you want to cancel the drop operation, simply press Esc.
  • Right click on a component or on workspace to open the contextual menu with some useful actions.

Next steps 🗓ī¸

  • Add tests.
  • Angular module.
  • React component.
  • Better event management.
  • More customizations.
  • Better mobile.
  • Horizontal mode

How to use this repo 🛠ī¸

Install the required dev dependencies with: npm i.

Run demo while compiling (dev mode)

npm run start

Enjoy!

Run demo only

npm run run-demo

License

This project is released under the MIT license.

firjs's People

Contributors

sedax90 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

Forkers

24bridgemoon

firjs's Issues

onNodeDeselect not called

When a node A is selected, if another node B is selected "onNodeDeselect" is not called for node A
Safari and Chrome on Mac

To reproduce, please use the provided demo with any node:

1 Select a node A
2 "onNodeSelect" is called for node A
3 Select a node B (without deselecting node A)
4 "onNodeSelect" is called for node B
5 No "onNodeDeselect" is called for node A

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤ī¸ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.