npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

headless-json-editor

v0.34.0

Published

<p align="center"> <img src="./docs/he-full-blue-2000x564.png" width="100%" alt="headless-json-editor"><br /> <img src="./docs/title-700x54.png" width="350px" alt="headless-json-editor"> </p>

Downloads

498

Readme

install

yarn add headless-json-editor

Npm package version Types

quick overview

// stateful api - removes management of json-schema-draft and current state
import { HeadlessEditor } from 'headless-json-editor';

const jsonSchema = { type: 'array', items: { type: 'string' } };
const he = new HeadlessEditor({ schema: jsonSchema, data: ['first item'] });
let rootNode = he.getState();
rootNode = he.setValue('#/1', 124);

// doRender(rootNode);
{
  "id": "686f3a2e-6d99-4f32-bd16-1e965431fc52",
  "type": "array",
  "pointer": "#",
  "property": "#",
  "schema": {
    "type": "array",
    "items": {
      "type": "string"
    }
  },
  "options": {
    "disabled": false,
    "hidden": false
  },
  "children": [
    {
      "id": "29de15eb-cb22-44c3-bc73-74fde9ccec7f",
      "type": "string",
      "pointer": "#/0",
      "property": "0",
      "options": {
        "disabled": false,
        "hidden": false
      },
      "schema": {
        "type": "string"
      },
      "value": "first item",
      "errors": []
    },
    {
      "id": "20a3e4e5-2995-4065-8e2b-db380b6463ac",
      "type": "number",
      "pointer": "#/1",
      "property": "1",
      "options": {
        "disabled": false,
        "hidden": false
      },
      "schema": {
        "type": "string"
      },
      "value": 124,
      "errors": [
        {
          "type": "error",
          "name": "TypeError",
          "code": "type-error",
          "message": "Expected `124` (number) in `#/1` to be of type `string`",
          "data": {
            "received": "number",
            "expected": "string",
            "value": 124,
            "pointer": "#/1"
          }
        }
      ]
    }
  ],
  "errors": []
}

introduction

From your input data and json-schema, headless-json-editor creates a tree data-structure that reflects your input-data. Each node within this tree is a data-point containing its json-schema and either children (object or array «» parent node) or the property value (leaf node «» value node). Thus, each tree also contains your state of data. You can retrieve the data of a tree or node anytime using getData(node).

You may work on a node-tree using the functional api. Functions that modify a tree will always return a new tree to support immutable data. Just be aware that new trees are generated shallow so you should not edit nodes directly unless this is on purpose.

node

get current value of valueNode

const value = node.value;

get type of node

const nodeType: string = node.type;

get current json-pointer of node

const nodeLocation: string = node.pointer;

get current validation errors of node

const errors: JsonError[] = node.errors;

get options for current node

const options: Record<string, any> = node.options;

get children of parentNode

const value: Node[] = node.children;

get json-schema of value

const schema: JsonSchema = node.schema;

api

plugins | how to write plugins

stateful api removes management of json-schema-draft, current state and offers a simple interface for plugins

import { HeadlessEditor } from 'headless-json-editor';

const jsonSchema = { type: 'array', items: { type: 'string' } };
const he = new HeadlessEditor({ schema: jsonSchema, data: ['first item'] });
let rootNode = he.getState();
rootNode = he.setValue('#/1', 124);

render(rootNode);

functional helpers exposed with instance that change state

create a new node tree from the passed data

const newRootNode = he.create(data);

change value at a specific location in data

const newRootNode = he.setValue("#/pages/0/title", data);

remove value at specific location in data

const newRootNode = he.removeValue("#/pages/3");

move an array item to another index

const newRootNode = he.moveItem("#/pages/2", 0);

create and append an new item to an array using the passed json schema

const newRootNode = he.appendItem(arrayNode, schema);

get possible child schemas to add for given node

const jsonSchemas = he.getArrayAddOptions(node);

create data that validates against the current json-schema

const data = he.getTemplateData();

plugins

plugin-api is supported by stateful-api. Internally, each plugin may use the functional api to modify the current state, passing back a new state and the changes made to stateful-api.

OnChangePlugin

import { HeadlessEditor, OnChangePlugin } from 'headless-json-editor';

const jsonSchema = { type: 'array', items: { type: 'string' } };
const he = new HeadlessEditor({ schema: jsonSchema, data: ['first item'] });
he.addPlugin(OnChangePlugin, {
  onChange(data, rootNode, he) {
    console.log("data changed to", data);
  }
})

