AppSuite:Extending the UI: Difference between revisions
m (white space fixes) |
|||
Line 1: | Line 1: | ||
<div class="title">Extending the UI </div> | <div class="title">Extending the UI </div> | ||
'''Abstract' | '''Abstract.''' Abstractly speaking extension points are an architecture for letting plugins contribute functionality to other parts of the program. They form the core of the OX App Suite plugin architecture. A less detailed hands-on introduction can be found [[ AppSuite:Extending_the_UI_(Hands-on_introduction) | here]]. Some basics about the extention point concept and advantages compared to inheritance. | ||
''Abstractly speaking extension points are an architecture for letting plugins contribute functionality to other parts of the program. They form the core of the OX App Suite plugin architecture. A less detailed hands-on introduction can be found [[ AppSuite:Extending_the_UI_(Hands-on_introduction) | here]]. Some basics about the extention point concept and advantages compared to inheritance. | |||
__TOC__ | __TOC__ | ||
== Introduction == | == Introduction == | ||
=== Inheritance vs. Extension points === | === Inheritance vs. Extension points === | ||
OX App Suite uses the extension point concept to create extension points that allow a simple and flexible way to extending functionality. When system reaches a part that can be extended it asks a central registry if extensions are registered. In that case these extensions will be executes independent of the providing component (some plugin or OX App Suite itself). | OX App Suite uses the extension point concept to create extension points that allow a simple and flexible way to extending functionality. When system reaches a part that can be extended it asks a central registry if extensions are registered. In that case these extensions will be executes independent of the providing component (some plugin or OX App Suite itself). | ||
Line 16: | Line 13: | ||
[[File:Ui_ext_01.gif|frame|border|left]] | [[File:Ui_ext_01.gif|frame|border|left]] | ||
=== Some characteristics === | === Some characteristics === | ||
* good fences: extension points unregister corrupt extenders | * good fences: extension points unregister corrupt extenders | ||
* lazy loading: extenders are loaded if they are used | * lazy loading: extenders are loaded if they are used | ||
Line 27: | Line 22: | ||
=== Components === | === Components === | ||
The extension point system lives in the 'io.ox/core/extensions' module and consists of these elements: | The extension point system lives in the 'io.ox/core/extensions' module and consists of these elements: | ||
* extension point system: accessing the outer parts | * extension point system: accessing the outer parts | ||
Line 35: | Line 29: | ||
* baton: object used to store context, passed back through callbacks | * baton: object used to store context, passed back through callbacks | ||
==Extension Point System== | |||
== Extension Point System == | |||
<pre class="language-javascript"> //load extension points module | <pre class="language-javascript"> //load extension points module | ||
require(['io.ox/core/extensions'], function (ext) { | require(['io.ox/core/extensions'], function (ext) { | ||
//insert code here | //insert code here | ||
});</pre> | });</pre> | ||
== Registry == | == Registry == | ||
* manages extension points, extensions and their state | * manages extension points, extensions and their state | ||
=== list points === | === list points === | ||
<pre class="language-javascript"> // returns array of point ids | <pre class="language-javascript"> // returns array of point ids | ||
var keys = ext.keys();</pre> | var keys = ext.keys(); | ||
</pre> | |||
=== get/create point === | === get/create point === | ||
<pre class="language-javascript"> //also registers a point if not happened yet | <pre class="language-javascript"> //also registers a point if not happened yet | ||
var mypoint = ext.point('io.ox/calendar/detail');</pre> | var mypoint = ext.point('io.ox/calendar/detail'); | ||
</pre> | |||
== Extension Point == | == Extension Point == | ||
Line 68: | Line 56: | ||
* id | * id | ||
* description (optional) | * description (optional) | ||
'''''example''''' | '''''example''''' | ||
Line 76: | Line 63: | ||
descr = point.description = ''; | descr = point.description = ''; | ||
</pre> | </pre> | ||
=== add extension === | === add extension === | ||
* important: existing extensions with same id will not be overwritten - use replace insted | * important: existing extensions with same id will not be overwritten - use replace insted | ||
* example: add extension with id 'date' | * example: add extension with id 'date' | ||
'''''example''''' | '''''example''''' | ||
<pre class="language-javascript"> // chainable (returns mypoint) | <pre class="language-javascript"> // chainable (returns mypoint) | ||
point.extend({ | point.extend({ | ||
Line 93: | Line 76: | ||
//draw something | //draw something | ||
} | } | ||
});</pre> | }); | ||
</pre> | |||
=== replace extension === | === replace extension === | ||
* important: only extension properties will be replaced (jQuery extend is used internally) | * important: only extension properties will be replaced (jQuery extend is used internally) | ||
* hint: ''replace'' can also be executed before extension is initially created with ''extend'' | * hint: ''replace'' can also be executed before extension is initially created with ''extend'' | ||
'''''example''''' | '''''example''''' | ||
<pre class="language-javascript"> // chainable (returns mypoint) | <pre class="language-javascript"> // chainable (returns mypoint) | ||
mypoint.replace({ | mypoint.replace({ | ||
Line 111: | Line 91: | ||
//draw something completely different | //draw something completely different | ||
} | } | ||
});</pre> | }); | ||
</pre> | |||
=== use extensions=== | === use extensions=== | ||
Line 119: | Line 99: | ||
* baton forwarded within programmatic flow and used for storing and exchanging data between extensions | * baton forwarded within programmatic flow and used for storing and exchanging data between extensions | ||
<pre class="language-javascript"> mypoint.invoke(name, context, baton);</pre> | <pre class="language-javascript"> | ||
mypoint.invoke(name, context, baton); | |||
</pre> | |||
'''''example''''' | '''''example''''' | ||
<pre class="language-javascript"> //call 'draw' of all registered extensions (order defined by index attribute) | <pre class="language-javascript"> //call 'draw' of all registered extensions (order defined by index attribute) | ||
//node used as context ('draw' function is called via apply(node, args)) | //node used as context ('draw' function is called via apply(node, args)) | ||
//baton's data property contains relevant information about current entity (for example a mail object) | //baton's data property contains relevant information about current entity (for example a mail object) | ||
mypoint.invoke('draw', node, baton);</pre> | mypoint.invoke('draw', node, baton); | ||
</pre> | |||
=== access extensions === | |||
<pre class="language-javascript"> | |||
// returns array containing all extension ids | |||
mypoint.keys(); | |||
// returns array containing all extensions | |||
mypoint.all(); | |||
// executes callback for a specific extension; chainable (returns point) | |||
mypoint.get(id, callback); | |||
// disabled extension will return true also; | |||
var exists = mypoint.has(id); | |||
</pre> | |||
'''''enabled only''''' | '''''enabled only''''' | ||
<pre class="language-javascript"> | |||
// returns array containing all enabled extensions | |||
mypoint.list(); | |||
// returns number containing enabled extensions | |||
mypoint.count(); | |||
</pre> | </pre> | ||
=== enabling/disabling === | === enabling/disabling === | ||
<pre class="language-javascript"> | |||
<pre class="language-javascript"> var enabled = mypoint.isEnabled();</pre> | var enabled = mypoint.isEnabled();</pre> | ||
<pre class="language-javascript"> //chainable (returns mypoint) | <pre class="language-javascript"> // chainable (returns mypoint) | ||
mypoint.enable(id);</pre> | mypoint.enable(id);</pre> | ||
<pre class="language-javascript"> //chainable (returns mypoint) | <pre class="language-javascript"> // chainable (returns mypoint) | ||
mypoint.disable(id);</pre> | mypoint.disable(id);</pre> | ||
Revision as of 18:52, 12 April 2013
Abstract. Abstractly speaking extension points are an architecture for letting plugins contribute functionality to other parts of the program. They form the core of the OX App Suite plugin architecture. A less detailed hands-on introduction can be found here. Some basics about the extention point concept and advantages compared to inheritance.
Introduction
Inheritance vs. Extension points
OX App Suite uses the extension point concept to create extension points that allow a simple and flexible way to extending functionality. When system reaches a part that can be extended it asks a central registry if extensions are registered. In that case these extensions will be executes independent of the providing component (some plugin or OX App Suite itself).
The illustrated example compares inheritance and extension points. The main benefit of using extension points is that the programm is still the active component and it's in controll. This leads to the following advantages:
- Reduced coupling
- Increased cohesion
- Modularity- Re-usability
- Dynamic
Some characteristics
- good fences: extension points unregister corrupt extenders
- lazy loading: extenders are loaded if they are used
- fair play: all extenders have equal rights
- diversity: extension points support different extension
Components
The extension point system lives in the 'io.ox/core/extensions' module and consists of these elements:
- extension point system: accessing the outer parts
- registry: manages extension points, extensions and their state
- extension point: part of the systems that can be extended, referenced by a unique id
- extension: adding/replacing functionality during runtime, referenced by a unique id
- baton: object used to store context, passed back through callbacks
Extension Point System
//load extension points module require(['io.ox/core/extensions'], function (ext) { //insert code here });
Registry
- manages extension points, extensions and their state
list points
// returns array of point ids var keys = ext.keys();
get/create point
//also registers a point if not happened yet var mypoint = ext.point('io.ox/calendar/detail');
Extension Point
- part of the systems that can be extended
- referenced by a unique id
- defines some kind of contract it's extension that to comply
attributes
- id
- description (optional)
example
//get a point and it's description var point = ext.point('io.ox/mail/links/toolbar'), descr = point.description = '';
add extension
- important: existing extensions with same id will not be overwritten - use replace insted
- example: add extension with id 'date'
example
// chainable (returns mypoint) point.extend({ id: 'example1', // Every extension is supposed to have an id index: 100, // Extensions are ordered based on their indexes draw: function () { //draw something } });
replace extension
- important: only extension properties will be replaced (jQuery extend is used internally)
- hint: replace can also be executed before extension is initially created with extend
example
// chainable (returns mypoint) mypoint.replace({ id: 'example1', index: 100, draw: function (baton) { //draw something completely different } });
use extensions
- invoking extension point extensions by defining functionname, context and baton
- node used as context (function is called via apply(node, args))
- baton forwarded within programmatic flow and used for storing and exchanging data between extensions
mypoint.invoke(name, context, baton);
example
//call 'draw' of all registered extensions (order defined by index attribute) //node used as context ('draw' function is called via apply(node, args)) //baton's data property contains relevant information about current entity (for example a mail object) mypoint.invoke('draw', node, baton);
access extensions
// returns array containing all extension ids mypoint.keys(); // returns array containing all extensions mypoint.all(); // executes callback for a specific extension; chainable (returns point) mypoint.get(id, callback); // disabled extension will return true also; var exists = mypoint.has(id);
enabled only
// returns array containing all enabled extensions mypoint.list(); // returns number containing enabled extensions mypoint.count();
enabling/disabling
var enabled = mypoint.isEnabled();
// chainable (returns mypoint) mypoint.enable(id);
// chainable (returns mypoint) mypoint.disable(id);
example
//disable ext.point('io.ox/mail/detail/header').disable('receiveddate');
underscore equivalents
- only considers enabled extensions
- functions returns underscore-chain object of enabled extensions
- take a look at [ http://underscorejs.org ] for more details
mypoint.chain(); mypoint.each(callback); mypoint.map(callback); mypoint.filter(callback); //select alias mypoint.reduce(callback, memo); //inject alias mypoint.pluck(propertyName);
example
// Shuffle extension order ext.point('io.ox/calendar/detail').each(function (e) { e.index = Math.random() * 1000 >> 0; }).sort();
Event Hub
- Event Hub based on jQuery's on, off, one and trigger
- differences documentated for each function
// attach listener mypoint.on(type, data, function);
// detach listener mypoint.off(type, function);
// attach listener for a single execution mypoint.one(type, data, function);
// trigger event // difference: allows multiple types separated by spaces. // difference: actually calls triggerHandler since we are not in the DOM. mypoint.trigger(types);
// explicit destroy to clean up. mypoint.destroy();
Extension
- adding/replacing functionality during runtime
- referenced by a unique id
Attributes
- id
- index (optional): numeric value used for specify order of execution (also valid are 'first' and 'last')
- functions: as required by the extension point contract
'example
//defining a extension for some extension point that requires a draw function { id: 'example1', index: 100, draw: function () { //draw something } };
extensions patterns
OX App Suite uses extensions patterns. Please keep in mind that this list not necessarily covers all pattern currently used.
io.ox/backbone/forms.js
- CheckBoxField
- ControlGroup
- DateControlGroup
- DatePicker
- ErrorAlert
- Header
- InputField
- Section
- SectionLegend
- SelectBoxField
- SelectControlGroup
io.ox/backbone/views.js
- AttributeView
io.ox/calendar/edit/recurrence-view
- RecurrenceView
io.ox/core/extPatterns/links
- Button
- DropdownLinks
- InlineLinks
- link
- ToolbarLinks
io.ox/core/tk/attachments
- AttachmentList
- EditableAttachmentList
io.ox/contacts/widgets/pictureUpload.js
io.ox/preview/main.js
- Engine
Baton
Part of extension points system is a structure called baton which serves as an context object. The baton passed back through callbacks within programmatic flow allowing data exchange between extension points.
attributes
- data: usually contains current entity object, also used for data exchange
- options: contains data such as the current active application if a baton is used comprehensively
- flow:
- disabled: stores disabled extensions (managed via baton.disable(pointId, extensionId))
- disabled: stores disabled extensions (managed via baton.disable(pointId, extensionId))
- $: used to reference a jQuery node object
disable extensions
//disable baton.disable(pointid, extensionid); //is disabled var isDisabled = baton.isDisabled(pointid, extensionid);
example
var pointid = 'io.ox/mail/detail', extensionid = 'example3', node = $('div'), baton = ext.Baton(); //disable extension //returns undefined baton.disable(pointid,extensionid); //invoke extension with baton instance ext.point(pointid).invoke('draw', node, baton)
data exchange
example
//extension using baton to store data { id: 'example1', index: 100, draw: function (baton) { //get the currenty process mail object var mail = baton.data; //append subject to current node referenced as this this.append( $('div').text(mail.subject); ) //extend mail object to store some flag mail.drawn = mail.drawn || {}; mail.drawn.subject = true; //disable extension3 baton.disable() } }; { id: 'example2', index: 200, draw: function (baton) { //get the currenty process mail object var mail = baton.data; //use value set by 'example1' if(mail && mail.drawn && mail.drawn.subject) { //do something } } }; { id: 'example3', index: 300, draw: function (baton) { //wil not be executed if baton from 'disable example' is used } };
ensure
- ensure that submitted object is instanceof baton
- return obj if it's an instanceof baton
- return new baton instance where baton.data is extended by obj or obj.data (if exists)
var baton = ext.Baton.ensure(obj)
example
//new baton.data extended by object var baton = ext.Baton.ensure({ id: 2 })
Conclusion
As you can see, unlike adding functionality, customizing and modifying existing extensions is always more of a grey box operation and might incur some risks when updating the software. For example when replacing a certain functionality parts of the original functionality will have to be reimplemented, and all that extra code will have to be maintained in the future.
In essence extension points are better suited to integrating new functionality into the product rather than customizing existing functionality, but, when in a pinch or really wanting to change a certain part of the software, this is certainly a way to consider. At its most extreme use you could even disable all extensions for the mail detail view to register a set of your own extensions to completely change the way mails are displayed, at the cost of having to maintain your own detail view.
This wraps up our little tour of the OX App Suite extension point system. It is used to integrate new functionality into the OX App Suite and provides a system for 3rd party applications to become extensible themselves. It can be used to customize the existing UI at the cost of havint to know a bit more about the internals of our application. For now until more comprehensive documentation becomes available, look at the existing OX App Suite code to see concrete extensions and extension points in action.