API Reference v0.4.2

previous versions


Table of contents

  1. MetapageDefinition
  2. Metapage
    1. Metapage#setDebugFromUrlParams
    2. Metapage#dispose
    3. Metapage#getMetaframe
    4. Metapage#getIframes
    5. Metapage#getMetaframeIds
    6. Metapage#getMetaframes()
    7. Metapage#getPluginIds
    8. Metapage#getPlugins()
    9. Metapage#getState()
    10. Metapage#setState()
    11. Metapage#onState()
    12. Metapage#setDefinition()
    13. Metapage#getDefinition()
    14. Metapage#onInputs
    15. Metapage#onOutputs
    16. Metapage#removeAll
    17. Metapage#setInput/setInputs
    18. Metapage#setDefinition
    19. Metapage events
    20. Metapage#STATE
    21. Metapage#DEFINITION
    22. Metapage#ERROR
    23. Metapage.MetaframeClient
    24. Metapage.MetaframeClient#url
    25. Metapage.MetaframeClient#iframe
    26. Metapage.MetaframeClient#dispose
    27. Metapage.MetaframeClient#onInputs
    28. 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. Plugins
    1. Implenting a metapage plugin
    2. Metaframe#plugin
    3. Metaframe#plugin.requestState
    4. Metaframe#plugin.onState
    5. Metaframe#plugin.setState
    6. Metaframe#plugin.onDefinition
    7. Metaframe#plugin.setDefinition
    8. Metaframe#plugin.getDefinition
    9. Metaframe#plugin pipes

MetapageDefinition

The JSON description consists of metaframes and the metaframe inputs.

Example minimal metapage with two metaframes:

graph LR metaframe1["random-data-generator"] -- "y -> y" --> metaframe2["graph-dynamic"]

Defined by:

{
  "version": "0.3",
  "meta": {
    "layouts": {
      "flexboxgrid" : {
        "docs": "http://flexboxgrid.com/",
        "layout": [
          [ {"name":"random-data-generator", "width":"col-xs-4", "style": {"maxHeight":"600px"}}, {"url":"https://metapages.org/metaframes/passthrough-arrow/?rotation=90", "width":"col-xs-1"}, {"name":"graph-dynamic", "width":"col-xs-7"}  ]
        ]
      }
    }
  },
  "metaframes": {
    "random-data-generator": {
      "url": "https://metapages.org/metaframes/random-data-generator/?frequency=1000"
    },
    "graph-dynamic": {
      "url": "https://metapages.org/metaframes/graph-dynamic/",
      "inputs": [
        {
          "metaframe": "random-data-generator",
          "source": "y",
          "target": "y"
        }
      ]
    }
  },
  "plugins": [
    "https://metapages.org/metaframes/mermaid.js/"
  ]
}

Run above example

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.
}

Metapage#setDebugFromUrlParams

metapage.setDebugFromUrlParams()

If the URL has one of the parameters MP_DEBUG, mp_debug, DEBUG, debug, then debug logging is enabled, e.g.: https://domain.org/metapage1?debug.

The metapage and metaframes will output coloured console logs (the color is randomly but deterministically computed from the respective id).

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#STATE

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) { ...});
metapage.on(Metapage.STATE, function(definition) { ... });

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

Metapage#DEFINITION

/**
 * 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.on(Metapage.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#ERROR

metapage.on('error', function(definition) { ... });
metapage.on(Metapage.ERROR, 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.

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#url

// The URL of the underlying iframe
const url :string = metapage.getMetaframe(id).url;

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.

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"
		}
	}
}

Metaframe#plugin

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

Metaframe#plugin.onState

Requests the current state from the metapage.

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

Metaframe#plugin.setState

metaframe.plugin.setState(metapageState);

Metaframe#plugin.onDefinition

Metaframe#plugin.setDefinition

Metaframe#plugin.getDefinition

var disposeFunction = metaframe.plugin.onDefinition(function(metapageDefinition) { ... });
metaframe.plugin.setDefinition(metapageDefinition);
var definition = metaframe.plugin.getDefinition);

Metaframe#plugin 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 has 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: