Skip to content
Andy Clement edited this page Jun 3, 2016 · 7 revisions

Welcome to the Spring Flo wiki!

Spring Flo is a set of Angular JS directives for a diagram editor able to represent a DSL graphically and synchronize graphical and textual representation of that DSL. Graphical representation is done with a Joint JS graph object, textual representation can be either a plain HTML element (such as <textarea>) or a feature rich editor based on CodeMirror available via the Flo directive.

How to embed Spring Flo in your application:

Spring Flo can be used in any angular application via the flo-editor directive. The controller for the directive creates/populates flo (Flo) and definition (Definition) objects. These objects are how a consuming client interoperates with Flo. The definition object is a two-way binding to read/write the DSL textual representation. The flo object is a two-way binding for working with the graphical representation of the DSL (including controlling editor features such as zooming, grid, layout etc.).

There is a sample project in the samples subfolder of the spring-flo repo: https://github.com/spring-projects/spring-flo/tree/master/samples/spring-flo-sample - this sample will be referenced throughout the documentation below to give a pointer to a real use of each feature.

The initial step of embedding Flo into angular application is simply to use the flo-editor directive on a page or in angular template.

<flo-editor></flo-editor>

The above results in a page with a skeleton for the editor: empty palette and empty canvas. See the sample usage for a more sophisticated configuration. Flo defines UI for the editor and graph interactions but needs domain knowledge from the client to be actually useful. There are actually three services the consuming client can provide to control the behaviour of Flo, only one of which is mandatory:

  1. Service that defines the types of element being represented by the graph and converts both ways between the domain DSL textual format and the graph format (required)
  2. Service that defines the visual representation of the nodes and links on the graph
  3. Service that controls graph editing capabilities

Implementations should be provided by the client as angular services that adhere to a specific contract as detailed in Extension Points. The services are: Metamodel Service (required), Render Service and Editor Service.

Many of the service functions have sensible defaults. In this section we will discuss the important ones, only some of which are mandatory.

In order to populate the palette with content, Flo needs to know about your domain. What are the 'things' you are linking together in your graph? Implementing the Metamodel Service load() function will provide that information to Flo.

app.factory('MyPageMetamodelService', ['$log', function($log) {
  return {
    load: function() { $log.info('Return descriptions of the domain specific model elements'); }
  };
}]);

The sample includes a more complete metamodel service. Once defined, the services can be supplied by name to the parents scope (via parent controller $scope.metamodelServiceName = 'MyPageMetamodelService';.) or directly to the flo-editor directive (via metamodel-service-name attribute on <flo-editor> element). Lets modify the flo-editor directive:

<flo-editor metamodel-service-name="MyPageMetamodelService">
</flo-editor>

This is also how the sample index.html does it. Once the Metamodel Service is hooked up to the editor the palette will be populated based on the metamodel object obtained from the Metamodel Service. You should then be able to drop nodes on the canvas, connect nodes with links, move them around. Note that visual representation of nodes and links might be far from what is desired. Implementing a Render Service enables you to customize that look and feel (in the absence of a provided render service, Flo performs some very basic rendering). A basic render service will implement the createNode() and createLink() functions (see also Joint JS API Docs) to get the right look and feel for the shapes on the palette and canvas.

app.factory('MyPageRenderService', ['$log', function($log) {
  return {
    createNode: function(metadata) { $log.info('Create and return new Joint JS joint.dia.Element for a graph node'); },
    createLink: function(source, target, metadata, props) { $log.info('Create and return new Joint JS joint.dia.Link for a link between graph nodes'); }
  };
}]);

The sample includes a more complete render service. As with the metamodel service, connect it to the flo-editor directive

<flo-editor
  metamodel-service-name="MyPageMetamodelService" 
  render-service-name="MyPageRenderService">
</flo-editor>

With basic rendering taken care of, we could further customize:

  • Adding "handles" around the shape when it is selected. "handles" are small shapes usually placed around the shape interaction with which would perform some editing actions on the associated shape such as delete, resize or edit properties.
  • Provide validation of the graph with validation indicators (errors/warnings) on nodes and links.
  • An editor action toolbar might be nice.

Some of these features are supported by implementing more functions on the render-service but some require implementing the final key service, the Editor Service:

app.factory('MyPageEditorService', ['$log', function($log) {
  return {
    createHandles: function(flo, createHandle, selected) { $log.info('Create handles around selected element'); },
    validateLink: function(flo, cellViewS, portS, cellViewT, portT, end, linkView) { $log.info('Validate link');},
    validateNode: function(flo, node) { $log.info('Validate Node'); },
    preDelete: function(flo, deletedElement) { $log.info('Delete links incident with element being deleted '); },
    interactive: true, /* Consult Joint JS Paper #interactive property doc. Make elements interactive */
    allowLinkVertexEdit: false /* Disallow creation and editing of 'bend-points' in links */
  };
}]);

The sample includes a more complete editor service. Rendering for handles and validation markers (decorations) must be provided by the Render Service since they are required by Editor Service. There is no out-of-the-box rendering defined in Spring-Flo for rendering handles and error marker decoration shapes. Consequently, if you define validateNode() and createHandles() in the Editor Service then createDecoration() and createHandle must be implemented in the Render Service.

app.factory('MyPageRenderService', ['$log', function($log) {
  return {
    createHandle: function(kind, parent) { $log.info('Implement handle node creation'); },
    createDecoration: function(kind, parent) { $log.info('Implement error decoration node creation'); },
    createNode: function(metadata) { $log.info('Create and return new Joint JS joint.dia.Element for a graph node'); },
    createLink: function(source, target, metadata, props) { $log.info('Create and return new Joint JS joint.dia.Link for a link between graph nodes'); }
  };
}]);

New toolbar actions can be added in HTML in the flo-editor directive via transclusion, this is how that might look with our editor service also plugged in:

<flo-editor
  metamodel-service-name="MyPageMetamodelService" 
  render-service-name="MyPageRenderService"
  editor-service-name="MyPageEditorService">
  <div>
    <button ng-click="flo.deleteSelectedNode()" ng-disabled="!flo.getSelection()">Delete</button>
    <button ng-click="flo.noPalette = !flo.noPalette" ng-class="{on:!flo.noPalette}">Palette</button>
  </div>
</flo-editor>

Note the flo object references in various handler attributes in HTML. The controller for flo-editor directive will populate flo object with functions for manipulating the Flo editor content.

The graph editor should be fully functional at this stage, but what about the text DSL?

Functions converting graph to text DSL and text DSL to graph must be provided by the client in order for this to work. These two function (graphToText() and textToGraph()) must be implemented inside the Metamodel Service, let's add them to the definition we had earlier:

app.factory('MyPageMetamodelService', ['$log', function($log) {
  return {
    load: function() { $log.info('Implement load and return metamodel object!'); },
    textToGraph: function(flo, definition) { $log.info('Convert text (definition.text) to graph (flo.getGraph())'); },
    graphToText: function(flo, definition) { $log.info('Convert graph (flo.getGraph()) to text (definition.text)'); }
  };
}]);

The sample includes basic implementations of graph-to-text and text-to-graph for a 'demo' DSL, those are used by the sample metamodel service. The Flo editor internally maintains the text DSL in the definition object it only remains to be exposed in the UI. The text DSL can be displayed either by plain HTML elements such as <textarea> or advanced CodeMirror based dsl-editor directive. Lets modify the HTML to have the text DSL UI with help from the dsl-editor directive.

<flo-editor
  metamodel-service-name="MyPageMetamodelService" 
  render-service-name="MyPageRenderService"
  editor-service-name="MyPageEditorService">
  <div>
    <button ng-click="flo.deleteSelectedNode()" ng-disabled="!flo.getSelection()">Delete</button>
    <button ng-click="flo.noPalette = !flo.noPalette" ng-class="{on:!flo.noPalette}">Palette</button>
  </div>
  <textarea dsl-editor></textarea>
</flo-editor>

The HTML above translates into a page with toolbar for buttons (Layout and Show/Hide Palette), text area for DSL and the Flo editor for graph representation of the DSL.

All the extension points:

Metamodel Service

This service enables the domain in which Flo is being used to specify what kinds of element are being connected together in the graph and also how the graph should be converted to-and-from a textual representation. Sample metamodel service is here.

textToGraph(flo, definition)

Sets the graph contents for the flo object based on the textual representation of the DSL from definition object. Text is transformed into the corresponding Joint JS graph content. The graph is to be populated via flo objects functions such as flo.createLink() and flo.createNode() and cleared with flo.clearGraph

graphToText(flo, definition)

Convert the current graph available from the flo object into a textual representation which is then set (as the text property) on the definition object.

load()

Returns a promise that resolves to a metamodel object. The metamodel object layout is a map of element group names to a map of elements that belong to this group. The map of elements that belong to the group is a mapping between element's name and element's Metadata Object

refresh() (Optional)

Refreshes the meta-model and returns a promise that is resolved to the same result as load(). Refresh should also fire event to metamodel change listeners.

encodeTextToDSL(text) (Optional)

Encodes DSL element property value text to the DSL required format. Example is converting multiline text into a single line required by the DSL format. Used to display the property value in a human readable format.

decodeTextFromDSL(dsl) (Optional)

Decodes DSL element property value text from DSL format. Example is converting single line text into a multiline text, i.e. replacing escaped line breaks. Used to set a property value for DSL element entered by the user via UI.

subscribe(listener) (Optional)

Adds a listener to metamodel events. (See Metamodel Listener)

unsubscribe(listener) (Optional)

Removes metamodel events listener. (See Metamodel Listener)

isValidPropertyValue(element, key, value) (Optional)

Check if the the value being specified for the key on the specified element is allowed. For example: if the key takes an integer, don't allow alphabetic characters.

Render Service

The service is responsible for visual representation of graph elements based on the metadata (coming from Metamodel Service). This service is optional. Sample render service is here.

createNode(metadata, properties) (Optional)

Creates an instance of Joint JS graph node model object (joint.dia.Element). Parameters that may affect the kind of node model object are element's metadata and map of properties (if any passed in).

createLink(source, target, metadata, properties) (Optional)

Creates an instance of Joint JS graph link model object (joint.dia.Link). Parameters that may affect the kind of link model object are element's metadata, map of properties (if any passed in), source and target elements

createHandle(kind, parent) (Optional)

Creates an instance of Joint JS graph node model object (joint.dia.Element). An example of a handle is a shape shown next to the parent shape interacting with which results in some editing action over the parent shape. Parameters that may affect the kind of handle model object are kind of type string (user defined, i.e. delete, resize, etc.) and handle's parent element. This function is only called by the framework if Editor Service createHandles() function is implemented.

createDecoration(kind, parent) (Optional)

Creates an instance of Joint JS graph node model object (joint.dia.Element). An example of decoration is a validation marker displayed over the parent shape. Parameters that may affect the kind of decoration model object are kind of type string and decoration's parent element. Note that kind parameter is coming from the framework (unlike for createHandle function). This function is only called by the framework if Editor Service validateNode() function is implemented. (At the moment decorations are only the validation error markers).

initializeNewNode(node, context) (Optional)

Performs any additional initialization of a newly created graph node when node is already added to the Joint JS graph and rendered on the canvas, e.g. element's SVG DOM structure is available. The context parameter is an object with paper and graph properties applicable for the node. Useful to perform any kind of initialization on a node when it's SVG DOM is appended to the page DOM. Examples: fit string label inside a shape, use angular directive on a shape, add DOM listeners etc.

initializeNewLink(link, context) (Optional)

Performs any additional initialization of a newly created graph link when link is already added to the Joint JS graph and rendered on the canvas, e.g. element's SVG DOM structure is available. The context parameter is an object with paper and graph properties applicable for the link. Useful to perform any kind of initialization on a link when it's SVG DOM is appended to the page DOM. Examples: use angular directive on a shape, add DOM listeners etc.

initializeNewHandle(handle, context) (Optional)

Performs any additional initialization of a newly created graph handle when handle is already added to the Joint JS graph and rendered on the canvas, e.g. element's SVG DOM structure is available. The context parameter is an object with paper and graph properties applicable for the handle. Useful to perform any kind of initialization on a handle shape when it's SVG DOM is appended to the page DOM. Examples: fit string label inside a shape, use angular directive on a shape, add DOM listeners etc.

initializeNewDecoration(decoration, context) (Optional)

Performs any additional initialization of a newly created graph decoration when decoration is already added to the Joint JS graph and rendered on the canvas, e.g. element's SVG DOM structure is available. The context parameter is an object with paper and graph properties applicable for the decoration. Useful to perform any kind of initialization on a decoration shape when it's SVG DOM is appended to the page DOM. Examples: fit string label inside a shape, use angular directive on a shape, add DOM listeners etc.

getNodeView() (Optional)

