ajax_tables.js
375 lines
| 12.1 KiB
| application/javascript
|
JavascriptLexer
Brian E Granger
|
r1234 | // This is from the MochiKit ajax_tables example | ||
/* | ||||
On page load, the SortableManager: | ||||
- Rips out all of the elements with the mochi-example class. | ||||
- Finds the elements with the mochi-template class and saves them for | ||||
later parsing with "MochiTAL". | ||||
- Finds the anchor tags with the mochi:dataformat attribute and gives them | ||||
onclick behvaiors to load new data, using their href as the data source. | ||||
This makes your XML or JSON look like a normal link to a search engine | ||||
(or javascript-disabled browser). | ||||
- Clones the thead element from the table because it will be replaced on each | ||||
sort. | ||||
- Sets up a default sort key of "object_name" and queues a load of the json | ||||
document. | ||||
On data load, the SortableManager: | ||||
- Parses the table data from the document (columns list, rows list-of-lists) | ||||
and turns them into a list of [{column:value, ...}] objects for easy sorting | ||||
and column order stability. | ||||
- Chooses the default (or previous) sort state and triggers a sort request | ||||
On sort request: | ||||
- Replaces the cloned thead element with a copy of it that has the sort | ||||
indicator (↑ or ↓) for the most recently sorted column (matched up | ||||
to the first field in the th's mochi:sortcolumn attribute), and attaches | ||||
onclick, onmousedown, onmouseover, onmouseout behaviors to them. The second | ||||
field of mochi:sortcolumn attribute is used to perform a non-string sort. | ||||
- Performs the sort on the objects list. If the second field of | ||||
mochi:sortcolumn was not "str", then a custom function is used and the | ||||
results are stored away in a __sort__ key, which is then used to perform the | ||||
sort (read: shwartzian transform). | ||||
- Calls processMochiTAL on the page, which finds the mochi-template sections | ||||
and then looks for mochi:repeat and mochi:content attributes on them, using | ||||
the data object. | ||||
*/ | ||||
processMochiTAL = function (dom, data) { | ||||
/*** | ||||
A TAL-esque template attribute language processor, | ||||
including content replacement and repeat | ||||
***/ | ||||
// nodeType == 1 is an element, we're leaving | ||||
// text nodes alone. | ||||
if (dom.nodeType != 1) { | ||||
return; | ||||
} | ||||
var attr; | ||||
// duplicate this element for each item in the | ||||
// given list, and then process the duplicated | ||||
// element again (sans mochi:repeat tag) | ||||
attr = getAttribute(dom, "mochi:repeat"); | ||||
if (attr) { | ||||
dom.removeAttribute("mochi:repeat"); | ||||
var parent = dom.parentNode; | ||||
attr = attr.split(" "); | ||||
var name = attr[0]; | ||||
var lst = valueForKeyPath(data, attr[1]); | ||||
if (!lst) { | ||||
return; | ||||
} | ||||
for (var i = 0; i < lst.length; i++) { | ||||
data[name] = lst[i]; | ||||
var newDOM = dom.cloneNode(true); | ||||
processMochiTAL(newDOM, data); | ||||
parent.insertBefore(newDOM, dom); | ||||
} | ||||
parent.removeChild(dom); | ||||
return; | ||||
} | ||||
// do content replacement if there's a mochi:content attribute | ||||
// on the element | ||||
attr = getAttribute(dom, "mochi:content"); | ||||
if (attr) { | ||||
dom.removeAttribute("mochi:content"); | ||||
replaceChildNodes(dom, valueForKeyPath(data, attr)); | ||||
return; | ||||
} | ||||
// we make a shallow copy of the current list of child nodes | ||||
// because it *will* change if there's a mochi:repeat in there! | ||||
var nodes = list(dom.childNodes); | ||||
for (var i = 0; i < nodes.length; i++) { | ||||
processMochiTAL(nodes[i], data); | ||||
} | ||||
}; | ||||
mouseOverFunc = function () { | ||||
addElementClass(this, "over"); | ||||
}; | ||||
mouseOutFunc = function () { | ||||
removeElementClass(this, "over"); | ||||
}; | ||||
ignoreEvent = function (ev) { | ||||
if (ev && ev.preventDefault) { | ||||
ev.preventDefault(); | ||||
ev.stopPropagation(); | ||||
} else if (typeof(event) != 'undefined') { | ||||
event.cancelBubble = false; | ||||
event.returnValue = false; | ||||
} | ||||
}; | ||||
SortTransforms = { | ||||
"str": operator.identity, | ||||
"istr": function (s) { return s.toLowerCase(); }, | ||||
/* "isoDate": isoDate*/ | ||||
}; | ||||
getAttribute = function (dom, key) { | ||||
try { | ||||
return dom.getAttribute(key); | ||||
} catch (e) { | ||||
return null; | ||||
} | ||||
}; | ||||
loadFromDataAnchor = function (ev) { | ||||
ignoreEvent(ev); | ||||
var format = this.getAttribute("mochi:dataformat"); | ||||
var href = this.href; | ||||
sortableManager.loadFromURL(format, href); | ||||
}; | ||||
valueForKeyPath = function (data, keyPath) { | ||||
var chunks = keyPath.split("."); | ||||
while (chunks.length && data) { | ||||
data = data[chunks.shift()]; | ||||
} | ||||
return data; | ||||
}; | ||||
SortableManager = function () { | ||||
this.thead = null; | ||||
this.thead_proto = null; | ||||
this.tbody = null; | ||||
this.deferred = null; | ||||
this.columns = []; | ||||
this.rows = []; | ||||
this.templates = []; | ||||
this.sortState = {}; | ||||
bindMethods(this); | ||||
}; | ||||
SortableManager.prototype = { | ||||
"initialize": function (prefix, sortkey) { | ||||
// just rip all mochi-examples out of the DOM | ||||
var examples = getElementsByTagAndClassName(null, prefix+"-example"); | ||||
while (examples.length) { | ||||
swapDOM(examples.pop(), null); | ||||
} | ||||
// make a template list | ||||
var templates = getElementsByTagAndClassName(null, prefix+"-template"); | ||||
for (var i = 0; i < templates.length; i++) { | ||||
var template = templates[i]; | ||||
var proto = template.cloneNode(true); | ||||
removeElementClass(proto, prefix+"-template"); | ||||
this.templates.push({ | ||||
"template": proto, | ||||
"node": template | ||||
}); | ||||
} | ||||
// set up the data anchors to do loads | ||||
var anchors = getElementsByTagAndClassName("a", null); | ||||
for (var i = 0; i < anchors.length; i++) { | ||||
var node = anchors[i]; | ||||
var format = getAttribute(node, "mochi:dataformat"); | ||||
if (format) { | ||||
node.onclick = loadFromDataAnchor; | ||||
} | ||||
} | ||||
// to find sort columns | ||||
this.thead = getElementsByTagAndClassName("thead", prefix)[0]; | ||||
this.thead_proto = this.thead.cloneNode(true); | ||||
this.sortkey = sortkey; | ||||
/* this.loadFromURL("json", "objects.json");*/ | ||||
}, | ||||
"loadFromURL": function (format, url) { | ||||
log('loadFromURL', format, url); | ||||
var d; | ||||
if (this.deferred) { | ||||
this.deferred.cancel(); | ||||
} | ||||
if (format == "xml") { | ||||
var d = doXHR(url, { | ||||
mimeType: 'text/xml', | ||||
headers: {Accept: 'text/xml'} | ||||
}); | ||||
d.addCallback(datatableFromXMLRequest); | ||||
} else if (format == "json") { | ||||
d = loadJSONDoc(url); | ||||
} else { | ||||
throw new TypeError("format " + repr(format) + " not supported"); | ||||
} | ||||
// keep track of the current deferred, so that we can cancel it | ||||
this.deferred = d; | ||||
var self = this; | ||||
// on success or error, remove the current deferred because it has | ||||
// completed, and pass through the result or error | ||||
d.addBoth(function (res) { | ||||
self.deferred = null; | ||||
log('loadFromURL success'); | ||||
return res; | ||||
}); | ||||
// on success, tag the result with the format used so we can display | ||||
// it | ||||
d.addCallback(function (res) { | ||||
res.format = format; | ||||
return res; | ||||
}); | ||||
// call this.initWithData(data) once it's ready | ||||
d.addCallback(this.initWithData); | ||||
// if anything goes wrong, except for a simple cancellation, | ||||
// then log the error and show the logger | ||||
d.addErrback(function (err) { | ||||
if (err instanceof CancelledError) { | ||||
return; | ||||
} | ||||
logError(err); | ||||
logger.debuggingBookmarklet(); | ||||
}); | ||||
return d; | ||||
}, | ||||
"initWithData": function (data) { | ||||
/*** | ||||
Initialize the SortableManager with a table object | ||||
***/ | ||||
// reformat to [{column:value, ...}, ...] style as the objects key | ||||
var objects = []; | ||||
var rows = data.rows; | ||||
var cols = data.columns; | ||||
for (var i = 0; i < rows.length; i++) { | ||||
var row = rows[i]; | ||||
var object = {}; | ||||
for (var j = 0; j < cols.length; j++) { | ||||
object[cols[j]] = row[j]; | ||||
} | ||||
objects.push(object); | ||||
} | ||||
data.objects = objects; | ||||
this.data = data; | ||||
// perform a sort and display based upon the previous sort state, | ||||
// defaulting to an ascending sort if this is the first sort | ||||
var order = this.sortState[this.sortkey]; | ||||
if (typeof(order) == 'undefined') { | ||||
order = true; | ||||
} | ||||
this.drawSortedRows(this.sortkey, order, false); | ||||
}, | ||||
"onSortClick": function (name) { | ||||
/*** | ||||
Return a sort function for click events | ||||
***/ | ||||
// save ourselves from doing a bind | ||||
var self = this; | ||||
// on click, flip the last sort order of that column and sort | ||||
return function () { | ||||
log('onSortClick', name); | ||||
var order = self.sortState[name]; | ||||
if (typeof(order) == 'undefined') { | ||||
// if it's never been sorted by this column, sort ascending | ||||
order = true; | ||||
} else if (self.sortkey == name) { | ||||
// if this column was sorted most recently, flip the sort order | ||||
order = !((typeof(order) == 'undefined') ? false : order); | ||||
} | ||||
self.drawSortedRows(name, order, true); | ||||
}; | ||||
}, | ||||
"drawSortedRows": function (key, forward, clicked) { | ||||
/*** | ||||
Draw the new sorted table body, and modify the column headers | ||||
if appropriate | ||||
***/ | ||||
log('drawSortedRows', key, forward); | ||||
// save it so we can flip next time | ||||
this.sortState[key] = forward; | ||||
this.sortkey = key; | ||||
var sortstyle; | ||||
// setup the sort columns | ||||
var thead = this.thead_proto.cloneNode(true); | ||||
var cols = thead.getElementsByTagName("th"); | ||||
for (var i = 0; i < cols.length; i++) { | ||||
var col = cols[i]; | ||||
var sortinfo = getAttribute(col, "mochi:sortcolumn").split(" "); | ||||
var sortkey = sortinfo[0]; | ||||
col.onclick = this.onSortClick(sortkey); | ||||
col.onmousedown = ignoreEvent; | ||||
col.onmouseover = mouseOverFunc; | ||||
col.onmouseout = mouseOutFunc; | ||||
// if this is the sorted column | ||||
if (sortkey == key) { | ||||
sortstyle = sortinfo[1]; | ||||
// \u2193 is down arrow, \u2191 is up arrow | ||||
// forward sorts mean the rows get bigger going down | ||||
var arrow = (forward ? "\u2193" : "\u2191"); | ||||
// add the character to the column header | ||||
col.appendChild(SPAN(null, arrow)); | ||||
if (clicked) { | ||||
col.onmouseover(); | ||||
} | ||||
} | ||||
} | ||||
this.thead = swapDOM(this.thead, thead); | ||||
// apply a sort transform to a temporary column named __sort__, | ||||
// and do the sort based on that column | ||||
if (!sortstyle) { | ||||
sortstyle = "str"; | ||||
} | ||||
var sortfunc = SortTransforms[sortstyle]; | ||||
if (!sortfunc) { | ||||
throw new TypeError("unsupported sort style " + repr(sortstyle)); | ||||
} | ||||
var objects = this.data.objects; | ||||
for (var i = 0; i < objects.length; i++) { | ||||
var object = objects[i]; | ||||
object.__sort__ = sortfunc(object[key]); | ||||
} | ||||
// perform the sort based on the state given (forward or reverse) | ||||
var cmp = (forward ? keyComparator : reverseKeyComparator); | ||||
objects.sort(cmp("__sort__")); | ||||
// process every template with the given data | ||||
// and put the processed templates in the DOM | ||||
for (var i = 0; i < this.templates.length; i++) { | ||||
log('template', i, template); | ||||
var template = this.templates[i]; | ||||
var dom = template.template.cloneNode(true); | ||||
processMochiTAL(dom, this.data); | ||||
template.node = swapDOM(template.node, dom); | ||||
} | ||||
//permission based coloring | ||||
} | ||||
}; | ||||
// create the global SortableManager and initialize it on page load | ||||
sortableManager = new SortableManager(); | ||||
sortableManager2 = new SortableManager(); | ||||
addLoadEvent(function() {sortableManager.initialize("notebook", "dateModified")}); | ||||
addLoadEvent(function() {sortableManager2.initialize("user", "username")}); | ||||