Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 227 lines (134 sloc) 9.877 kB
fccc685 Initial open-source release
MLstate authored
1 Guidelines for writing new Widgets
2 ==================================
3 Mathieu Baudet <mathieu.baudet@mlstate.com>
4 v1.2
5 (C) MLstate 2010
6
7 ////////////////////
8 asciidoc format: use 'asciidoc GUIDELINES' to compile into HTML (or not)
9 ////////////////////
10
11 == Definitions ==
12
13 We distinguish two types of graphical elements in the OPA library :
14
15 1. passive elements, a.k.a. 'widgets', are elementary building blocks meant to be essentially stateless;
16
17 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).
18
19 As opposed to library modules, components and widgets are meant to impact the user interface.
20
21 CAUTION: Choosing the right design for a new graphical element may not be easy (but reading this guidelines should help you a lot).
22
23
24 == General design of widgets ==
25
26 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).
27
28 Widgets never use sessions, nor the database.
29
30 Widgets are installed in two steps:
31
32 1. Compute the initial xhtml of the widget
33 => This computation is done by a pure function receiving a (reusable) configuration and a set of specific parameters as arguments.
34
35 2. Install the xhtml
36 => 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.
37
38
39 == Client/server slicing ==
40
41 The creation of the initial xhtml must be feasible on both sides.
42
43 Once they are installed (unless they are provided with server-only functions) widgets should be entirely run on the client-side.
44
45 Default configurations must be available on both sides and the functions that they contain must be serializable.
fe16af9 @OpaOnWindowsNow [feature] stdlib,opabsl: removing all S3 occurences
OpaOnWindowsNow authored
46 => This means that the functions must be defined at top-level and themselves available on both sides or that they should be obtained with @public_env.
fccc685 Initial open-source release
MLstate authored
47
48 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+.
49
50
51 == Package and naming conventions ==
52
53 Widgets must be part of the package +widgets+ whose source code lies in +opa/stdlib/packages/widgets+.
54
55 Each widget must have its own directory in the repo and usually has one main public module.
56
57 The name of the package is *a priori* the basename of the widget in lowercase, as in +statusbar+.
58
59 [NOTE]
60 For a better organization, later, during the consolidation phase, small widgets will be grouped in one package, and subpackages may be created.
61
62 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+.
63 The name of the main file of the package must be based on the package name, as in +statusbar.opa+.
64
65
66 == Configuration of widgets ==
67
68 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.
69
70 Instance parameters, such as displayed values and DOM identifiers, must NOT be included in the structure WXxx.config.
71
72 Each widget should come with at least one default value named +default_config+.
73
74
75 == Management of DOM identifiers ==
76
77 Each instance of a widget is identified by a unique, global DOM identifier +id+ that must be recalled to every function of the API.
78
79 This +id+ denotes the location of DOM where the widget will be installed.
80
81 It is used as a prefix for all subsequent internal ids created by the component.
82
83 Unless specified otherwise, a widget may replace itself at runtime, by writing over its id.
84
85
86 == HTML styles and CSS classes ==
87
88 No CSS class names should be hard-coded in the source code.
89
90 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.)
91
92 [NOTE]
93 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.
94
95 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.
96
97 === Style-based v.s. CSS-based default configurations ===
98 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.
99
100 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 :
101 ---------------------------
102 default_config_with_CSS(... CSS_class_prefix : string) : Xxx.config
103 ---------------------------
104
105 In the latter case, of course a corresponding default css must be provided.
106
107 TIP: Use the function +Css.add_prefix_to_all_classes+ to add a prefix everywhere in your basic CSS.
108 ---------------------------
109 CWeather.default_css(prefix : string) = Css.add_prefix_to_all_classes(CWeather.private_default_css, prefix)
110 ----------------------------
111
112
113 === Usage of a widget with CSS-based default configuration ===
114 ---------------------------------
115 css = CWeather.css("toto") ++ css
116 ---------------------------------
117
118 == Tests and examples ==
119
120 Tests and examples should be pushed in the repository reftester, within the directory +opa/stdlib/packages/widgets/xxx+.
121
122 CAUTION: Use the same branch as their widget, that is +master+.
123
124 The main test should be name +test_xxx.opa+ (if +xxx+ is the name of the widget in lower case).
125
126
127 == Minimal interface (required) ==
128
129 The minimal API of a widget includes
130
131 - HTML creation functions (in possibly various modes, no side effects)
132
133 - parsing functions (client-side only, only read access to the DOM are permitted)
134
135 The typical creation function is:
136
137 ---------------------------------
138 html(config : WXxx.config, id : string, action<i> : funtype<i>..., value : contenttype) : xhtml
139 // standard mode
140 ---------------------------------
141
142 [NOTE]
143 Widgets with several distinct modes may define additional creation functions.
144 For instance, a readonly mode and an editable mode should be named as follows:
145 ---------------------------------
146 show(config : WXxx.config, id : string, action<i> : funtype<i>..., value : contenttype) : xhtml
147 // read-only mode
148
149 edit(config : WXxx.config, id : string, action<i> : funtype<i>..., value : contenttype) : xhtml
150 // editable mode
151 ---------------------------------
152
153
154 [NOTE]
155 The functions above are functional but the ``fun-actions" are not. They may replace some elements of the DOM (include the +id+.)
156
157
158 For widget with an editable content, a typical parsing function is
159
160 ---------------------------------
161 parse(config : WXxx.config, id : string) : option(contenttype)
162 // read and parse the fields of the widget
163 ---------------------------------
164
165 This function will be client-side only.
166
167
168 == Typical usage of the minimal API ==
169
170 The typical way to install a new widget somewhere in the DOM is
171
172 - select an +id+ to install the widget and that can be used as a prefix
173
174 - create a config :
175 ---------------------------------
176 config = { default_config with foo = myvalue }
177 ---------------------------------
178
179 - get the xhtml and install it
180 ---------------------------------
181 html = show(config, id, ...)
182 Dom.transform([#id <- html])
183 ---------------------------------
184
185 If you are building a bigger xhtml value in a functional way, you may use a span or a div, as in :
186 ---------------------------------
187 <div id=#{id}>
188 { show(default_config, id, ...) }
189 </div>
190 ---------------------------------
191
192 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.
193
194
195 == Imperative interface (optional) ==
196
197 For efficiency reasons, state modifiers (e.g. +open+, +close+) may be provided for those widgets that embed a (small, display-oriented) internal state.
198
199 Two locations may be used to store the internal state of a widget:
200
201 - the closures of ``fun-actions''
202
203 - the widget's own HTML (encoding the state in classes).
204 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+.
205
206 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).
207
208 CAUTION:
209 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.
210
211 Example from the date picker:
212 ---------------------------------
213 do_open(opened_class: string, id: string): void
214 ---------------------------------
215 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.
216
217
218 == Widgets core library ==
219
220 The helping module +WCore+ of the package +widgets.core+ contains a number of generic tools and type definition.
221
222 +WStyler.styler+ is unifying type for +css_class+ and +Css.properties+.
223
224 +WCore.make*+ are helping functions to install a widget somewhere in the DOM.
225
226 +WCore.add_binds+ is a utilitary function based on +Xhtml.add_binds+.
Something went wrong with that request. Please try again.