API Reference v0.3.0

Table of contents

  1. MetapageDefinition
  2. Metapage
    1. Metapage#dispose
    2. Metapage#getMetaframe
    3. Metapage#getIframes
    4. Metapage#getMetaframeIds
    5. Metapage#getMetaframes()
    6. Metapage#getPluginIds
    7. Metapage#getPlugins()
    8. Metapage#getState()
    9. Metapage#setState()
    10. Metapage#onState()
    11. Metapage#setDefinition()
    12. Metapage#getDefinition()
    13. Metapage#onInputs
    14. Metapage#onOutputs
    15. Metapage#removeAll
    16. Metapage#setInput/setInputs
    17. Metapage#setDefinition
    18. Metapage events
    19. Metapage.MetaframeClient
    20. Metapage.MetaframeClient#iframe
    21. Metapage.MetaframeClient#dispose
    22. Metapage.MetaframeClient#onInputs
    23. Metapage.MetaframeClient#onOutputs
  3. Metaframe
    1. Metaframe#id
    2. Metaframe#getInput
    3. Metaframe#getInputs
    4. Metaframe#getOutput
    5. Metaframe#getOutputs
    6. Metaframe#onInputs
    7. Metaframe#onInput
    8. Metaframe#setInput
    9. Metaframe#setOutput
    10. Metaframe#setOutput
    11. Metaframe#dispose
  4. Metapage plugins
    1. Implenting a metapage plugin

MetapageDefinition

The JSON description consists of metaframes and the metaframe inputs.

Example minimal metapage with two metaframes:

graph LR metaframe1 -- "output1 -> input1" --> metaframe2 metaframe2 -- "data-stream" --> metaframe1

Defined by:

{
    "metaframes": {
      "metaframe1": {
        "url": "https://metapages.org/metaframes/example00_iframe1/",
        "inputs": [
          {
            "metaframe": "metaframe2",
            "source"   : "output1",
            "target"   : "input1"
          }
        ]
      },
      "metaframe2": {
        "url": "https://metapages.org/metaframes/example00_iframe2/",
        "inputs": [
          {
            "metaframe": "metaframe1",
            "source"   : "data-stream"
          }
        ]
      }
    },
    "plugins": [
      "https://metapages.org/metaframes/example00_iframe2/"
    ]
}

The pipe entries of “inputs” are objects describing the source metaframe, source metaframe output pipe name, and the target metaframe (the owning metaframe) input pipe name

{
  "metaframe": "<sourceMetaframeId>",
  "source": "<sourceMetaframePipeName>",
  "target": "<thisMetaframePipeName>",
}

pipe.metaframe: source metaframeId

pipe.source: source metaframe output pipe name. Can be * or any glob, this will forward all output pipes matching the glob

pipe.target: (optional) target metaframe input pipe name. If target is omitted, then the input pipe name will be the same as the output pipe name.

graph LR metaframeSource[pipeInput.metaframe] -- "pipeInput.source -> pipeInput.target" --> targetMetaframe

version: This library hasn’t reached version "1" yet. Backwards compatibility is built into this library from it’s first release, and will be automated as much as possible. This is required to maintain long-term compatibility: metaframes should be useful forever, if that is possible from the content. Users should only need to upgrade for bug fixes, performance improvements, or to access new features, and there should never be cognitive load (no worries) about versioning for library users. Mistakes happen, no process is perfect, but this tool assumes the burden of handling all changes to versions over time.


Metapage

A metapage manages piping metaframe outputs to metaframe inputs.

Ways to initialize a metapage:

  1. Load a metapage.json dynamically and add the metaframes. If no loadCallback is given, metaframe iframes will automatically be added to the document element with the id that matches the metaframe id:
      Metapage.load(?metapage :<string|object>, ?loadCallback :func): Promise<Metapage>
    


    metapage: URL to metapage definition, defaults to “metapage.json”, or the metapage JSON object
    loadCallback: optional callback with two arguments (metaframeId, IFrameElement)

  2. Create a metapage from a MetapageDefinition JSON object. No metaframes are yet added to the page:
      const metapage = Metapage.from(def :MetapageDefinition, ?opts :Options): Metapage
      // updating the definition
      metapage.setDefinition(def :MetapageDefinition)
    

The optional opts argument defineds some extra options, mostly used for debugging:

interface Options {
  color?: String;  // Color of console.logs, since all iframes and the main page will output logs to the same console
  id?   : String;  // Id, not required, but useful if you have multiple metapages in a single website.
}

If the URL has the parameter MP_DEBUG then debug logging is enabled: https://domain.org/metapage1?MP_DEBUG

Metapage#dispose

metapage.dispose()

Removes all metaframes, window listeners, event listeners.

Metapage#getMetaframe

metapage.getMetaframe(metaframeId :String): MetaframeClient

Get the MetaframeClient for the given id.

Metapage#getIframes

metapage.getIframes(): Map<String, IFrameElement>

Returns a plain Object with metaframe ids mapped to the underlying metaframe iframes.