Returns instance of joint.dia.ElementView. It can also be a function of the form function(element) that takes an element model and should return an object responsible for rendering that model onto the screen. Under normal circumstances this function does not need to be implemented and the Joint JS view object created by the framework should be enough. Implement this function if different nodes require different Joint Js views or view has some special rendering (i.e. embedded HTML elements). See Joint JS Paper Options

getLinkView() (Optional)

Returns instance of Joint JS joint.dia.LinkView. Default is joint.dia.LinkView. It can also be a function of the form function(link) that takes a link model and should return an object responsible for rendering that model onto the screen. Under normal circumstances this function does not need to be implemented and the Joint JS view object created by the framework should be enough. Implement this function if different links require different Joint JS views or view has some special rendering (i.e. pattern applied to a line - joint.shapes.flo.PatternLinkView). See Joint JS Paper Options

layout(paper) (Optional)

Responsible for laying out the Joint JS graph that can be derived from passed in paper parameter (paper.model).

handleLinkEvent(paper, event, link) (Optional)

Responsible for handling event that occurred on the link that belong to passed in Joint JS paper object. The event parameter is a string with possible values: 'add', 'remove' or Joint JS native link change events such as 'change:source', 'change:target', etc. see Joint JS Link Events

isSemanticProperty(propertyPath, element) (Optional)

Returns true for string property attribute path propertyPath on an element if graphs needs to perform some visual update based on propertyPath value change (Not needed for properties under props on an element). Visual update is performed by refreshVisuals(). The property path propertyPath is relative to Joint JS element attrs property

refreshVisuals(element, propertyPath, paper) (Optional)

Performs some visual update of the graph or, which is more likely, the passed in element displayed on Joint JS paper based on the changed property specified by propertyPath

getLinkAnchorPoint(linkView, view, port, reference) (Optional)

This function allows you to customize what are the anchor points of links. The function must return a point (with x and y properties) where the link anchors to the element. The function takes the link view, element view, the port (SVG element) the link should stick to and a reference point (either the closest vertex or the anchor point on the other side of the link).

Editor Service

The service responsible for providing Flo editor with rich editing capabilities such as handles around selected shapes, custom drag and drop behaviour, live and static validation. This service is optional. Sample editor service is here

createHandles(flo, createHandle, selected) (Optional)

Called when node is selected and handles can be displayed. Handles are usually small shapes around the selected Joint JS node in flo editor interactions with which modify properties on selected node, i.e. resize or delete handles. Call createHandle(selected, kind, clickHandlerFunction, coordinate) function to create a handle. The kind parameter is a string kind of a handle, clickHandlerFunction is performed when handle has been clicked on and coordinate is the place to put the handle shape. Note that if this function is implemented then Render Service createHandle(...) function must be implemented as well. The framework will remove handles automatically when needed, hence no need to worry about this on the client side.

validatePort(paper, view, portView) (Optional)

Decide whether to create a link if the user clicks a port. The portView is the DOM element representing the port, view is the port's parent Joint JS view object show in Joint JS paper

validateLink(flo, cellViewS, portS, cellViewT, portT, end, linkView) (Optional)

Decide whether to allow or disallow a connection between the source view/port (cellViewS/portS) and target view/port (cellViewT/portT). The end is either 'source' or 'target' and tells which end of the link is being dragged. This is useful for defining whether, for example, a link starting in a port POut of element A can lead to a port PIn of elmement B.

calculateDragDescriptor(flo, draggedView, targetUnderMouse, coordinate, context) (Optional)

Called when dragging of a node draggedView is in progress over targetUnderMouse Joint JS graph element (node or link) at coordinate. There are also flo object parameter and context object, which currently just has a boolean property palette to denote whether drag and drop occurring on the palette or canvas. The function should return a Drag Descriptor Object.

handleNodeDropping(flo, dragDescriptor) (Optional)

Performs necessary graph manipulations when the node being dragged is dropped. The dragDescriptor Drag Descriptor should have the mandatory information on what is being dragged and where it's being dropped. The flo object parameter would help to make necessary graph modifications

showDragFeedback(flo, dragDescriptor) (Optional)

Any custom visual feedback when dragging a node over some graph element (node or link) can be drawn by this function. dragDescriptor parameter has a Drag Descriptor Object that has complete information about dragging in progress and flo object would help with drawing feedback using Joint JS

hideDragFeedback(flo, dragDescriptor) (Optional)

Removes any custom visual feedback drawn by showDragFeedback(). Has the same parameters.

