Skip to content
This repository
tag: v1166
Fetching contributors…

Cannot retrieve contributors at this time

file 226 lines (134 sloc) 9.858 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
Guidelines for writing new Widgets
Mathieu Baudet <>
(C) MLstate 2010

asciidoc format: use 'asciidoc GUIDELINES' to compile into HTML (or not)

== Definitions ==

We distinguish two types of graphical elements in the OPA library :

1. passive elements, a.k.a. 'widgets', are elementary building blocks meant to be essentially stateless;

2. active elements, a.k.a. 'components', are meant to realize more elaborate tasks; for that purpose they generally embed a state, typically stored into a client-side session (sometime even linked to a server-side session and indirectly to the dababase).

As opposed to library modules, components and widgets are meant to impact the user interface.

CAUTION: Choosing the right design for a new graphical element may not be easy (but reading this guidelines should help you a lot).

== General design of widgets ==

Widgets are passive, that is, mostly stateless. The only permitted states are internal and display-oriented (e.g. for a dialog box, being closed or being open).

Widgets never use sessions, nor the database.

Widgets are installed in two steps:

1. Compute the initial xhtml of the widget
=> This computation is done by a pure function receiving a (reusable) configuration and a set of specific parameters as arguments.

2. Install the xhtml
=> This is done either by including the xhtml of the widget in the web page returned by the server, or by replacing some part of the clients's DOM by the widget.

== Client/server slicing ==

The creation of the initial xhtml must be feasible on both sides.

Once they are installed (unless they are provided with server-only functions) widgets should be entirely run on the client-side.

Default configurations must be available on both sides and the functions that they contain must be serializable.
=> In current version of OPA S3, this means that the functions must be defined at top-level and themselves available on both sides.

CAUTION: Please ensure these properties by testing your widget, e.g. by deconnecting the server after the page is loaded. For maximum coverability, test examples must generate the xhtml on the server's side. OPA experts may also use the compiler option +--dump-slicer+.

== Package and naming conventions ==

Widgets must be part of the package +widgets+ whose source code lies in +opa/stdlib/packages/widgets+.

Each widget must have its own directory in the repo and usually has one main public module.

The name of the package is *a priori* the basename of the widget in lowercase, as in +statusbar+.

For a better organization, later, during the consolidation phase, small widgets will be grouped in one package, and subpackages may be created.

The name of the main module of a widget must be the basename in lowercase except for the first letters of words, prefixed by a +W+, as in: +WStatusBar+.
The name of the main file of the package must be based on the package name, as in +statusbar.opa+.

== Configuration of widgets ==

Each widget +xxx+ should define a type +WXxx.config+ that is essentially a record providing the 'configuration parameters' of the widget. By configuration parameters we mean parameters that can (reasonably) be shared between several instances of the widgets.

Instance parameters, such as displayed values and DOM identifiers, must NOT be included in the structure WXxx.config.

Each widget should come with at least one default value named +default_config+.

== Management of DOM identifiers ==

Each instance of a widget is identified by a unique, global DOM identifier +id+ that must be recalled to every function of the API.

This +id+ denotes the location of DOM where the widget will be installed.

It is used as a prefix for all subsequent internal ids created by the component.

Unless specified otherwise, a widget may replace itself at runtime, by writing over its id.

== HTML styles and CSS classes ==

No CSS class names should be hard-coded in the source code.

Ideally, the general appearance of a widget should be specified using only the WStyler.styler(s) provided by the configuration. (N.B. generating functions may be used for complex widgets.)

However, for simplicity, it is admitted that the first version of a widget uses only classes instead of stylers. Even in this case, the class names MUST be prefixed by a user-provided string to prevent clashes between variants of the widget.

If necessary, a widget MAY use *internal classes* for storing a state into the DOM. These MUST use a user-provided string as a prefix.

