Version 1 vs 10
Version 1 vs 10
Content Changes
Content Changes
WARNING: This is a wiki used as a scratch-book. The texts on this wiki are being worked on actively, and are considered to be drafts.
= What is the Roundcube Shell? =
The Roundcube Shell builds the basic environment for the Roundcube Next client. It's based on Ember.js and provides the following core functionality:
- Access to the data store and the JMAP library
- UI Toolkit which consists of
- Top level page layout + main navigation
- Reusable UI components
- Routing system and hooks
- Pub/sub system for inter-component communication
== JMAP data store ==
TBD.
== UI Toolkit ==
TBD.
== Routing system ==
TBD.
== Pub/sub system ==
TBD.
= User Stories =
TBD.
WARNING: This is a wiki used as a scratch-book. The texts on this wiki are being worked on actively, and are considered to be drafts.
# What is the Roundcube Shell?
The Roundcube Shell builds the basic environment for the Roundcube Next client. It's based on Ember.js and provides the following core functionality:
As an application:
- Authentication UI
- Top-level navigation between full-page apps
- User settings page (for simple account tweaks, password changes, etc)
As an API:
- Access to the data store via the JMAP library
- UI Toolkit with reusable components
- Routing system with hooks
- Pub/sub system for inter-component communication
# Design
## JMAP data store
### Server
Roundcube Next's persistence is intended to be chiefly [[ http://jmap.io/ | JMAP ]]-based. It ought to work with any server that speaks correct JMAP. At this point, the most complete server implementation is the jmap-proxy (written in perl). Promising upcoming implementations are within [[ http://james.apache.org/server/ | Apache James ]] and Cyrus.
For development, we use the Kolab-maintained perl proxy [[ https://kolab.org/blog/seigo/2015/10/19/jmap-proxy-docker-image | docker image ]].
### Client
Two client libraries have been evaluated, one being [[ https://github.com/jmapio/jmap-js | jmap-js ]], and the other being [[ https://github.com/linagora/jmap-client | jmap-client ]]. The former lays a strong emphasis on Overture-like code patterns, while the latter is a simple, well-tested ES6 library that works very well with Ember.
For development, we use jmap-client.
An instance of the API client should be made available as an Ember [[ http://guides.emberjs.com/v2.1.0/applications/services/ | service ]], which can be accessed by embedded apps.
## UI Toolkit
When working with Ember, most widgets are contained within [[ http://guides.emberjs.com/v1.10.0/components/ | Components ]]. Components which are expected to be repeatedly used within the various applications that Roundcube Next shall come with, ought to be provided as a reusable set by Roundcube Shell. This would range from simple things like buttons and labels to more complex components like list-views and editor toolbars. See a list of planned UI components [[roundcube-next/apps/#components | here]].
Having all components in one place lets us keep them organized and allows for an easy way to customize the look of the entire application consistently, making it very smooth for designers and developers to work together. Every time a new reusable component is created, it should be added to a living //styleguide//. For realizing this, we shall use [[ https://github.com/livingstyleguide/broccoli-livingstyleguide | broccoli-livingstyleguide ]], which is designed for ember-cli backed toolchains.
## Routing
Ember lets an application define a set of abstract routes, which map to `/`-delimited locations. Routes can be organized hierarchically. Suppose an plugin with the name `mail` has it's own special routes. It should be able to provide those in the following hypothetical manner:
```lang=js
var exports = {
name: 'mail',
routes: function () {
this.route('index', { path: '/' });
this.route('message', function () {
this.route('compose', { path: '/compose' });
this.route('detail', { path: '/:id' });
});
},
//...
};
```
This produces the following plugin-specific routes:
- `index` -> `/`
- `message.compose` -> `/message/compose`
- `message.detail` -> `/message/:id`
The shell could //mount// routes from plugins like this:
```lang=js
var plugins = loadSpecifiedPlugins();
Router.map(function() {
this.route('index', { path: '/' });
this.route('login', { path: '/login' });
for (var plugin in plugins) {
this.route(plugin.name, plugin.routes);
}
});
```
This would result in the roundcube app gaining the following routes:
- `mail.index` -> `/mail`
- `mail.message.compose` -> `/mail/message/compose`
- `mail.message.detail` -> `/mail/message/:id`
... which seems nice and modular. Activating a route will broadcast an event indicating the abstract route name as explained in the pub/sub section.
NOTE: Incidentally, Discourse also uses a similar routing technique for plugins.
## Pub/sub system
The shell also provides a global event emitter system where all components and apps can use to publish notifications and subscribe to messages from other components. Each component shall emit events
- whenever it reaches a point where the application state is changed
- where it makes sense to inform others about a certain action
- where feedback and/or additional/modified data is desired
This can begin with an `shell.init` event at application startup where apps can register themselves in the shell, define routes and push items to the main navigation.
The pub/sub is inspired by the [[ https://nodejs.org/api/events.html | Node.js events.EventEmitter ]] component and primarily provides three methods for public use: `on()`, `once()` and `emit()`. Bonus points if the registration of event listeners with `on()` supports wildcard event names such as `mail.message.*`.
NOTE: Shall the App instance itself provide the pub/sub methods or shall we define a specific (singleton) module that can be imported? Any suggestions for existing libraries to use for this?
(NOTE) **Aditya says:** See below - the shell can provide a (singleton) `Ember.Evented` service that can be imported by plugins.
(NOTE) **Thomas says:** Perfect. Sold!
### Works with Promises
Event listener should be able to work asynchronously and therefore return a Promise. The pub/sub system collects Promises returned by the registered listeners and returns a list of Promises to the emitter. Thus, whoever emits an event through the pub/sub system is responsible to handle returned promises and execute them. Example:
lang=js
Promise.all(pubsub.emit('foo.bar')).then(function(results) { /* continue */ }).catch(...);
(NOTE) **Aditya says:** I am not entirely sold on this. This promise pattern might be nice when listening for data coming over the jmap adapter, but probably not when it comes to intra-app communication. Especially when we have Ember-provided ways of doing the latter.
(NOTE) **Thomas says:** It's not a hard requirement but I can see use-cases where asynchronous execution can be useful. For example an encrypted message can be decrypted by the according plugin before rendering it. That might be an async procedure as we see it in the [[http://mailvelope.github.io/mailvelope | Mailvelope extension]].
### Works with Ember.Evented
`Ember.Evented` is how you do custom events in Ember. It lets you build a global message bus in a [[ http://www.thesoftwaresimpleton.com/blog/2015/04/27/event-bus/ | straightforward manner ]].
It also has a very similar API to `EventEmitter`, providing the following important methods:
- `on()` is like `on()`,
- `one()` is like `once()`,
- `off()` is like `removeListener()`
- `trigger()` is like `emit()`,
NOTE: Again, Discourse seems to be successfully [[ http://eviltrout.com/extending/#application-events | using ]] it for application-wide communication.
### Naming conventions
Since the proposed pub/sub system is one global message bus, it's important to use unique names for the events emitted to it. A few rules apply for composing event names:
# each component (or app) emitting an event, shall prefix the name with its own namespace (e.g. `shell.*`).
# the general classification of the emitting component and the entity name shall be reflected in the event name (e.g. `model.message` or `view.contactlist`).
# finally, choose sane names describing the action performed before the event is emitted.
# by default, events are emitted **after** a certain action was performed.
# if events are emitted //before// and //after// the according action, this shall be reflected in the event name with the `before` and `after` keywords.
Here are a few examples, illustrating the just listed conventions:
- `shell.init`
- `shell.ui.load`
- `mail.model.mailboxlist.load`
- `mail.model.message.flags.set`
- `mail.view.mailboxlist.render`
- `shell.account.settings.beforesave`
- `shell.account.settings.aftersave`
### Documentation
In order to publish a comprehensive list of events emitted throughout the application, each component shall describe the emitted events and the provided parameters in a jsdoc block according to the [[https://github.com/senchalabs/jsduck/wiki/@event | JSDuck spec]]:
/**
* @event mail.model.message.flags.set
* Emitted when message flags are updated
* @param {Mail.Model.Message} message The message object receiving flag updates
* @param {Object} flags Map of flag names and their new values
*/
# Plugins
Plugins are sub-applications like `mail`, `jabber`, `calendar`.
They are written as Ember Addons; most of the business logic resides within the `addon/` directory, and the parts of that which we want to be 'merged' into the main application tree of the shell are exposed in the `app/` directory.
Every addon "registers" it's functionality into the app using an Ember *initializer*, which is run before the application starts responding to user interaction.
NOTE: An idea to allow writing literal sub-applications has surfaced and is being excitedly worked upon, called [[ https://github.com/tomdale/rfcs/blob/master/active/0000-engines.md | Ember Engines ]]. Probably not something we will be able to use anytime soon.
WARNING: This is a wiki used as a scratch-book. The texts on this wiki are being worked on actively, and are considered to be drafts.
=# What is the Roundcube Shell? =
The Roundcube Shell builds the basic environment for the Roundcube Next client. It's based on Ember.js and provides the following core functionality:
As an application:
- Access to the data store and the JMAP library- Authentication UI
- UI Toolkit which consists of- Top-level navigation between full-page apps
- Top level- User settings page layout + main navigation(for simple account tweaks, password changes, etc)
- Reusable UIAs an API:
- Access to the data store via the JMAP library
- UI Toolkit with reusable components
- Routing system andwith hooks
- Pub/sub system for inter-component communication
== JMAP data store ==# Design
## JMAP data store
### Server
Roundcube Next's persistence is intended to be chiefly [[ http://jmap.io/ | JMAP ]]-based. It ought to work with any server that speaks correct JMAP. At this point, the most complete server implementation is the jmap-proxy (written in perl). Promising upcoming implementations are within [[ http://james.apache.org/server/ | Apache James ]] and Cyrus.
For development, we use the Kolab-maintained perl proxy [[ https://kolab.org/blog/seigo/2015/10/19/jmap-proxy-docker-image | docker image ]].
### Client
Two client libraries have been evaluated, one being [[ https://github.com/jmapio/jmap-js | jmap-js ]], and the other being [[ https://github.com/linagora/jmap-client | jmap-client ]]. The former lays a strong emphasis on Overture-like code patterns, while the latter is a simple, well-tested ES6 library that works very well with Ember.
For development, we use jmap-client.
An instance of the API client should be made available as an Ember [[ http://guides.emberjs.com/v2.1.0/applications/services/ | service ]], which can be accessed by embedded apps.
TBD.## UI Toolkit
== UI Toolkit ==When working with Ember, most widgets are contained within [[ http://guides.emberjs.com/v1.10.0/components/ | Components ]]. Components which are expected to be repeatedly used within the various applications that Roundcube Next shall come with, ought to be provided as a reusable set by Roundcube Shell. This would range from simple things like buttons and labels to more complex components like list-views and editor toolbars. See a list of planned UI components [[roundcube-next/apps/#components | here]].
TBD.Having all components in one place lets us keep them organized and allows for an easy way to customize the look of the entire application consistently, making it very smooth for designers and developers to work together. Every time a new reusable component is created, it should be added to a living //styleguide//. For realizing this, we shall use [[ https://github.com/livingstyleguide/broccoli-livingstyleguide | broccoli-livingstyleguide ]], which is designed for ember-cli backed toolchains.
## Routing
Ember lets an application define a set of abstract routes, which map to `/`-delimited locations. Routes can be organized hierarchically. Suppose an plugin with the name `mail` has it's own special routes. It should be able to provide those in the following hypothetical manner:
```lang=js
var exports = {
name: 'mail',
routes: function () {
this.route('index', { path: '/' });
this.route('message', function () {
this.route('compose', { path: '/compose' });
this.route('detail', { path: '/:id' });
});
},
//...
};
```
This produces the following plugin-specific routes:
- `index` -> `/`
- `message.compose` -> `/message/compose`
- `message.detail` -> `/message/:id`
The shell could //mount// routes from plugins like this:
```lang=js
var plugins = loadSpecifiedPlugins();
Router.map(function() {
this.route('index', { path: '/' });
this.route('login', { path: '/login' });
for (var plugin in plugins) {
this.route(plugin.name, plugin.routes);
}
});
```
This would result in the roundcube app gaining the following routes:
- `mail.index` -> `/mail`
- `mail.message.compose` -> `/mail/message/compose`
- `mail.message.detail` -> `/mail/message/:id`
... which seems nice and modular. Activating a route will broadcast an event indicating the abstract route name as explained in the pub/sub section.
NOTE: Incidentally, Discourse also uses a similar routing technique for plugins.
## Pub/sub system
The shell also provides a global event emitter system where all components and apps can use to publish notifications and subscribe to messages from other components. Each component shall emit events
- whenever it reaches a point where the application state is changed
- where it makes sense to inform others about a certain action
- where feedback and/or additional/modified data is desired
This can begin with an `shell.init` event at application startup where apps can register themselves in the shell, define routes and push items to the main navigation.
The pub/sub is inspired by the [[ https://nodejs.org/api/events.html | Node.js events.EventEmitter ]] component and primarily provides three methods for public use: `on()`, `once()` and `emit()`. Bonus points if the registration of event listeners with `on()` supports wildcard event names such as `mail.message.*`.
NOTE: Shall the App instance itself provide the pub/sub methods or shall we define a specific (singleton) module that can be imported? Any suggestions for existing libraries to use for this?
(NOTE) **Aditya says:** See below - the shell can provide a (singleton) `Ember.Evented` service that can be imported by plugins.
(NOTE) **Thomas says:** Perfect. Sold!
### Works with Promises
Event listener should be able to work asynchronously and therefore return a Promise. The pub/sub system collects Promises returned by the registered listeners and returns a list of Promises to the emitter. Thus, whoever emits an event through the pub/sub system is responsible to handle returned promises and execute them. Example:
lang=js
Promise.all(pubsub.emit('foo.bar')).then(function(results) { /* continue */ }).catch(...);
(NOTE) **Aditya says:** I am not entirely sold on this. This promise pattern might be nice when listening for data coming over the jmap adapter, but probably not when it comes to intra-app communication. Especially when we have Ember-provided ways of doing the latter.
(NOTE) **Thomas says:** It's not a hard requirement but I can see use-cases where asynchronous execution can be useful. For example an encrypted message can be decrypted by the according plugin before rendering it. That might be an async procedure as we see it in the [[http://mailvelope.github.io/mailvelope | Mailvelope extension]].
### Works with Ember.Evented
`Ember.Evented` is how you do custom events in Ember. It lets you build a global message bus in a [[ http://www.thesoftwaresimpleton.com/blog/2015/04/27/event-bus/ | straightforward manner ]].
It also has a very similar API to `EventEmitter`, providing the following important methods:
- `on()` is like `on()`,
- `one()` is like `once()`,
- `off()` is like `removeListener()`
- `trigger()` is like `emit()`,
NOTE: Again, Discourse seems to be successfully [[ http://eviltrout.com/extending/#application-events | using ]] it for application-wide communication.
### Naming conventions
Since the proposed pub/sub system is one global message bus, it's important to use unique names for the events emitted to it. A few rules apply for composing event names:
# each component (or app) emitting an event, shall prefix the name with its own namespace (e.g. `shell.*`).
# the general classification of the emitting component and the entity name shall be reflected in the event name (e.g. `model.message` or `view.contactlist`).
# finally, choose sane names describing the action performed before the event is emitted.
# by default, events are emitted **after** a certain action was performed.
# if events are emitted //before// and //after// the according action, this shall be reflected in the event name with the `before` and `after` keywords.
Here are a few examples, illustrating the just listed conventions:
- `shell.init`
- `shell.ui.load`
- `mail.model.mailboxlist.load`
- `mail.model.message.flags.set`
- `mail.view.mailboxlist.render`
- `shell.account.settings.beforesave`
- `shell.account.settings.aftersave`
### Documentation
In order to publish a comprehensive list of events emitted throughout the application, each component shall describe the emitted events and the provided parameters in a jsdoc block according to the [[https://github.com/senchalabs/jsduck/wiki/@event | JSDuck spec]]:
/**
* @event mail.model.message.flags.set
* Emitted when message flags are updated
* @param {Mail.Model.Message} message The message object receiving flag updates
* @param {Object} flags Map of flag names and their new values
*/
== Routing system ==# Plugins
TBDPlugins are sub-applications like `mail`, `jabber`, `calendar`.
== Pub/sub system ==They are written as Ember Addons; most of the business logic resides within the `addon/` directory, and the parts of that which we want to be 'merged' into the main application tree of the shell are exposed in the `app/` directory.
TBDEvery addon "registers" it's functionality into the app using an Ember *initializer*, which is run before the application starts responding to user interaction.
= User Stories =NOTE: An idea to allow writing literal sub-applications has surfaced and is being excitedly worked upon, called [[ https://github.com/tomdale/rfcs/blob/master/active/0000-engines.md | Ember Engines ]]. Probably not something we will be able to use anytime soon.
TBD.