validateNode(flo, node) (Optional)

Returns a javascript array of string error messages that are the result of validating node Joint JS graph node on the canvas in flo editor

preDelete(flo, deletedElement) (Optional)

Called prior to removal of the specified deletedElement allowing extra tidyup before that happens. For example: removes any dependent Joint JS graph elements related to the element about to be deleted.

interactive (Optional)

If set to false, interaction with elements and links is disabled. If it is a function, it will be called with the cell view in action and the name of the method it is evaluated in ('pointerdown', 'pointermove', ...). If the returned value of such a function is false interaction will be disabled for the action. For links, there are special properties of the interaction object that are useful to disable the default behaviour. These properties are: vertexAdd, vertexMove, vertexRemove and arrowheadMove. By setting any of these properties to false, you can disable the related default action on links.

allowLinkVertexEdit (Optional)

If set to false link vertex (or bend point) creation or editing (e.g. movement) is not allowed in the editor.

Data structure reference:

Flo

This object is created by the flo-editor directive controller and it contains various editor specific properties and functions.

scheduleUpdateGraphRepresentation()

Schedules an asynchronous update of the graph DSL representation based on the text DSL representation.

updateGraphRepresentation()

Asynchronously update the graph DSL representation based on the text DSL representation. A promise is returned which gets resolved when the update completes.

updateTextRepresentation()

Asynchronously update the text DSL representation (definition object) based on the graph DSL representation. A promise is returned which gets resolved when the update completes.

performLayout()

Arranges nodes and links of the graph on the canvas.

clearGraph()

Clears out canvas of all nodes and links. With syncing on this also causes the text DSL representation to clear.

getGraph()