Metapage#getMetaframeIds

metapage.metaframeIds(): Array<String>

Returns an array of metaframe ids.

Metapage#getMetaframes()

metapage.getMetaframes(): Map<String, MetaframeClient>

Returns a plain Object with metaframe ids mapped to MetaframeClient objects.

Metapage#getPluginIds

metapage.getPluginIds(): Array<String>

Returns an array of plugin ids.

Metapage#getPlugins()

metapage.getPlugins(): Map<String, MetaframeClient>

Returns a plain Object with metaframe ids mapped to MetaframeClient objects.

Metapage#getState()

metapage.getState(): State

Where State looks like:

{
  "metaframes": {
    "inputs": {
      "metaframeId1": {
        "inputPipe1": true
      }
    }
  }

It represents the entire state of the metapage.

Metapage#setState()

metapage.setState(state :State)

This will update the entire state of the application and set all the metaframe inputs.

Metapage#onState()

metapage.onState(function(state :State) { ... });

The callback is called on every state change.

Metapage#setDefinition()

metapage.setDefinition(def :Definition)

Metapage#getDefinition()

var def :Definition = metapage.getDefinition();

Metapage#onInputs

metapage.getMetaframe(<metaframeId>).onInputs(function(inputs) {...}): function():

Callback called on every input update event. Example inputs payload:

{
  "inputPipeId1": "value"
}

An unbind function is returned.

The same thing can be called via the event:

metapage.getMetaframe(<metaframeId>).on('inputs', function(inputs) {...}): function():

You can also listen on the metaframe clients directly:

metapage.get(metaframeId).on('inputs', function(inputs) {...}): function():

Metapage#onOutputs

metapage.get(metaframeId).onOutputs(function(outputs) {...}): function():

Callback called on every output update event. Example outputs payload:

{
  "outputPipeId1": "value"
}

An unbind function is returned.

The same thing can be called via the event:

metapage.get(metaframeId).on('outputs', function(outputs) {...}): function():

Metapage#removeAll

metapage.removeAll()

Removes all metaframes, essentially resetting the metapage. It doesn’t remove window listeners though, for proper disposal call dispose().

Metapage#setInput/setInputs

Set metaframe inputs a variety of ways (there is no difference between setInput/setInputs calls).

metapage.setInput(metaframeId :String, inputPipeId :String, value :any)
metapage.setInput(metaframeId :String, inputsObject :any)
metapage.setInput(metaframeInputsObject :any)

inputsObject:

{
  "inputPipeName": "some value"
}

metaframeInputsObject:

{
  "metaframeId": {
    "inputPipeName": "some value"
  }
}

Metapage#setDefinition

Update the metapage definition.

This will destroy the current metaframes and plugins, and recreate new ones based on the new definition.

The

Metapage events

Metapage event allows you to add hooks to the data flow:

/**
 * Example update:
 * {
 *   "metaframes": {
 *     "inputs": {
 *        "metaframe1": {
 *          "input1": "foobar",
 *          "input2": 3
 *        }
 *     }
 *   }
 * }
 */
metapage.on('state', function(metapageState) { ...});

Listens to changes in the state for metaframes (and plugins). The listener is called on every discrete update of inputs and outputs.

/**
 * Example definition event:
 * {
 *   "definition": { <MetapageDefinition> },
 *   "metaframes": {
 *     "metaframe1": { "url": "https://metapages.org/metaframes/example1/" },
 *     "metaframe2": { "url": "https://metapages.org/metaframes/example2/" }
 *   },
 *   "plugins": [
 *     { "plugin1": "https://plugin1.io" },
 *     { "plugin2": "https://plugin2.io" }
 *   ]
 * }
 */
metapage.on('definition', function(definition) { ... });

metapage.setDefinition(def :MetapageDefinition); // Fires above event
metapage.getDefinition(def) :MetapageDefinition;

The definition can be updated, this will fire on every change and give the full definition.

It also returns the metaframe and plugin sets, with the objects needed to e.g. add the iframe objects to the DOM.

This is the main event you should listen to if your metapage gets updated.

Metapage.MetaframeClient

An internal object managing the data flow in and out of the actual metaframe iframe. You shouldn’t need to access this object directly. This object is in the metapage page, managing the metaframe data flow.

Metapage.MetaframeClient#iframe

The concrete metaframe iframe HTML element.

Metapage.MetaframeClient#dispose

metaframe.dispose()

Removes all event listeners and clears internal objects.

Metapage.MetaframeClient#onInputs

metapage.get(metaframeId).onInputs(function(inputs) {...}): function():

Callback called on every input update event. Example inputs payload:

{
  "inputPipeId1": "value"
}

An unbind function is returned.

The same thing can be called via the event:

metapage.get(metaframeId).on('inputs', function(inputs) {...}): function():

Metapage.MetaframeClient#onOutputs

metapage.get(metaframeId).onOutputs(function(outputs) {...}): function():

Callback called on every output update event. Example outputs payload:

{
  "outputPipeId1": "value"
}

An unbind function is returned.

The same thing can be called via the event:

metapage.get(metaframeId).on('outputs', function(outputs) {...}): function():

Metaframe

A metaframe is a website running the metaframe library that adds input+output data pipes to the page.

Metaframes optionally have a metaframe.json definition at the URL path root, defining e.g. name, and the inputs and outputs. The definition is not strictly needed, however is is very helpful. Some metaframes, such as plugins, do require the metaframe.json to declare what inputs/outputs are allowed.

You need to specify a version. All metaframe versions will be compatible with all metapage versions, however some new functionality will obviously not be available with using older versions.

{
	"version": "0.3",
	"metadata": {
		"title": "A button example",
		"author": "Dion Whitehead"
  },
  "inputs": {
		"input_name": {
			"type": "json"
		}
	},
	"outputs": {
		"output_name": {
			"type": "string"
		}
	}
}

Initialize a metaframe:

const metaframe = new Metaframe();

Wait until it has registered and established a connection with the parent metapage:

await metaframe.ready

or:

metaframe.ready.then(function(_) {
	//Do something, set inputs/outputs
});

Metaframe#id

let s :String = metaframe.id;

The id the metapage assigned to this metaframe. Defaults to the key in the metapage definition.

Metaframe#getInput

let inputValue = metaframe.getInput('inputName')

Metaframe#getInputs

let inputValues = metaframe.getInputs()

Get a object of input pipe names mapped to values.

Do not modify the returned object! For efficiency purposes this is not a copy.

Metaframe#getOutput

let output = metaframe.getOutput('outputName')

Metaframe#getOutputs

let outputs = metaframe.getOutputs()

Do not modify the returned object! For efficiency purposes this is not a copy.

Metaframe#onInputs

metaframe.onInputs(function(inputs) {...}): function():

Callback called on every input update event. Example inputs payload:

{
  "inputPipeId1": "value"
}

Do not modify the returned object! For efficiency purposes this is not a copy.

An unbind function is returned.

Metaframe#onInput

metaframe.onInput('inputName', function(value) {...}): function():

The callback will fire with the value of the input pipe on every update.

An unbind function is returned.

Metaframe#setInput

metaframe.setInput('inputName', value :any)

Metaframe#setOutput

metaframe.setOutput('outputName', value :any)

This value will be send to all downstream metaframe consumers.

Metaframe#setOutput

metaframe.setOutputs(values :Object)

values: object mapping output names to values.

These values will be send to all downstream metaframe consumers.

Metaframe#dispose

metaframe.dispose()

Removes window message listener, event listeners, and nulls potentially large fields.

Metapage plugins

Metapage plugins are metaframes that are not connected to the normal metaframes, instead they have hooks (via inputs) into:

  • metapage state changes (the metaframe inputs + outputs)
  • the metapage definition

Uses for the above include:

  • saving and restoring state changes
  • displaying representations of the definition (graph views, JSON editors)
  • others things currently outside my imagination

They are defined in the definition as unique URL to the plugin metaframe:

{
    "metaframes": {
      ...
    },
    "plugins": [
      "https://plugin1.io",
      "https://plugin2.io?param1=value1"
    ]
      
}

The order of the plugins is usually the order displayed in the app.metapages.org UI.

The plugin URLs must be unique (even though they are defined in a list) and are not allowed to collide with the metaframe ids.

Implenting a metapage plugin

Plugins declare which inputs they are allows to receive in the metaframe.json definition located at the base of the URL path. The outputs declare which values they will emit. All supported values are in this example plugin metaframe definition:

{
	"version": "0.3",
	"metadata": {
		"title": "My metapage plugin"
	},
	"inputs": {
		"metapage/state": {
			"type": "json"
    },
    "metapage/definition": {
			"type": "json"
		}
  },
  "outputs": {
		"metapage/state": {
			"type": "json"
    },
    "metapage/definition": {
			"type": "json"
		}
	}
}

##Plugin#API

The plugin field on the metapage object will be present if the metaframe is initialized as a plugin by the owning metapage:

metaframe.plugin

metapage/definition and metapage/state pipes may be listened to as per usual, or you may call the plugin methods:

metaframe.plugin.requestState(); // arrives via the next line
var disposeFunction = metaframe.plugin.onState(function(metapageState) { ... });

var metapageState = metaframe.plugin.getState();
metaframe.plugin.setState(metapageState);
var disposeFunction = metaframe.plugin.onDefinition(function(metapageDefinition) { ... });
metaframe.plugin.setDefinition(metapageDefinition);

##Plugin#input pipes

metapage/definition: this input will always have the most recent metapage definition. If the output is set, the metapage will update the definition. No action is needed to get this data.

metapage/state: this input gets the metapage state. If the output is set, the metapage will update the entire application state. The metapage/state is not sent automatically, it must be requested every time:

##Plugin#output pipes

metapage/state: The current metapage state will be replaced with this value.

metapage/definition: The current metapage definition will be replaced with this value.