-
Notifications
You must be signed in to change notification settings - Fork 4
OML
OML
, short forObject Markup Language
, is a JSON based markup language to create complex UI interfaces withOGX.JS
. OML is like a shadow DOM made with objects, where the objects are linked to HTML elements in the DOM.
Just as the DOM, you can cycle and find Objects, starting from any node and descending into the tree.
OML files can be loaded and cached using
app.json
and adding references to files in/oml
. If you are hosting on IIS, you will need to add a MIME type for oml files of typeapplication/json
OML is composed of JSON Nodes identified by a name of format selector:Class. A complete node looks like this
{"someSelector:someClass":{
[some properties for the class]
[a possible node]
}}
The selector part of the name of the node is the destination of the node on your page and Class is the Object to be created at that location. For instance, if we wanted to render a simple HTML template in a DIV of id myDiv, our node would be
{"#myDiv:Templates.MyTemplate":{}}
Now let's pretend that the HTML template holds other placeholders (other DIV's) and that we want to add other templates inside each of them, we could do
{"#myDiv:Templates.MyTemplate":{
"node:OML":{
".top:Templates.OtherTemplate":{},
".bottom:Templates.OtherTemplate":{}
}
}
}
Considering that the
MyTemplate
HTML preloaded file, is as follow
<div id="myDiv">
<div class="top"></div>
<div class="bottom"></div>
</div>
You can of course as have other sub nodes in each template, or even other OGX Objects instead, with a more complex HTML template.
{"#myDiv:Templates.MyTemplate":{
"node:OML":{
".top:Templates.OtherTemplate":{
"node:OML":{
"#some_id:Carousel":{
"id" : "my_carousel",
"node:OML":[
{"default:Templates.WhateverTemplate":{
"node:OML":{ ... ∞}
}},
{"default:Templates.WhateverTemplate":{
"node:OML":{ ... ∞}
}}
]
}
}
},
".bottom:Templates.OtherTemplate":{}
}
}
}
Using
<div id="myDiv">
<div class="top"><div id="some_id"></div></div>
<div class="bottom"></div>
</div>
as
MyTemplate
Subfiles can be merged into any OML node, by settings the value of the
node:OML
as simple string, or as OSE expression. Both lines produce the same result
"node:OML":"myOMLFile"
"node:OML":"{{oml myOMLFile}}"
Reusing the previous
{"#myDiv:Templates.MyTemplate":{
"node:OML":{
".top:OML": "subfileA",
".bottom:OML": "subFileB"
}
}
}
Most of the time, a selector is a standard selector such as
#myDiv
or.myClass
but there are also reserved selectors. Some objects do not require a selector, such as Popups and Windows because they are added by default to the object creating them.
Here are some OML selectors you will encounter across the framework
Create an object and attach it to an element given an id
#someDiv:someObject
Create an object and attach it to an element given a class
.someClass:someObject
Create an object and attach it to the default element generated by an Object
default:someObject
Create a Float object given an id
someObjectid:someObject
Create a Float object without an id
1.26.0+
node:someObject
Attach an existing detached Uxi from invoking
uxi.detach();
, to the default element
default:Uxi
Scope Fork
scope:Fork
OSE Script Fork
some_OSE_script:Fork
Bind a property with a controller
property:Bind
Origin allowed for a route
origin:Route
Get/Tranform data
someString:OSE
Note that if you are transforming a template with OSE but your script variables are all global, you can pass an empty string to force evaluation, such as
...,
"template":"MyTemplate",
"data:OSE":""
Get data from db (mongogx)
someString:Database
Render an OML sub node based on the result of a promise returned by a global function
some_global_function:Function
OML Node
node:OML
OML file
selector:OML
data for dynamic selectors
data:OML
Some components will generate placeholders on their own, as they might have their own mechanic to deal with their placement. Let's take the example of a
Popup
. It will generate a single placeholder that is the body of itself. Now it would be very tedious to have to remember and write the name of the default selector per generated object. For this reason, thedefault
selector exists.
Let's create a Popup OML node first. As popups are added to their parent, they do not require a selector and it is replaced by the id that the Popup will receive.
{"myPopup:Popup":{
"width":"50%", "height":"50%", "title":"Popup", "drag":true, "resize":true
}}
We just created a blank popup that will have a width of 50% of its parent and 50% height of its parent. Let's now create this time a popup with a HTML template in it, in its default inner location (its body)
{"myPopup:Popup":{
"width":"50%", "height":"50%", "title":"Popup", "drag":true, "resize":true,
"node:OML":[
{"default:Templates.MyTemplate":{}}
]
}}
Popups and Windows only generate 1 placeholder. But it's also possible to have multiple objects in a single location using a
Container
, to create, for instance, a popup with tabs
{"myPopup:Popup":{
"width":"50%", "height":"50%", "title":"Popup", "drag":true, "resize":true,
"node:OML":[
{"default:Container":{
"id":"someid", "tabs":true,
"node:OML":[
{"default:Templates.MyTemplate":{}},
{"default:Templates.MyOtherTemplate":{}}
]
}}
]
}}
Here we have created a Popup with tabs that holds 2 templates. Clicking each tab will reveal the associated template.
By default,
View
andHTML
(templates) both extend thePlaceholder
Class. It will look fordefault
declarations in its nodes and replace them by the direct selector of the class. For instance, if you create a view that has a dynamic id and a template such as
{"#something:Views.MyView":{
"id":"#"+some_generated_id,
"template":"MyTemplate",
"node:OML":{
"default .class_in_template:DynamicList":{...}
}
}};
Instead of writing the selector as
"#"+some_generated_id+"default .class_in_template"
, we usedefault .class_in_template
to refer to an element of classclass_in_template
With OML you can create a virtually unlimited tree of Objects.
You can also convert inline OML. Consider the following as the content of
myoml.oml
{"default:MyClass":{...}}
You can load this content from another OML file, by referring to its file name
{".mySelector:OML" = "myoml"};
Then the
default
selector in the OML will be replaced to reflect the desired selector
{".mySelector:MyClass" = {...}};
You can bind controls to DynamicList to act as a filtering control. You can use as many controls per property as you need.
Binding a simple input element to a DynamicList
{"#mylist:DynamicList":{
...,
"name:Bind":{
"object":"input[name=\"name\"]",
"action":"filter",
"mode":"in",
"min_length":2
}
}}
Binding a Uxi to the list as control
{"#mylist:DynamicList":{
...,
"active:Bind":{
"object:OSE":"{{uxi test_switch:Switch}}",
"action":"filter",
"mode":"eq"
}
}}
Binding multiple controls, in this case, filter a
date
by using a range (start_date
andend_date
)
{"#mylist:DynamicList":{
...,
"date:Bind":[
{
"object:OSE":"{{uxi start_date:Calendar}}",
"action":"filter",
"mode":"gte"
},
{
"object:OSE":"{{uxi end_date:Calendar}}",
"action":"filter",
"mode":"lte"
}
]
}}
The scope node is a fork that redirects the OML flow depending on the end user scope. For instance, if your application supports multiple user types, and that some parts of your application is restricted to a certain scope (user type), then you can you the node scope redirect to the proper node.
{"scope:Fork":{
"admin":{
[OML]
},
"support":{
[OML]
}
}}
When this node is reached, it will fork towards the
admin
node if the user has the admin scope, or support if the user has thesupport
scope.
Note that the scope can also be declared as a
scope expression
. In this case the scope is computed based on the current scope and the expression that is passed. It then returns the first matching expression
{"scope:Fork":{
"permA|permC":{
[OML]
},
"(permA|permB)+permC":{
[OML]
}
}}
You can also use
Fork
as a condition where you can pass anOSE
script and an object, such as
{"{{$has_comments}}:Fork":{
"data":{"has_comments":true},
"values":{
"true:Boolean":[OML A],
"false:Boolean":[OML B]
}
}}
You can use
:Boolean
or:Number
to convert the value from a string
The
Function
node behave like aFork
but is intended to use withPromises
. HereMyFunction
must be a globally available function (name/path) that must return aPromise
{"MyFunction:Function":{
"id" : "myFnc",
"data" : {...},
"success" : OML,
"error" : OTHER_OML
}}
Here's a dummy function available globally
function MyFunction(args){
let promise = new Promise(function(success, error){...});
return promise;
}
To retrieve the result passed to each callback (success or error), you can then use down the OML tree, the
OSE
tag
"data:OSE": "{{result MyFunction}}"
Note that once the result is retrieved once, it is removed from memory and not accessible anymore
If some of your nodes generate by themselves other OML node dynamically, such as with a DynamicList, you can still access the relative objects of the list and their properties by using
{{&item}}
to target the current element of the list. Note that it relies on theas
property of the config of theDynamicList
, which by default is"item"
.
In this example, we create, from a list of objects, a
DynamicList
that creates oneCarousel
per item of its list, and 2 views perCarousel
. The second view of theCarousel
will have anotherDynamicList
based on a sub-list of the current item in the firstDynamicList
.
{
"#subscriptions > .list:DynamicList":{
"id":"subscription_list",
"key":"_id",
"as":"item",
"scroll":true,
"display":{
"oml":{
"default:Carousel":{
"dots":false,
"node:OML":[
{"default:Templates.Subscription":{
"data:OSE":"{{&item}}"
}},
{"default:Templates.Settings":{
"id:OSE":"#settings_{{&item._id}}",
"data:OSE":"{{&item}}",
"node:OML":{
"#settings_{{&item._id}} .list:DynamicList":{
"id:OSE":"subscription_{{&item._id}}",
"key":"_id",
"as":"renewal",
"display":{
"template":"Renewal",
"css":"renewal"
},
"list:OSE":"{{&renewal.subList}}"
}
}
}}
]
}
}
}
}
}
We use
{{&OBJECT_ID}}
to refer to another object, in our case{{&subscription_list}}
which refers to theDynamicList
of id "subscription_list". We also use{{&OBJECT_ID.$}}
or{{&subscription_list.$}}
to refer to the current item of that list.
Also note that the first
DynamicList
id:"subscription_list" isn't passed any data. We would pass some data to that list later on and it would render all the objects based on that oml.
Dynamic selectors can also be resolved using the
data:OML
property. For instance, if you dynamically create an id and want to target the HTML element created dynamically withinOML
, you can target any property withdata:OML
.
{'#mydiv > .something:Views.MyViews':{
id:'#myview_'+__someobject.id,
'node:OML':{
'#myview_{{$id}}:Component':{
'data:OML':__someobject
}
}
}}
The same result can be achieved by pre-replacing values using OGX.Templater.jmake without using
data:OML
.
Note that the
data:OML
property has to be passed inside the OML node, and thatComponent
is just generic naming used for the sake of the example and is not actually a valid object.
OML nodes can be added at anytime! Every UI Object in OGX.JS that extend the Uxi Class can add node to itself. To create a tree of objects from, for instance, a View
OGX.OML.render(this, [OML node]);
The purpose of OML nodes, is to render a tree of information through different components, where each sub component inherit the status of its parent. If you wish to only create one object that does not have sub-objects (as nodes), then you should use
let o = OGX.Object.create(Class, Config);
In this case the object will be instantiated but its constructor
construct
will not be fired and the object won't be added to any parent. You can do that manually.
someObj.add(o);
o.construct(someData);
Now, to create, fire
construct
and add to a parent automatically, use (from anyUxi
)
this.create(Class, Config);
If you want to render a tree of objects with OML, where sub nodes can also have sub nodes and are all added to their respective parent and have their constructor
construct
fired, then you have to use
OGX.OML.render(this, OML);
Any Object created from a
Uxi
node do not need to be destroyed, it is handled automatically from route to route. Objects created as stand alone and never attached to anotherUxi
node need to be destroyed. This is done by doing
myObject.destroy();
Since
1.8.0
the methodreplaceNodeName
has been deprecated and replaced byrename
You can also rename a node using the
rename
method.
OGX.OML.rename(_NODE_, _REPLACE_, _SEARCH_);
Note that
_SEARCH_
defaults todefault
as per OML default, hence passed last. Also note that_SEARCH_
and_REPLACE_
can also be of typeArray
. In this case, both_SEARCH_
and_REPLACE_
are expected to be of typeArray
. Consider this node:
let myNode = {
'.someclass .otherclass:Roulette':{
...
}
};
If you want to rename
.someclass .otherclass
with#someid .otherclass
, do
let newNode = OGX.OML.rename(myNode, '#someid .otherclass', '.someclass .otherclass:Roulette');
You can lookup a node given its id, the node can be nested as deep as you want
let myNode = {
...,
'node:OML';
'.someclass .otherclass:Roulette':{
id:'my_roulette'
}
}
};
let obj = OGX.OML.getNodeById(myNode, 'my_roulette');
let node = {'default:Views.MyViews':{...}};
const cls = OGX.OML.getNodeClass(node); //Views.MyViews
let is_oml = OGX.OML.isOML('hello'); //false
let is_oml = OGX.OML.isOML('{".selector:Html:{"html":"hello"}}'); //true
Given selector, class and optional config
let oml = OGX.OML.make('.myclass', 'DynamicList', {id: 'myid'});
If the routing node of your app.json file is being too big or you just want to organize things better, you can merge OML data to the routing, as such
In myview.oml
{"#myId:SomeObject":{...}}
In the route to get to that view, in app.json
{
"routing": {
"routes": {
"mystage/myroute":{
"oml":"myview"
}
}
}
}
options
are passed to OML when creating an instance ofCore
vianew App()
and are exposed as getters/setters.
The maximum time it should take the engine to render an OML script, expressed in
int
. Get or Set this value using
const time = OGX.OML.maxRenderTime();
OGX.OML.maxRenderTime(300);
The maximum (recursive) depth the engine should look to resolve OSE scripts. Recommended default is 2 but then it depends on your code. Get or Set this value using
const max = OGX.OML.maxDepth(_value_or_nothing);
OGX.OML.maxDepth(4);
- Welcome
- Changelog
- Structure
- Configuration
- Getting started
- CLI
- Poly
- Core
- Templating
- Routing
- Controllers
- Components
- Extra Components
- Helpers
- Styling
- Debugging