5390dd7293ad0a2632311bcbb13e785e67273300
3 On page load, the SortableManager:
5 - Rips out all of the elements with the mochi-example class.
6 - Finds the elements with the mochi-template class and saves them for
7 later parsing with "MochiTAL".
8 - Finds the anchor tags with the mochi:dataformat attribute and gives them
9 onclick behvaiors to load new data, using their href as the data source.
10 This makes your XML or JSON look like a normal link to a search engine
11 (or javascript-disabled browser).
12 - Clones the thead element from the table because it will be replaced on each
14 - Sets up a default sort key of "domain_name" and queues a load of the json
18 On data load, the SortableManager:
20 - Parses the table data from the document (columns list, rows list-of-lists)
21 and turns them into a list of [{column:value, ...}] objects for easy sorting
22 and column order stability.
23 - Chooses the default (or previous) sort state and triggers a sort request
28 - Replaces the cloned thead element with a copy of it that has the sort
29 indicator (↑ or ↓) for the most recently sorted column (matched up
30 to the first field in the th's mochi:sortcolumn attribute), and attaches
31 onclick, onmousedown, onmouseover, onmouseout behaviors to them. The second
32 field of mochi:sortcolumn attribute is used to perform a non-string sort.
33 - Performs the sort on the domains list. If the second field of
34 mochi:sortcolumn was not "str", then a custom function is used and the
35 results are stored away in a __sort__ key, which is then used to perform the
36 sort (read: shwartzian transform).
37 - Calls processMochiTAL on the page, which finds the mochi-template sections
38 and then looks for mochi:repeat and mochi:content attributes on them, using
43 processMochiTAL
= function (dom
, data
) {
46 A TAL-esque template attribute language processor,
47 including content replacement and repeat
51 // nodeType == 1 is an element, we're leaving
53 if (dom
.nodeType
!= 1) {
57 // duplicate this element for each item in the
58 // given list, and then process the duplicated
59 // element again (sans mochi:repeat tag)
60 attr
= getAttribute(dom
, "mochi:repeat");
62 dom
.removeAttribute("mochi:repeat");
63 var parent
= dom
.parentNode
;
64 attr
= attr
.split(" ");
66 var lst
= valueForKeyPath(data
, attr
[1]);
70 for (var i
= 0; i
< lst
.length
; i
++) {
72 var newDOM
= dom
.cloneNode(true);
73 processMochiTAL(newDOM
, data
);
74 parent
.insertBefore(newDOM
, dom
);
76 parent
.removeChild(dom
);
79 // do content replacement if there's a mochi:content attribute
81 attr
= getAttribute(dom
, "mochi:content");
83 dom
.removeAttribute("mochi:content");
84 replaceChildNodes(dom
, valueForKeyPath(data
, attr
));
87 // we make a shallow copy of the current list of child nodes
88 // because it *will* change if there's a mochi:repeat in there!
89 var nodes
= list(dom
.childNodes
);
90 for (var i
= 0; i
< nodes
.length
; i
++) {
91 processMochiTAL(nodes
[i
], data
);
95 mouseOverFunc
= function () {
96 addElementClass(this, "over");
99 mouseOutFunc
= function () {
100 removeElementClass(this, "over");
103 ignoreEvent
= function (ev
) {
104 if (ev
&& ev
.preventDefault
) {
106 ev
.stopPropagation();
107 } else if (typeof(event
) != 'undefined') {
108 event
.cancelBubble
= false;
109 event
.returnValue
= false;
114 "str": operator
.identity
,
115 "istr": function (s
) { return s
.toLowerCase(); },
119 getAttribute
= function (dom
, key
) {
121 return dom
.getAttribute(key
);
127 datatableFromXMLRequest
= function (req
) {
130 This effectively converts domains.xml to the
131 same form as domains.json
134 var xml
= req
.responseXML
;
135 var nodes
= xml
.getElementsByTagName("column");
136 var rval
= {"columns": map(scrapeText
, nodes
)};
138 nodes
= xml
.getElementsByTagName("row")
139 for (var i
= 0; i
< nodes
.length
; i
++) {
140 var cells
= nodes
[i
].getElementsByTagName("cell");
141 rows
.push(map(scrapeText
, cells
));
147 loadFromDataAnchor
= function (ev
) {
149 var format
= this.getAttribute("mochi:dataformat");
150 var href
= this.href
;
151 sortableManager
.loadFromURL(format
, href
);
154 valueForKeyPath
= function (data
, keyPath
) {
155 var chunks
= keyPath
.split(".");
156 while (chunks
.length
&& data
) {
157 data
= data
[chunks
.shift()];
163 SortableManager
= function () {
165 this.thead_proto
= null;
167 this.deferred
= null;
175 SortableManager
.prototype = {
177 "initialize": function () {
178 // just rip all mochi-examples out of the DOM
179 var examples
= getElementsByTagAndClassName(null, "mochi-example");
180 while (examples
.length
) {
181 swapDOM(examples
.pop(), null);
183 // make a template list
184 var templates
= getElementsByTagAndClassName(null, "mochi-template");
185 for (var i
= 0; i
< templates
.length
; i
++) {
186 var template
= templates
[i
];
187 var proto
= template
.cloneNode(true);
188 removeElementClass(proto
, "mochi-template");
189 this.templates
.push({
194 // set up the data anchors to do loads
195 var anchors
= getElementsByTagAndClassName("a", null);
196 for (var i
= 0; i
< anchors
.length
; i
++) {
197 var node
= anchors
[i
];
198 var format
= getAttribute(node
, "mochi:dataformat");
200 node
.onclick
= loadFromDataAnchor
;
204 // to find sort columns
205 this.thead
= getElementsByTagAndClassName("thead", null)[0];
206 this.thead_proto
= this.thead
.cloneNode(true);
208 this.sortkey
= "domain_name";
209 this.loadFromURL("json", "domains.json");
212 "loadFromURL": function (format
, url
) {
213 log('loadFromURL', format
, url
);
216 this.deferred
.cancel();
218 if (format
== "xml") {
220 mimeType
: 'text/xml',
221 headers
: {Accept
: 'text/xml'}
223 d
.addCallback(datatableFromXMLRequest
);
224 } else if (format
== "json") {
225 d
= loadJSONDoc(url
);
227 throw new TypeError("format " + repr(format
) + " not supported");
229 // keep track of the current deferred, so that we can cancel it
232 // on success or error, remove the current deferred because it has
233 // completed, and pass through the result or error
234 d
.addBoth(function (res
) {
235 self
.deferred
= null;
236 log('loadFromURL success');
239 // on success, tag the result with the format used so we can display
241 d
.addCallback(function (res
) {
245 // call this.initWithData(data) once it's ready
246 d
.addCallback(this.initWithData
);
247 // if anything goes wrong, except for a simple cancellation,
248 // then log the error and show the logger
249 d
.addErrback(function (err
) {
250 if (err
instanceof CancelledError
) {
254 logger
.debuggingBookmarklet();
259 "initWithData": function (data
) {
262 Initialize the SortableManager with a table object
266 // reformat to [{column:value, ...}, ...] style as the domains key
268 var rows
= data
.rows
;
269 var cols
= data
.columns
;
270 for (var i
= 0; i
< rows
.length
; i
++) {
273 for (var j
= 0; j
< cols
.length
; j
++) {
274 domain
[cols
[j
]] = row
[j
];
276 domains
.push(domain
);
278 data
.domains
= domains
;
280 // perform a sort and display based upon the previous sort state,
281 // defaulting to an ascending sort if this is the first sort
282 var order
= this.sortState
[this.sortkey
];
283 if (typeof(order
) == 'undefined') {
286 this.drawSortedRows(this.sortkey
, order
, false);
290 "onSortClick": function (name
) {
293 Return a sort function for click events
296 // save ourselves from doing a bind
298 // on click, flip the last sort order of that column and sort
300 log('onSortClick', name
);
301 var order
= self
.sortState
[name
];
302 if (typeof(order
) == 'undefined') {
303 // if it's never been sorted by this column, sort ascending
305 } else if (self
.sortkey
== name
) {
306 // if this column was sorted most recently, flip the sort order
307 order
= !((typeof(order
) == 'undefined') ? false : order
);
309 self
.drawSortedRows(name
, order
, true);
313 "drawSortedRows": function (key
, forward
, clicked
) {
316 Draw the new sorted table body, and modify the column headers
320 log('drawSortedRows', key
, forward
);
322 // save it so we can flip next time
323 this.sortState
[key
] = forward
;
327 // setup the sort columns
328 var thead
= this.thead_proto
.cloneNode(true);
329 var cols
= thead
.getElementsByTagName("th");
330 for (var i
= 0; i
< cols
.length
; i
++) {
332 var sortinfo
= getAttribute(col
, "mochi:sortcolumn").split(" ");
333 var sortkey
= sortinfo
[0];
334 col
.onclick
= this.onSortClick(sortkey
);
335 col
.onmousedown
= ignoreEvent
;
336 col
.onmouseover
= mouseOverFunc
;
337 col
.onmouseout
= mouseOutFunc
;
338 // if this is the sorted column
339 if (sortkey
== key
) {
340 sortstyle
= sortinfo
[1];
341 // \u2193 is down arrow, \u2191 is up arrow
342 // forward sorts mean the rows get bigger going down
343 var arrow
= (forward
? "\u2193" : "\u2191");
344 // add the character to the column header
345 col
.appendChild(SPAN(null, arrow
));
351 this.thead
= swapDOM(this.thead
, thead
);
353 // apply a sort transform to a temporary column named __sort__,
354 // and do the sort based on that column
358 var sortfunc
= SortTransforms
[sortstyle
];
360 throw new TypeError("unsupported sort style " + repr(sortstyle
));
362 var domains
= this.data
.domains
;
363 for (var i
= 0; i
< domains
.length
; i
++) {
364 var domain
= domains
[i
];
365 domain
.__sort__
= sortfunc(domain
[key
]);
368 // perform the sort based on the state given (forward or reverse)
369 var cmp
= (forward
? keyComparator
: reverseKeyComparator
);
370 domains
.sort(cmp("__sort__"));
372 // process every template with the given data
373 // and put the processed templates in the DOM
374 for (var i
= 0; i
< this.templates
.length
; i
++) {
375 log('template', i
, template
);
376 var template
= this.templates
[i
];
377 var dom
= template
.template
.cloneNode(true);
378 processMochiTAL(dom
, this.data
);
379 template
.node
= swapDOM(template
.node
, dom
);
387 // create the global SortableManager and initialize it on page load
388 sortableManager
= new SortableManager();
389 addLoadEvent(sortableManager
.initialize
);
391 // rewrite the view-source links
392 addLoadEvent(function () {
393 var elems
= getElementsByTagAndClassName("A", "view-source");
394 var page
= "ajax_tables/";
395 for (var i
= 0; i
< elems
.length
; i
++) {
397 var href
= elem
.href
.split(/\//).pop();
398 elem
.target
= "_blank";
399 elem
.href
= "../view-source/view-source.html#" + page
+ href
;