he.setValue('#/0', 124);
// log: [124]

HistoryPlugin

import { HeadlessEditor, HistoryPlugin } from 'headless-json-editor';

const jsonSchema = { type: 'array', items: { type: 'string' } };
const he = new HeadlessEditor({ schema: jsonSchema, data: ['first item'] });
const history = he.addPlugin(HistoryPlugin);
he.setValue('#/0', 124);

history.undo();
// log: ['first item']

// history.redo();
// history.getUndoCount();
// history.getRedoCount();

how to write plugins

plugins are function that return an object with a unique id and a method onEvent(Node, PluginEvent).

import { Plugin } from 'headless-json-editor';

export const MyPlugin: Plugin<{ myOption: string }> = (he, options) => {
    return {
        id: "my-plugin-id",
        onEvent(root, event) {
            console.log(options.myOption, event);
        }
    };
};

Witin onEvent you have access to all events omitted. You can hook into changes and modify the state. When chaning the state you have to pass back a list of changes to the editor for further processing:

import { Plugin, isChangeEvent } from 'headless-json-editor';

export const MyPlugin: Plugin<{ myOption: string }> = ({ draft}, options) => ({
    id: "set-option-title-to",
    onEvent(root, event) {
        if (!isChangeEvent(event) || event.node.schema["MyKey"] == null) {
            return undefined;
        }
        const node = event.node;
        const target = node.schema["MyKey"];
        const index = node.schema!.enum!.indexOf(node.value as string);
        if (index < 0) {
            return undefined;
        }
        const value = node.schema.options?.enum?.[index];
        const [newAST, changes] = setValue(draft, root, target, value);
        if (isJsonError(newAST)) {
            return undefined;
        }
        // return new root and list of changes
        return [newAST, changes ?? []];
    }
});

The last event, the done-event, does not accept node-changes. It lists all changes made with the final root node. Use this event to hook into completed update-cycles.

onEvent(root, event) {
    if (event.type === "done") {
        // do something
    }
}

Note, plugins are run sequentially to avoid circular updates. This also means plugin order matters. To break out of the update loop and trigger a fresh update-cycle use the editor's data-update methods, like setValue (asynchronously):

onEvent(root, event) {
    if (isChangeEvent(event)) {
        setTimeout(() => {
          he.setValue("#/1", 124);
        });
    }
}

functional api

create and change state | working with nodes

The functional api requires an instance of a json-schema draft to work with. This instance needs to be passed for all actions that change nodes. In the following example a modified draft JsonEditor is used. This draft offers some additions helpful to build user-forms. @see json-schema-library for more details

quick overview

import { JsonEditor } from 'json-schema-library';
import { createNode, setValue } from "headless-json-editor";

const jsonSchema = { type: 'array', items: { type: 'string' } };
const draft = new JsonEditor({ schema: jsonSchema, data: ['first item'] });
let rootNode = createNode(draft, ["9fa"])
rootNode = setValue(draft, rootNode, '#/1', 124);

// doRender(rootNode);

create and change state

transformation functions that modify state

import { Draft07 } from "json-schema-library";
const draft = new Draft07(jsonSchema);

create node tree from schema and data

const currentRootNode = createNode(draft, data);

move an array item by index

const [newRootNode, changes] = moveNode(draft, currentRootNode, "#/items", 2, 1);

delete data at location

const [newRootNode, changes] = removeNode(draft, currentRootNode, "#/page/header");

set value at location

const [newRootNode, changes] = setValue(draft, currentRootNode, "#/page/header", { text: "hey" });

working with nodes

get all errors

const validationErrors = errors(root);

find a node

const node = findNode(root, node => node.property === "this one");

get all nodes in a flat list

const allNodes = getNodeList(root);

get a specific node by its path

const target = getNode(root, "#/page/header");

get a childnode by property or index

const page = getChildNode(root, "page");

get json data starting from node

const jsonData = getData(root);

get all nodes along the path

const [rootNode, page, header, title] = getNodeTrace(root, "#/page/header/title");

exposed utilities

type guards

import { isJsonError, isParentNode, isValueNode, isChangeEvent } from "headless-json-editor";

change default error messages globally, @see settings;

import { setErrorMessages } from "headless-json-editor";

bundled library fast-equals to compare data

import { deepEqual } from "headless-json-editor";

Happy Building!