1 % The MasterKey Widget Set developer's guide
9 This manual is for people who want to build the widget set from
10 source, develop the widget set's core code, or (more likely) create
11 their own widgets as extensions to the main set.
13 Those who want to use existing widgets should read
14 [The MKWS manual: embedded metasearching with the MasterKey Widget
15 Set](mkws-manual.html) instead.
18 Required development tools
19 ==========================
21 If you are building the widget set, you will need the following Debian
22 packages (or their equivalents on your operating system):
24 $ sudo apt-get install curl git make unzip apache2 \
25 pandoc yui-compressor libbsd-resource-perl
27 You also need Node.js, but unfortunately the `node-js` package is not
28 available for Debian wheezy. You can either get it from
29 wheezy-backports or download the source from
30 http://nodejs.org/download/ and build it yourself. You need both Node
31 itself and its package manager NPM: `make install` puts them into
41 Development with MKWS consists primarily of defining new types of
42 widgets. These can interact with the core functionality is several
45 You create a new widget type by calling the `mkws.registerWidgetType`
46 function, passing in the widget name and a function. The name is used
47 to recognise HTML elements as being widgets of this type -- for
48 example, if you register a `Foo` widget, elements like
49 `<div class="mkwsFoo">` will be widgets of this type.
51 The function promotes a bare widget object (passed as `this`) into a
52 widget of the appropriate type. MKWS doesn't use classes or explicit
53 prototypes: it just makes objects that have the necessary
54 behaviours. There are _no_ behaviours that Widgets are obliged to
55 provide: you can make a doesn't-do-anything-at-all widget if you like:
57 mkws.registerWidgetType('Sluggard', function() {});
59 More commonly, widgets will subscribe to one or more events, so that
60 they're notified when something interesting happens. For example, the
61 `Log` widget asks to be notified when a `log` event happens, and
62 appends the logged message to its node, as follows:
64 mkws.registerWidgetType('Log', function() {
67 this.team.queue("log").subscribe(function(teamName, timestamp, message) {
68 $(that.node).append(teamName + ": " + timestamp + message + "<br/>");
72 This simple widget illustrates several important points:
74 * The base widget object (`this`) has several baked-in properties and
75 methods that are available to individual widgets. These include
76 `this.team` (the team that this widget is a part of) and `this.node`
77 (the DOM element of the widget). See below for a full list.
79 * The team object (`this.team`) also has baked-in properties and
80 methods. These include the `queue` function, which takes an event-name
81 as its argument. See below for a full list.
83 * You can add functionality to a widget by subscribing it to an
84 event's queue using `this.team.queue("EVENT").subscribe`. The
85 argument is a function which is called whenever the event is
86 published. The arguments to the function are different for different
89 * As with so much JavaScript programming, the value of the special
90 variable `this` is lost inside the `subscribez` callback function,
91 so it must be saved if it's to be used inside that callback
92 (typically as a local variable named `that`).
95 Widget specialisation (inheritance)
96 -----------------------------------
98 Many widgets are simple specialisations of existing widgets. For
99 example, the `Record` widget is the same as the `Records` widget
100 except that it defaults to displaying a single record. It's defined as
103 mkws.registerWidgetType('Record', function() {
104 mkws.promotionFunction('Records').call(this);
105 if (!this.config.maxrecs) this.config.maxrecs = 1;
108 Remember that when a promotion function is called, it's passed a base
109 widget object that's not specialised for any particular task. To make
110 a specialised widget, you first promote that base widget into the type
111 that you want to specialise from -- in this case, `Records` -- using
112 the promotion function that's been registered for that type.
114 Once this has been done, the specialisations can be introduced. In
115 this case, it's a very simple matter of changing the `maxrecs`
116 configuration setting to 1 unless it's already been given an explicit
117 value. (That would occur if the HTML used an element like `<div
118 class="mkwsRecord" maxrecs="2">`, though it's not obvious why anyone
126 Widget properties and methods
127 -----------------------------
129 The following properties and methods exist in the bare widget object
130 that is passed into `registerWidgetType`'s callback function, and can
131 be used by the derived widget.
133 * `String this.type` --
134 A string containing the type of the widget.
136 * `Team this.team` --
137 The team object to which this widget belongs. The team has
138 several additional important properties and methods, described
141 * `DOMElement this.node` --
142 The DOM element of the widget
144 * `Hash this.config` --
145 A table of configuration values for the widget. This table
146 inherits missing values from the team's configuration, which
147 in turn inherits from the top-level MKWS configuration, which
148 inherits from the default configuration. Instances of widgets
149 in HTML can set configuration items as HTML attributes: for
150 example, the HTML element
151 `<div class="mkwsRecords" maxrecs="10">`.
152 creates a widget for which `this.config.maxrecs` is set to 10.
154 * `String this.toString()` --
155 A function returning a string that briefly names this
156 widget. Can be useful in logging.
158 * `Void this.log(string)` --
159 A function to log a string for debugging purposes. The string
160 is written on the browser console, and also published to any
161 subcribers to the `log` event.
163 * `String this.value()` --
164 A function returning the value of the widget's HTML element.
166 * `VOID autosearch()` --
167 Registers that this kind of widget is one that requires an
168 automatic search to be run for it if an `autosearch` attribute
169 is provided on the HTML element. This is appropriate for
170 widgets such as `Records` and `Facet` that display some part
173 * `VOID hideWhenNarrow()` --
174 Registers that this widget should hide itself when the page
175 becomes "narrow" -- that is, fewer pixels in width that the
176 threshhold value specified by the top-level configuration item
177 `responsive_design_width`. Should be used for "unimportant"
178 widgets that can be omitted from the mobile version of a site.
181 TODO: either document this or remove it from the API.
183 * `subwidget(type, overrides, defaults)` --
184 Returns the HTML of a subwidget of the specified type, which
185 can then be inserted into the widget using the
186 `this.node.html` function. The subwidget is given the same
187 attributes at the parent widget that invokes this function,
188 except where overrides are passed in. If defaults are also
189 provided, then these are used when the parent widget provides
190 no values. Both the `overrides` and `defaults` arguments are
191 hashes: the latter is optional.
193 See for example the `Credo` widget defined in the example
194 area's `mkws-widget-credo.js` file. This uses several
195 invocations of `subwidget` to create a complex compound widget
196 with numerous text, facet and image panes. TODO: rename this
197 widget and everything related to it.
199 In addition to these properties and methods of the bare widget object,
200 some kinds of specific widget add other properties of their own. For
201 example, the `Builder` widget uses a `callback` property as the
202 function that it use to publish the widget definition that it
203 constructs. This defaults to the builtin function `alert`, but can be
204 overridden by derived widgets such as `ConsoleBuilder`.
210 Since the team object is supposed to be opaque to widgets, all access
211 is via the following API methods rather than direct access to
214 * `String team.name()`
215 * `Bool team.submitted()`
216 * `Num team.perpage()`
217 * `Num team.totalRecordCount()`
218 * `Num team.currentPage();`
219 * `String team.currentRecordId()`
220 * `String team.currentRecordData()`
222 These are all simple accessor functions that provide the ability to
223 read properties of the team.
225 * `Array team.filters()` --
226 Another accessor function, providing access to the array of
227 prevailing filters (which narrow the search results by means
228 of Pazpar2 filters and limits). This is really too complicated
229 an object for the widgets to be given access to, but it's
230 convenient to do it this way. If you must insist on using
231 this, see the `Navi` widget, which is the only place it's used.
233 * `Bool team.targetFiltered(targetId)` --
234 Indicates whether the specified target has been filtered by
235 selection as a facet. This is used only by the `Facet` widget,
236 and there is probably no reason for you to use it.
238 * `Hash team.config()` --
239 Access to the team's configuration settings. There is almost
240 certainly no reason to use this: the settings that haven't
241 been overridden are accessible via `this.config`.
243 * `Void team.set_sortOrder(string)`, `Void team.set_perpage(number)` --
244 "Setter" functions for the team's sortOrder and perpage
245 functions. Unlikely to be needed outside of the `Sort` and
248 * `Queue team.queue(eventName)` --
249 Returns the queue associated with the named event: this can be
250 used to subscribe to the event (or more rarely to publish it).
252 * `Void team.newSearch(query, sortOrder, maxrecs, perpage, limit, targets, targetfilter)` --
253 Starts a new search with the specified parameters. All but the
254 query may be omitted, in which case the prevailing defaults
257 * `Void team.reShow()` --
258 Using the existing search, re-shows the result records after a
259 change in sort-order, per-page count, etc.
261 * `String team.recordElementId(recordId)` --
262 Utility function for converting a record identifer (returned
263 from Pazpar2) into a version suitable for use as an HTML
266 * `String team.renderDetails(recordData)` --
267 Utility function returns an HTML rendering of the record
268 represented by the specified data.
270 * `Template team.loadTemplate(templateName)` --
271 Loads (or retrieves from cache) the named Handlebars template,
272 and returns it in a form that can be invoked as a function,
275 Some of these methods either (A) are really too low-level and should
276 not be exposed, or (B) should be widget-level methods. The present
277 infelicities reflect the fact that some code that rightly belongs in
278 widgets is still in the team. When we finish migrating it, the widget
279 API should get simpler.
285 TODO: list of events that can be usefully subscribed to.
290 Copyright (C) 2013-2014 Index Data ApS. <http://indexdata.com>