Add two more lines.
[mkws-moved-to-github.git] / notes / developers.txt
1 Notes for developers
2
3 These notes are collected by Heikki, mostly from skype chats with Wolfram
4 and Mike. I collected them for my own use, but I hope they will turn out
5 to be helpful to anyone who needs to get started with mkws.
6
7 Environment
8 -----------
9
10 apt-get install yui-compressor
11 get nodejs, sudo make install, ln -s /usr/local/bin/npm ~/bin/npm if needed
12
13 cd .../mkws; make check
14
15
16 Apache
17 ------
18 You need to set up a local apache. 
19   * add 'mkws' in /etc/hosts to point to 127.0.0.2
20   * symlinked .../mkws/tools/apache2/mkws-heikki to /etc/apache/sites-available
21   * a2ensite mkws-heikki
22   * a2enmod rewrite
23   * a2enmod headers
24   * service apache2 reload
25   * Check that your browser sees somethig in http://mkws/ and 
26     http://mkws/jasmine-popup.html. If need be, enable javascript etc. 
27
28 Libraries
29 ---------
30
31 * We are using jquery as a browser indepent layer to access the dom, so we
32 don't have to worry about IE bugs.  Wolfram looked why we are using
33 jquery.json-2.4.js ... it turns out we needed it because the standard functions
34 in IE8 are broken.
35
36 * jasmine is a test framework (mkws dev). you will not use jasmine in a
37 production site.  the nice thing with the jasmine test framework is that it
38 will work with any browser. I can start a virtual machine with IE8, open the
39 test page, wait 3 seconds for success and shutdown windows.
40
41 * handlebar is a template engine
42
43
44
45 Include files
46 -------------
47
48 The manual says to include mkws-complete.js. This file is made by concatenating
49 a number of files (see Makefile). For us developers, it is easier to include the
50 raw files, as in
51
52     <script type="text/javascript" src="tools/htdocs/jquery-1.10.0.min.js"></script>
53     <script type="text/javascript" src="tools/htdocs/pz2.js"></script>
54     <script type="text/javascript" src="tools/htdocs/handlebars-v1.1.2.js"></script>
55     <script type="text/javascript" src="tools/htdocs/jquery.json-2.4.js"></script>
56     <script type="text/javascript" src="tools/htdocs/mkws.js"></script>
57
58 You can also include the css directly in your test page:
59     <style type="text/css">
60       #mkwsTermlists div.facet {
61       float:left;
62       width: 30%;
63       margin: 0.3em;
64       }
65       #mkwsStat {
66       text-align: right;
67       }
68     </style>
69
70
71 Most (all?) code work happens in mkws.js.
72
73
74 Unit tests
75 ----------
76
77 Tests are based on jasmine. a general description of jasmine is on
78 http://jasmine.github.io/1.3/introduction.html
79
80 If you want understand the test than you can look at mkws/test/spec/mkws-config.js
81 and mkws/test/spec/mkws-pazpar2.js . See also mkws/test/README.txt
82
83 The test scripts are included from the test page, for example
84 mkws/examples/htdocs/jasmine-popup.html has 
85 <script type="text/javascript" src="test/spec/mkws-pazpar2.js"></script>
86
87
88
89
90 Structure of mkws.js
91 --------------------
92 (This will soon be out of date, but should provide some kind of starting
93 point even then. This is taken directly from a Skype chat with Mike, where
94 he explained the whole thing.)
95
96 First page is just helper functions for the Handlebars template library, which we
97 use to generate some of the HTML output. (Down the line, we will use this more
98 heavilty -- right now it's only used for records).
99
100 Then we define the mkws object, which contains all global state -- which hopefully
101 there is not much of. It is one of only two objects we place in the global namespace:
102 the other is mkws_config, which is a hash supplied by the application if it wants
103 to override default configs.
104
105 Next is a very short function defined to allow us to publish and subscribe events.
106 That is not yet used: shifting much of the code to do so is a big part of what I
107 am working on right now.
108
109 Next, a very short stanza of code that just makes sure mkws_config is defined:
110 simple applications won't bother to define it at all since they override none
111 of the defaults.
112
113 Next, a factory method for making widget objects. At present this is trivial
114 because we are only now starting to need a representation of individual widgets
115 as JS objects. More of the functionality will get moved into these objects over
116 the next week.
117
118 Next, a factory method for making widget-team objects. This is where all the
119 awesomeness is at the moment. A team is a bunch of widgets that work together
120 to achieve a common goal, e.g. the search-box, search-button and results-pane
121 widgets.
122
123 HTML elements are defined as belonging to the same team if they have an
124 mkwsTeam_NAME class for the same NAME. You can have multiple teams (as in
125 two-teams.html that I linked to earlier) which are completely independent of
126 each other.
127
128 I guess you're familiar with the JS idiom where the factory function for a kind
129 of object also acts as a namespace where all the object's member-variables live,
130 invisible to the outside world? That's what we do here. All the member variables
131 have names of the form m_NAME.
132
133 Now I sugges you skip over all the team-object code for now -- we'll return to it
134 later. For now, page down to "// wrapper to call team() after page load" which is
135 the next thing after the end of that function (or class, if you like).
136
137 You're familiar with this JS idiom?
138   (function() { code ... })();
139 Runs the code immediately, but within its own namespace. That's what we do for
140 all the remaining code in mkws.js. In this case, we pass the jQuery object into
141 that namespace under the name `j' for reasons that are frankly opaque to me.
142
143 There's still a few places in the code where oddities live on, either from jsdemo
144 or from work Wolfram's done, where I don't really understand why it's that way
145 but I'm scared to change it. In this case, IIRC, it's something to do with
146 protecting our copy of the jQuery object, or something.
147
148 Aaanyway, within that namespaced area, where's what we do.
149
150 First, we set up the mkws.debug() function, which just logs to the console in a
151 form that doesn't explode IE. I have plans for this function, make it understand
152 debugging levels a bit like log4j or maybe more like yaz-log where there are
153 named logging types that are not in a sequence.
154
155 (You will notice that the teams have a debug() function which delegates to this
156 but adds some other useful team-specific stuff.)
157
158 Next up: the utility function mkws.handle_node_with_team(). We use a LOT of nodes
159 that have their team-name in a class (as in "mkwsTeam_NAME" outlined above).
160 All the utility does is parse out that team-name, and the widget-type, from the
161 classes, and pass them through to the callback.
162
163 mkws.resize_page() does what it says. Gets called when window-size changes, and
164 allows us to move the facers to the side or the bottom as the screen is wide or
165 narrow (e.g. when you turn your iPad 90 degrees)
166
167 (Aside: I thought we'd have to iterate over all teams to move their facet lists
168 but it turns out we don't: jQuery just Does The Right Thing if you call
169    $(".mkwsTermlistContainer1").hide();
170 or similar and there are multiple hits.)
171
172 Next come a bunch of JS functions that are invoked from the MKWS UIs --
173 swithching between target and record views, stepping through pages of results,
174 etc. All of these are team-specific, but the global code in the HTML can't
175 invoke a team's member function directly. So these stub functions just invoke
176 the relevant member of the appropriate team.
177
178 default_mkws_config() fills in the mkws_config structure from hardwired defaults.
179 This is the wrong way round: instead, whenever we want to find a config value, we
180 should default our way up a tree, starting with the individual widget's config,
181 falling back to the team's config if the widget doesn't define that value, then
182 the global config, and finally the default. I'll make that change once widget
183 objects are fully real.
184
185 authenticate_session() authenticates onto the SP when we're using it (rather
186 than raw pp2). It's a bit sellotape-and-string, to be honest, just does a wget.
187 It would be better if this was supported by pz2.js
188
189 run_auto_searches() is what makes pages like
190   http://example.indexdata.com/auto3.html
191 work. THere are two places it's invoked from. Either directly when all the HTML
192 has been set up if we're using raw pp2; or when SP authentication has been
193 completed if we're using that. As with the UI functions, it just delegates down
194 into the teams.
195
196 Finally, code that runs when the page has finished loading -- this is really
197 the main() function
198
199 The first thing it does is patch up the HTML so that old-style MKWS apps work
200 using new-style elements. This is the code you just fixed.
201
202 Straight after that, more fixup: elements that have an mkws* class but no
203 team are given an extra class mkwsTeam_AUTO. This is the ONLY thing that's special
204 about the team "AUTO" -- it has no other privileges.
205
206 Very near the end now: we walk through all nodes with an mkws* class, and create
207 the team and widget objects using the factories we described earlier. Jason is
208 worried this will be slow, hence the instrumentation. It's not :-)
209
210 Last of all: start things off! SP-auth if used, otherwise straight to the
211 auto-searches.
212
213
214 OK, want to plough into the team object?
215
216 ... bearing in mind that some of this should be moved out of the team into the
217 top-level code, and some other parts will be moved down into individual widgets
218 once we have them.
219
220 OK. So we start with a bunch of member variables for state. Many of them will be
221 hauntingly familiar to anyone who's worked on jsdemo :-)
222
223 A new one is m_debug_time, which is a structure used for keeping track of elapsed
224 time. It's nice: it lets debug messages for each time note how long it's been
225 since the last message in that same team, which means you can see how slow-ass
226 our network operations can be. That's implemented in debug(), which is the very
227 next thing in the file.
228
229 Then a bunch of code to do with setting initial values from defaults.
230
231 The stuff about languages is a great example of code that should be at the top
232 level, not in the team: it deals only with global objects, yet gets run n times
233 if there are n teams. (I am adding a ###-comment to this effect right now.)
234
235 Then we make the pz2 object, which is our only channel of communication with the
236 server outside of the HTTP GET hack in authenticate_session(). The callback
237 functions are all closures with access to this team's member variables. Are you
238 somewhat familiar with pz2.js already?
239
240 Well, it's all driven by callbacks. You register them at object-creation time;
241 then later when you call (say) m_paz.search(), it will invoke your my_onsearch()
242 function when the search-response arrives.
243
244 There are some oddities in the order things get done. For example, is m_perpage
245 set AFTER this object is created, rather than at the same time as its stablemate
246 m_sort up above? No reason. So plenty of tedious, error-prone, cleaning up of
247 this sort to be done.
248
249 Then come the pz2 callbacks. my_onshow() is an interesting representative. The
250 team-name is passed back by pz2 (which has used it as the SP window-ID to keep
251 sessions separate). I used to think it needed to be passed back like this so
252 the invoked callback functions could know what team they're being called for,
253 but now I realise they don't need to, because they're closures that can see
254 m_teamName. Oh well.
255
256 Indeed, you can see that my_onshow() doesn't even USE the teamName parameter.
257
258 Anyway, the point is to find specific elements that belong to the right team,
259 so they can be populated with relevant data. We do that for the pager, and of
260 course the record-list.
261
262 The meat of the record-list is filled in by invocations of renderSummary(),
263 which loads a Handlebars template and uses that to generate the HTML. Needless
264 to say, loadTemplate() caches compiled templates.
265
266 my_onstat() and my_onterm() do similar things -- you can figure out the details.
267
268 add_single_facet(), target_filtered() and others are uninteresting utility
269 functions.
270
271 my_onrecord() and my_onbytarget() are more of the same. There is some nastiness
272 in my_onrecord() to handle poping up a full-record div in the right place and
273 getting rid of any old ones. It doesn't work quite right.
274
275 onFormSubmitEventHandler() is more interesting. This is a JS event handle (for
276 form-submit, duh) but how does it know what team to work for? It checks `this'
277 to see that classes the HTML element has, and so finds the relevant mkwsTeam_NAME
278 class. Then it can fire newSearch() on the relevant team. But it now occurs to
279 me that this too is a closure so it doesn't need to do any of that. It can just
280 start the search in its own team.
281
282 [Why can that simplifying change be made here but not in, say, switchView()?
283 Because the former is assigned to a DOM object from within the JS code, so acts
284 as a closure; but the latter is invoked by a fragment of JS text which the user
285 clicks on, when there is no context.]
286
287 An oddity of newSearch(): it's not defined as
288     function newSearch()
289 like most of the other member functions, but
290     that.newSearch = function()
291 That's because, unlike most other member function, it gets explicitly invoked
292 on a team object:
293     mkws.teams[tname].newSearch(val);
294
295 But in fact that won't be necessary once I fix the invoker
296 (onFormSubmitEventHandler) to be aware of its own context, so that can simplify,
297 too.
298
299 The next interesting method is triggerSearch(). You can see that it assembles
300 the pp2filter and pp2limit values for the search invocation by walking the
301 array m_filters[], which is built by click on facets.
302
303 That's done in a slightly clumsy way. I might make a Filters object at some
304 point with some nice clean methods.
305
306 BTW., more unnecessary context-jockying with the windowid parameter. I don't
307 need it, I have m_teamName.
308
309 loadSelect() is another fine example of a method that appears in a random
310 place. It's just one of the HTML-generation helpers.
311
312 Now we come to a bunch of externally invoked functions, that.limitTarget() etc.
313 These are the meat that are called by the stubs in the top-level code -- remember
314 those one-liners?
315
316 They change state in various ways based on the user's clicks. The first four
317 ({un,}limit{Query,Target}()) do so by tweaking the m_filters[] array.
318
319 More HTML-generation helpers: redraw_navi(), drawPager() -- note the inconsistent
320 multi-word naming scheme
321
322 We are *completely* schizophrenic over whether to use camelCase or
323 underscore_separated
324
325 Then more UI functions (that.X, that.Y)
326
327 Anyway, onwards ... loadTemplate() you already know about.
328
329 defaultTemplate() is the hardwired defaults, used for applications that don't
330 define their own templates. For an application that does, see
331 http://example.indexdata.com/lolcat.html
332
333 As you can imagine, Lynn was WAY impressed by lolcat.html
334
335 mkws_html_all() is a big ugly function that generates a buttload of HTML.
336 Basically, it spots where you've used a magic name like <class="mkwsSearch">
337 and expands it to contain the relevant HTML.
338
339 Then it's just utility functions: parseQueryString() to read URL params,
340 mkws_set_lang() and friends generate more HTML.
341
342 All these HTML-generation functions should of course be together. Many of them
343 should also use Handlebars templates, so that clever applications can redefine
344 them. In places, too, they still need fixing to use CSS classes instead of inline
345 markup, <b> and suchlike.
346
347 that.run_auto_search() is interesting. It gets the term to search for from the
348 "autosearch" attribute on the relevant element, but there are special forms for
349 that string. !param!NAME gets the query from the URL parameter called NAME;
350 !path!NUM gets it from the NUM'th last component of the URL path. There may be
351 more of these in future.
352
353 Once it's got that it's a pretty straightforward invocation of newSearch()
354
355 M(string) yields the translation of string into the currently selected language.
356 We use it a lot in the HTML generation, and it's one part of that process that's
357 more cumbersome in Handlebars. The problem is that M() is a closure so it could
358 in principle know what the language of THIS team is, and it could be different
359 for different teams, even though that's not the case at the moment. But Handlebars
360 helpers are set once for all time, so they can't be team-specific, which means
361 they can only refer to the globally selected lan
362
363 And that, really, is the end of the team object (and so of mkws.js). TA-DAH!
364
365