The metadata framework is a RhoConnect & Rhodes tool which provides synchronized application layouts. For example, if your application had an “account” model with the fields “name” and “address”, metadata would allow you to add an additional field “phone” in the source adapter and this new field would automatically display in the Rhodes application.
In addition to adding/removing/updating fields, metadata also provides powerful features like handling field validations, data-binding, collections, repeatable elements, and much more.
Metadata is first defined in a source adapter by implementing an adapter method metadata
that returns a JSON structure. The JSON structure may contain a hash of hashes, as well as having child elements defined as arrays of hashes. The keys in each object can be any string, however there are some reserved labels and labels used by certain elements.
The metadata for an adapter is synchronized to Rhodes along with the rest of the adapter dataset. Metadata is called before the query method in the SyncEngine
workflow, so you can tailor your query method based on metadata.
Rhodes uses the synchronized metadata definition to render views at runtime. These views are driven by the metadata definition, so when a new definition is synchronized, the views will automatically reflect the changes.
The first step is to install the rhodes-translator gem:
$ gem install rhodes-translator
Then add rhodes_translator
to your extensions in your Rhodes application’s build.yml
:
extensions: ["rhodes_translator"]
Now define some metadata and add this to a metadata
method your source adapter model. For example:
def metadata # define the metadata row1 = { :label => 'Address 1', :value => '123 fake street', :name => 'address1', :type => 'labeledrow' } table = { :label => 'Table', :type => 'table', :children => [ row1, row1, row1 ] } view = { :title => 'UI meta panel', :type => 'iuipanel', :children => [table] } # return the definition as JSON {'index' => view}.to_json end
The following elements are available for building metadata definitions.
The following labels are reserved, used by the metadata framework.
:type
:children
:repeatable
:validation
Children of an element are defined by an array of hashes. To specify the children of an element you add the “:children” key and specify the children of that element.
The following example shows a table element that has three row1
elements as :children
:
row1 = { :label => 'Address 1', :value => '123 fake street', :name => 'address1', :type => 'labeledrow' } table = { :label => 'Table', :type => 'table', :children => [ row1, row1, row1 ] }
Repeatable elements use data binding to specify an array of objects to iterate over. The children of the repeatable element are duplicated for each element in the object referred to by the repeatable hash key. For more information on data-binding see the data binding section on this page.
The following example will result in a table with 2 child rows, one row with the value set to ‘123 fake street’ and the second row value set to ‘321 fake street’
@addresses = [ { :name => "123 fake street" }, { :name => "321 fake street" } ] row1 = { :label => 'Address 1', :value => '{{name}}', :name => 'address1', :type => 'labeledrow' } table = { :label => 'Table', :type => 'table', :children => [ row1 ], :repeatable => '{{@addresses}}' }
By default, any controller actions that have metadata for a given model with the key corresponding to the action will be rendered with metadata instead of the view.
However, you can render arbitrary metadata in the view with the render_metadata
method. You must specify the action to the metadata. By default, the metadata provided is pulled from the model, however this can be overridden as well.
Example:
<%= render_metadata('indexdata') %>
Or, using explicit metadata resource:
<%= render_metadata('index', Account.metadata) %>
Data binding happens before the elements get translated to html. All of the instance variables in your method are able to be referenced by the data binding keys.
A data binding key is always contained within a string. It is indicated by using double bracket enclosure: {{databinding path here}}
To reference hash keys, or index arrays, the /
operator is used. You must specify the whole path to the object, the only exception being within repeatable elements.
We will use the following dataset for example:
@data = { :key1 => [ 'array', 'with', 'elements'], :key2 => { :this => "is", :a => "hash" } }
Example data binding keys using this data:
{{@data/key1/0}}
will be replaced with ‘array’
{{@data/key1/2}}
will be replaced with ‘elements’
{{@data/key1/5}}
will be replaced with an error message
{{@data/key2/this}}
will be replaced with ‘is’
{{@data/key2/this}}
will be replaced with ‘hash’
Within a repeatable element, the root node is replaced with the element that is being repeated. See the example in the repeatable section above. To reference the root element while inside a repeatable a /
should be prepended to your data binding expression: {{/@data/key1/0}}
Validation is expressed as a key added to an existing metadata definition. Below is a sample showing all of the validations that can be used.
:validation => { :regexp = "^.+$", :validators = [:required, :number, :currency, :email, :phone], :min_len = 0, :max_len = 100, :min_value = 10, :max_value = 1000 }
An HTML form that is used for submitting data:
<form></form>
form
name
- used as name attribute in <form> tagaction
- used as action attribute in <form> tagmethod
- used as method attribute in <form> tagContains children with a <div> tag with the div class set to ‘panel’:
<div class="panel"></div>
panel
unused
Contains children with no extra html.
view
unused
HTML Table.
<table></table>
table
class
- class attribute on <table> elementA labeled text display.
<div> <label></label> <span></span> </div>
text
div_class
- class attribute for divlabel_class
- class attribute for labelvalue_class
- class attribute for spanlabel
- value for label elementvalue
- value for inner div elementA labeled text input.
<div> <label></label> <input type="text|password"></input> </div>
textinput|password
div_class
- class attribute for outer divlabel_class
- class attribute for labelvalue_class
- class attribute for inputlabel
- value for label elementvalue
- value attribute for inputname
- name attribute for inputeditable
- boolean value, enable/disable changing of selection. Default true
.Text area input field.
<textarea> </textarea>
textarea
class
- class attribute for textareavalue
- value attribute for textareaname
- name attribute for textarearows
- rows attribute for textareacols
- cols attribute for textareaeditable
- boolean value, enable/disable changing of selection. Default true
.HTML Select.
<div> <label></label> <select> <option></option> </select> </div>
select
size
- size attribute for selectclass
- class attribute for selectallow_multi
- boolean value, allow multiple selection on dropdown. Default false
.editable
- boolean value, enable/disable changing of selection. Default true
.div_class
- class attribute for outer divlabel_class
- class attribute for labellabel
- value for label elementvalue
- default value displayedvalues
- array of strings to provide as optionsA checkbox field
<div> <label></label> <input type="checkbox"></input> </div>
checkbox
div_class
- class attribute for outer divlabel_class
- class attribute for labellabel
- value for label elementname
- name attribute for inputdefault_value
- boolean value, true = checked, false = unchecked. Default false
.value
- string value, ‘true’ = checked, anything else = uncheckedAn anchor tag.
<a href=""></a>
link
url
- the url inside the href attributetext
- the text that the link displaysAnchor tag, with click to call information.
<a href=""></a>
telephone
number
- the phone number to calltext
- The text that the link displaysHere is a more advanced source adapter metadata
example which demonstrates the concepts we described here:
def metadata @contact = { :label => '{{name}}', :name => 'contact', :type => 'labeledrow', # a row which links to another # object in a different model :value => '{{age}}, {{year}}' } # name and phone of the contact object not the account! @rows = { :type => 'view', :children => [@contact], :repeatable => '{{@forms}}' } @link = { :label => 'New', :value => '{{@link1}}', :type => 'labeledrow' } @table = { :label => 'table', :name => 'table', :type => 'table', :class => 'grid', :children => [@link, @rows] } @index = { :title => 'PEOPLE', :type => 'iuipanel', :children => [@table] } @error = { :label => '{{@errorlabel}}', :type => 'text', :name => 'name', :value => '{{@errors}}' } @name = { :label => 'name', :type => 'textinput', :name => 'name', :value => '{{@form/name}}', :validation => { :validators => [:required] } } @age = { :label => 'age', :type => 'textinput', :name => 'age', :value => '{{@form/age}}', :validation => { :validators => [:required], :min_value => 13 } } @year = { :label => 'year', :type => 'textinput', :name => 'year', :value => '{{@form/year}}', :validation => { :validators => [:required], :max_value => 2010 } } @submit = { :name => 'submit', :value => 'submit', :type => 'submit' } @form = { :name => 'inputform', :action => '{{@submiturl}}', :type => 'form', :children => [@error, @name, @age, @year, @submit] } { 'indexdata' => @index, 'new' => @form }.to_json end
Templates can be overridden by placing the ERB files in the app/templates
folder in your Rhodes application. They will be used before using any of the built-in templates.