Returns a reference to joint.dia.Graph object instance of the canvas contents (The graph model, see Joint JS Graph API

getPaper()

Returns a reference to joint.dia.Paper object instance of the canvas (The graph view object, see Joint JS Paper API

enableSyncing(enable)

Enables or disables textual and graph DSL representation synchronization mechanism based on the passed boolean parameter enable. Useful when textual DSL representation UI is collapsed.

getSelection()

Returns currently selected graph model element (node or link) on the canvas

zoomPercent(percent)

Angular getter/setter function for the zoom value on the canvas. Sets zoom percent value if the integer number parameter is supplied. Returns the integer percent value if parameter is missing (getter mode)

gridSize(gridSize)

Angular getter/setter function for the canvas grid size in pixels. Sets grid width value if the integer number parameter gridSize is supplied. Returns the current grid size value if parameter is missing (getter mode). Note that setting grid width to 1 turns the grid off. Invalid values for gridSize are ignored

getMinZoom()

Returns integer number minimum allowed value for the zoom percent. Useful to set the proper range for zoom controls. Needed by the zoom control on the canvas (if it is set to be shown). The value equals 5 by default (5%).

getMaxZoom()

Returns integer number maximum allowed value for the zoom percent. Useful to set the proper range for zoom controls. Needed by the zoom control on the canvas (if it is set to be shown). The value equals 400 by default (400%).

getZoomStep()

Returns integer number zoom percent increment/decrement step. Needed by the zoom control on the canvas (if it is set to be shown). The value equals 5 by default (5% increment/decrement value).

fitToPage()

Fits the whole graph into canvas's viewport (i.e. no need to scroll to look for content on the canvas). Adjusts the zoom level and scroll position appropriately

readOnlyCanvas(newValue)

Angular getter/setter function for the canvas "read-only" property. Read-only canvas does not allow for any user editing interaction of any shapes on the canvas. Sets the read-only property based on the passed in newValue parameter as the result the canvas toggles the behaviour for read-only state right away. Returns the current "read-only" state value if parameter is missing (getter mode).

createNode(metadata, properties, location)

Creates and returns the newly created Joint JS graph node (instance of joint.dia.Element) based on the graph node metadata object (see Element Metadata), properties key-value pairs map, and location on the canvas (object with x and y properties). The new node is also added to the Flo canvas Joint JS graph and hence to the Joint JS paper and appears right away on the canvas before this function returns the result.

createLink(source, target, metadata, properties);

Creates and returns the newly created Joint JS graph link (instance of joint.dia.Link) between source and target nodes (of type joint.dia.Element) based on the graph link metadata object (see Element Metadata), properties key-value pairs map. The new link is also added to the Flo canvas Joint JS graph and hence to the Joint JS paper and appears right away on the canvas before this function returns the result.

Definition

This object holds data related to DSL's textual representation. Typically this object should at least have text property of type string for the DSL text, but it can also have other properties that might be added by client's Metamodel Service graph-text conversion functions.

Metamodel Listener

Typically Metamodel object is loaded asynchronously via HTTP request. If metadata is cached by the service then it might be useful to register listeners. Flo editor palette would automatically rebuild itself if metamodel has changed

{
  metadataError: function(data) {
    /* Error loading metadata has occurred */
  },
  metadataRefresh: function() {
    /* Metadata is about to be refreshed */
  },
  metadataChanged: function(data) {
    /* New metadata is available */
  }
}

Drag Descriptor

API client is free to add extra properties to this object (i.e. may help drawing visual feedback)

{
  context: context, /* String 'palette' or 'canvas' */
  source: {
    cell: draggedNode, /* Joint JS graph node being dragged */
    selector: selector, /* Optional. Joint JS CSS class selector for the subelement of the dragged node*/,
    port: portType /* Optional. Involved port DOM element type attribute value == port Joint JS markup 'type' property */
 },
 target: {
   cell: targetNode, /* Joint JS graph node target under mouse element */
   selector: selector, /* Optional. Joint JS CSS class selector for the element under mouse within the targetNode */
   port: portType /* Optional. Sub-element under mouse is a port. Port DOM element type attribute value == port Joint JS markup 'type' property */
 },
};

Joint JS Graph Node Markup

model: /* Joint JS model object for a module shape */
  ...
  attributes:
    ...    
    angle:  0,  /* Joint JS property - rotation angle */
            
    id:     "02be8001-ea1e-4f30-a94e-9503da5964b5"  /* Joint JS property - element model UUID
            
    position:       /* Joint JS property - coordinates of the shape's bounding rectangle */
      x:  119                                     
      y:  46
                
    size:           /* Joint JS property - size of the shape's bounding rectangle */
      height: 40                                  
      width:  120
                
    type:   "sinspctr.IntNode"  /* Flo property - internal, type (node, link, handle, decoration, etc) */
            
    z:      1   /* Joint JS property - z-index of the shape
            
    ports:              /* Joint JS property - internal, ports available on the shape */
      input:
        id: "input"
      output:
        id: "output"
      tap:
        id: "tap"
                    
      attrs:  /* Joint JS property - user defined rendering constructs and semantic properties */
            
        .               /*\                                                                 */
        .border         /* \                                                                */
        .box            /*  \                                                               */
        .input-port     /*   \                                                              */
        .label1         /*    \___User defined rendering constructs implied by the markup   */ 
        .label2         /*    /                                                             */
        .output-port    /*   /                                                              */
        .shape          /*  /                                                               */
        .stream-label   /* /                                                                */
        .tap-port       /*/                                                                 */
                                 
        metadata:                   /* Flo property. Node metadata supplied by Metamodel Service */
                                    
        props:                      /* Flo property. Semantic properties of the element. Name <-> value pair map */
          dir:    "/Users/x/tmp"
          file:   "temp.tmp"
          debug:  true
                    
      ...
  ...
...

Element Metadata

Graphical element metadata supplied by Metamodel Service

metadata: {
                
  get:    function(), /* function taking property key string as a parameter */ 
                      /* Returns promise that resolves to the metadata object of the property */
                      /* See snippet below showing the format of a property metadata */
                                                            
  group:  "source",   /* Category/Group of an element. Translates into palette groups of elements */

  name:   "file",     /* Name or Type of an element (should be unique within its group) */
                                                                                
  metadata:  { /* Additional metadata for the element */
    titleProperty: 'props/title', /* Property to be displayed at the top of all properties in properties Div */
    noEditableProps: false, /* If true then element doesn't have properties to edit and properties Div is not shown */
    allow-additional-properties: true, /* Allows user to create new properties for element in the properties Div */
  }

}

Element's property metadata is expected to be as follows

  properties: {
        info: {
            defaultValue:       null,
            description:        "General information about the file",
            id:                 "info",
            name:               "info",
            shortDescription:   "File Info"
        },
            
        language: {
            defaultValue:       "English"
            description:        "Language of the file contents",
            id:                 "language",
            name:               "language",
            shortDescription:   "Text Language"
        },
        ...
Clone this wiki locally