Mobile UI Components
Contents |
Introduction
Openbravo Mobile Core Infrastructure UI components are based on Enyo's component concept. A component can be the representation of either a Collection or a Model, or a stand-alone UI widget (e.g. Button).
This article aims to be an introduction/overview on the main development concepts behind Openbravo Mobile and how are related to each other.
Prerequisites
You should be proficient in JavaScript programming language. More info at Openbravo Developer's Guide prerequisite article.
Acknowledgments
As Enyo and Backbone.js are technologies used by Mobile Core, you will find code and information extracted from their sites.
Code
Part of the code in this article is available from the Mercurial repository web-pos-doc:
$ hg clone https://bitbucket.org/iperdomo/web-pos-doc
What's Enyo?
Enyo is a cross-platform framework that provides powerful features to build complex Web applications in a modular way:
- Object encapsulation, inheritance, and ownership hierarchy - Kinds and Components
- View and DOM widget model - UiComponent and Control
- Event routing - Dispatcher and Signals
- Services model - Async and Ajax
- Basic HTML components - Base UI (enyo.Input, enyo.Button, etc)
- Touch support - Touch scroller and gesture simulation
- Package loader - enyo.depends() and package.js
What's Backbone.js?
Backbone.js is a library that "gives structure to web applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions".
![]() | Note: Openbravo Mobile Core doesn't use the concept of View in Backbone.js |
Kinds, Components and Controls
The Enyo building blocks for writing applications are Kinds, Components and Controls:
- Kind: Is a constructor-with-prototype (like a class) that has advanced features like prototype-chaining (inheritance)
- Component: The
enyo.Component
, is the basic building block of Enyo, is a kind that can publish properties, expose events, and contain other components - Control:
enyo.Control
is a component that controls a DOM node (i.e., an element in the user interface). Controls are generally visible and the user often interacts with them directly. Things like buttons and input boxes are obviously controls, but in Enyo a control may become as complex as an entire application
Kind
You can think of a kind like a class in Java. Using the enyo.kind
function you can define your own kinds e.g.
enyo.kind({ name: 'OB.MyKind', // name of the kind you're defining kind: enyo.Component // reference to the parent kind from which will inherit all properties }); var x = new OB.MyKind(); // a new constructor function OB.MyKind is available, you can create an instance using the new operator
Notes:
- You can omit the kind attribute when defining a new kind, Enyo will default it to
enyo.Control
- Is a best practice to namespace your code. The name attribute supports using
'DBPrefix.KindName'
, and will create the DBPrefix object if is not available
![]() | Remember that OB and OBPOS are reserved namespace objects, you must not define new kinds inside this objects as it may collide with Openbravo code |
More info on the special attributes in the enyo.kind
function at Enyo documentation: Creating Kinds
Component
"A component is an Enyo kind that can publish properties, expose events, and contain other components. It may be useful to think about components as owning a set of content (other components) and providing inputs (methods and property setters) and outputs (events and property getters). A component controls the content it owns and sends messages to its owner in the form of events."
enyo.kind({ name: 'OB.MyComponent' kind: enyo.Component }); var comp = new OB.MyComponent();
Published Properties
A Component can publish properties. "For convenience, published properties are automatically provided with setter, getter, and changed methods."
enyo.kind({ name: 'OB.MyComponent', kind: enyo.Component, active: false, published: { count: 0 }, countChanged: function (oldValue) { // this function will get called when the setCount() finishes console.log('countChanged: ' + this.count); } }); var comp = new OB.MyComponent(); comp.setCount(2); // a setCount is now available since the count property is a published one comp.setCount(comp.getCount() + 1); // using the provided getter and setter, will trigger the countChanged comp.count = 1; // will not trigger the countChanged since is not using setter
Notes:
- When publishing a property a setter is created using the CamelCase version of the property name, e.g. count -> setCount
- You can define a propertyNameChanged (e.g. countChanged) method that will get executed after the setter finishes. Notice that if someone modifies directly then count property, this method will not get executed
- Not all properties of your kind/component/control needs to be a published property, e.g.
active
property in the previous example is not a published property
Components in Components
A Component can contain another component(s)
enyo.kind({ name: 'OB.MyComp1', kind: enyo.Component }); enyo.kind({ name: 'OB.Main', kind: enyo.Component, components: [ // defining the components attribute (an Array) you can append child components within a kind {name: 'c1', kind: 'OB.MyComp1'}, {name: 'c2', kind: 'OB.MyComp1'} // each nested component requires a name and the kind attribute ] }); var main = new OB.Main(); console.log(main.$.c1); // You can access the contained components using the $ hash and the component name
More information about enyo.Component
capabilities at: Creating Components
Control
"enyo.Control
is a component that controls a DOM node (i.e., an element in the user interface). Controls are generally visible and the user often interacts with them directly"
enyo.kind({ name: 'OB.MyControl', kind: enyo.Control, content: 'Hello World' }); var c = new OB.MyControl();
This code creates a DIV with the string Hello World inside it.
Notes:
- If you don't specify a
tag
attribute, the DOM node will be a DIV - The
content
attribute specifies the containing text inside the tag - Since an
enyo.Control
is a Component, a Control can have child controls using thecomponents
array
enyo.kind({ name: 'OB.MyControl2', tag: 'p', components: [ {name: 'span1', tag: 'span', content: 'Hello'}, {name: 'span2', tag: 'span', content: 'World'} ] }); var c = new OB.MyControl2();
Notes:
- Remember that if you omit the kind attribute, Enyo will default it to
enyo.Control
- In the previous example, the
c
instance is a P node, that contains two SPAN nodes with content Hello and World receptively
More info on handling Controls at: Creating Controls
Models and Collections
Model
More info at Model documentation
Collection
More info at Collection documentation
Examples
Enyo
enyo.kind({ name: 'Doc.Block', style: 'background: #d2d4d5;' }); enyo.kind({ name: 'Doc.App', classes: 'row-fluid', // Bootstrap like CSS class components: [ {classes: 'span1', kind: 'Doc.Block', content: 'Col 1'}, // span1 Bootstrap like CSS class {classes: 'span1', kind: 'Doc.Block', content: 'Col 2'}, {classes: 'span1', kind: 'Doc.Block', content: 'Col 3'}, {classes: 'span1', kind: 'Doc.Block', content: 'Col 4'}, {classes: 'span1', kind: 'Doc.Block', content: 'Col 5'}, {classes: 'span1', kind: 'Doc.Block', content: 'Col 6'}, {classes: 'span1', kind: 'Doc.Block', content: 'Col 8'}, {classes: 'span1', kind: 'Doc.Block', content: 'Col 9'}, {classes: 'span1', kind: 'Doc.Block', content: 'Col 10'}, {classes: 'span1', kind: 'Doc.Block', content: 'Col 11'}, {classes: 'span1', kind: 'Doc.Block', content: 'Col 12'}, ] });
Output
Notes:
-
Doc.Block
is a simple DIV node with some background style -
Doc.App
is a DIV containing 12Doc.Block
nodes
Enyo and Backbone
// Defining the kind of each item in a list, in this case is a LI node enyo.kind({ name: 'Doc.UI.ListItem', tag: 'li', components: [ {tag: 'a', attributes: { href: '#'}, name: 'label'} ], create: function () { this.inherited(arguments); this.$.label.setContent(this.label); } }); // Defining the kind for the list, a UL node enyo.kind({ name: 'Doc.UI.List', tag: 'ul', published: { collection: null }, collectionChanged: function () { enyo.forEach(this.collection.models, function (model) { this.createComponent({ kind: 'Doc.UI.ListItem', label: model.get('_identifier') }).render(); }, this); } }); // Defining a Product model Doc.Product = Backbone.Model.extend({}); // Defining a ProductList collection Doc.ProductList = Backbone.Collection.extend({ model: Doc.Product }); // Defining the main App enyo.kind({ name: 'Doc.App', classes: 'pagination', // Bootstrap CSS class components: [ {kind: 'Doc.UI.List', name: 'list'} ], create: function () { this.inherited(arguments); // data is a cached array of products, we create a new Backbone.Collection (ProductList) this.$.list.setCollection(new Doc.ProductList(data)); } });
Output
Notes:
-
create
function is available to hook your own logic after the component creation -
this.inherited(arguments)
is the way to call the 'super' -
enyo.forEach
is a simple way to iterate through an array and perform an action with each element -
createComponent
is the way to dynamically create new instances of components -
this.$
hash contains the names of the child components, you can access using:this.$.compName
, e.g.this.$.list