=== Style-based v.s. CSS-based default configurations ===
For better usability, the default configuration of small or ``sparsely used" widgets should preferably instantiate the +WStyler.styler+(s) with styles instead of CSS class names.

However, the default appearance of big, or very common widgets should preferably be specified by a set of CSS class-names, parameterized by a prefix :
default_config_with_CSS(... CSS_class_prefix : string) : Xxx.config

In the latter case, of course a corresponding default css must be provided.

TIP: Use the function +Css.add_prefix_to_all_classes+ to add a prefix everywhere in your basic CSS.
CWeather.default_css(prefix : string) = Css.add_prefix_to_all_classes(CWeather.private_default_css, prefix)

=== Usage of a widget with CSS-based default configuration ===
css = CWeather.css("toto") ++ css

== Tests and examples ==

Tests and examples should be pushed in the repository reftester, within the directory +opa/stdlib/packages/widgets/xxx+.

CAUTION: Use the same branch as their widget, that is +master+.

The main test should be name +test_xxx.opa+ (if +xxx+ is the name of the widget in lower case).

== Minimal interface (required) ==

The minimal API of a widget includes

- HTML creation functions (in possibly various modes, no side effects)

- parsing functions (client-side only, only read access to the DOM are permitted)

The typical creation function is:

html(config : WXxx.config, id : string, action<i> : funtype<i>..., value : contenttype) : xhtml
// standard mode

Widgets with several distinct modes may define additional creation functions.
For instance, a readonly mode and an editable mode should be named as follows:
show(config : WXxx.config, id : string, action<i> : funtype<i>..., value : contenttype) : xhtml
// read-only mode

edit(config : WXxx.config, id : string, action<i> : funtype<i>..., value : contenttype) : xhtml
// editable mode

The functions above are functional but the ``fun-actions" are not. They may replace some elements of the DOM (include the +id+.)

For widget with an editable content, a typical parsing function is

parse(config : WXxx.config, id : string) : option(contenttype)
// read and parse the fields of the widget

This function will be client-side only.

== Typical usage of the minimal API ==

The typical way to install a new widget somewhere in the DOM is

- select an +id+ to install the widget and that can be used as a prefix

- create a config :
     config = { default_config with foo = myvalue }

- get the xhtml and install it
     html = show(config, id, ...)
     Dom.transform([#id <- html])

If you are building a bigger xhtml value in a functional way, you may use a span or a div, as in :
 <div id=#{id}>
   { show(default_config, id, ...) }

CAUTION: Whenever possible, it is advised to use the latter option to build the initial web-page of a server, since this makes content indexation possible. In any case, this option should be preferred for tests.

== Imperative interface (optional) ==

For efficiency reasons, state modifiers (e.g. +open+, +close+) may be provided for those widgets that embed a (small, display-oriented) internal state.

Two locations may be used to store the internal state of a widget:

- the closures of ``fun-actions''

- the widget's own HTML (encoding the state in classes).
The second option is a standard trick that is useful when the state of the widget must be retrieved from outside, e.g. to implement the functions +is_open+ and +is_close+.

In the case of a widget with a lot of content, content modifiers may also be provided to allow incremental update of the values inside the widget (i.e. without generating and installing the whole xhtml fragment).

Similarly as for the minimal API, the functions of the imperative API do not store the configuration of the widgets nor its DOM identifier. These arguments must be recalled when needed.

Example from the date picker:
   do_open(opened_class: string, id: string): void
This function does not need the configuration of the widget (so it is not asked). It expects a class name used to encode the fact that the widget is open, and its DOM id.

== Widgets core library ==

The helping module +WCore+ of the package +widgets.core+ contains a number of generic tools and type definition.

+WStyler.styler+ is unifying type for +css_class+ and

+WCore.make*+ are helping functions to install a widget somewhere in the DOM.

+WCore.add_binds+ is a utilitary function based on +Xhtml.add_binds+.
Something went wrong with that request. Please try again.