Intro to MochiKit
Author: |
Bob Ippolito |
Date: |
May 2006 |
Venue: | The Ajax Experience 2006 |
What's MochiKit?
- Makes JavaScript suck less
- Provides browser workarounds
- Simplifies AJAX and DOM
- Plays nicely with other code
Another Library?!
(July 2005)
- Prototype:
- no docs or tests, mangles built-ins
- Dojo:
- no docs or demos
MochiKit Design Goals
- Documentation
- DOCUMENTATION
- Lots of tests
- Stay out of the way (not a framework)
- Consistent and portable
Why MochiKit?
- Good docs, well tested
- Async abstractions
- Painless DOM syntax
- Consistent browser events
- Python-like (think "standard library")
- Safari 2.0, Firefox 1.0, IE 6, Opera 8.5 (and others)
Why Not MochiKit?
- No support for IE 5.5
- No widgets
- No animation, transitions, etc.
- ... MochiKit 1.4
MochiKit.Base
- Beats some sense into JavaScript
- Provides analogs to a lot Python built-ins and object protocols
- This is the "suck less" part
toString Ambiguity
- [1].toString() === '1'
- (1).toString() === '1'
- '1'.toString() === '1'
repr > toString
- MochiKit provides repr() instead, just like Python.
- Unambiguous, extensible
MochiKit Interpreter
In-line demo of the MochiKit interpreter example.
The advantages of repr vs. toString and some of the interpreter's features
will be demonstrated during this slide.
Unreliable Operators
- Most JavaScript comparisons based on toString
- [1] == [1] is false!
- compare(a, b) provides consistent results
- Extensible
JSON Serialization
- serializeJSON(object) -> JSON string
- Extensible
Adapters?
- Not a good idea to hack on built-in objects
- MochiKit doesn't always know what you want to do
- Adapters let you extend existing functions
Adapting
- name:
- unique identifier for your adapter
- check:
- should wrap be called?
- wrap:
- performs the operation
DOM Comparator Example
Register HTML-based comparator for DOM nodes:
function isDOMNode(node) {
return typeof(node.nodeType) == 'number';
}
function compareDOMNodes(a, b) {
return compare(a.innerHTML, b.innerHTML);
}
registerComparator('compareDOM',
isDOMNode, compareDOMNode);
queryString
- Easily build URL query strings
- queryString(['foo', 'bar'], [1, 2]) == 'foo=1&bar=2'
- queryString({foo: 1, bar: 2}) == 'foo=1&bar=2'
queryString and DOM
queryString('formNode') == 'foo=1&bar=2', given:
<form id="formNode">
<input type="hidden" name="foo" value="1" />
<input type="text" name="bar" value="2" />
</form>
Mangling Objects
- merge(obj[, ...]):
- New object, every prop:value of given objects
- update(obj[, ...]):
- In-place merge
- updatetree(obj[, ...]):
- Recursive update
- setdefault(obj[, ...]):
- update, but no overwrite
Object Introspection
- keys(obj):
- Array of obj's properties
- items(obj):
- Array of obj's [property, value]
Function Functions
- bind(fn, self[, arg...]):
- fn.apply(self, concat(arg..., arguments))
- method(self, fn[, arg...]):
- convenience form for bind
- itemgetter(name):
- obj[name]
Array Functions
Array missing lots of useful functionality
- concat(lst[, ...]):
- concatenates Arrays
- extend(self, seq, skip=0):
- extends Array in-place
- flattenArguments(args[, ...]):
- recursively flatten arguments to single Array
Array Searching
- findValue(lst, value):
- finds index of value
- findIdentical(lst, value):
- finds index of identical value
- listMin(lst):
- finds least item in lst
- listMax(lst):
- finds greatest item in lst
Higher-order Array
- filter(func, lst):
- Array where funcn(lst[n])
- map(func, lst):
- [func(lst[0]), ...]
- keyComparator(key):
- compare(a[key], b[key])
MochiKit.Iter
MochiKit.Iter provides generalized iteration, like Python's
iteration protocol and itertools module.
- Works great on Arrays, but also on anything iterable.
- Anything with a next method is iterable, and the iteration stops when
StopIteration is thrown.
- Extensible
Collapsing Iterators
- exhaust(iterable):
- Iterate and ignore results
- list(iterable):
- new Array
- sorted(iterable):
- sorted Array
- sum(iterable, start=0):
- Return start plus sum of items
Iterating Iterables
The hard way:
var itr = iter(iterable);
try {
while (true) {
var item = itr.next();
// ...
}
} catch (e) {
if (e != StopIteration) throw e;
}
Sane Iterable Iteration
The easy way:
forEach(iterable, function (item) {
// ...
})
Infinite Iterators
- count(n=0):
- n, n + 1, n + 2, ...
- cycle(iterable):
- while (1) { iterable[0], ... }
- repeat(item):
- item, item, item, item, ...
MochiKit.DateTime
- JavaScript Date objects aren't very convenient
- W3C profile ISO 8601 style timestamps are Good
ISO Dates
- isoDate(str):
- Date object from ISO 8601 date string
- toISODate(date):
- Date object to ISO 8601 date string
American Dates
- americanDate(str):
- MM/DD/YYYY to a Date object
- toAmericanDate(date):
- Date object to M/D/YYYY
- toPaddedAmericanDate(date):
- Date object to MM/DD/YYYY
Time and Timestamps
- isoTimestamp(str):
- YYYY-MM-DDThh:mm:ssZ to Date object
- toISOTime(date):
- Date object to hh:mm:ss
- toISOTimestamp(date, realISO=false):
- Date object to a YYYY-MM-DD hh:mm:ss (or YYYY-MM-DDThh:mm:ssZ)
Whitespace Assassins
- lstrip(str):
- strip leading whitespace
- rstrip(str):
- strip trailing whitespace
- strip(str):
- strip leading and trailing whitespace
MochiKit.Logging
- alert() sucks
- Debugging is hard enough
- FireBug not portable
- Venkman hard to use
- Microsoft Script Debugger....
Simple Logging
- log(msg):
- Logs a message at the INFO level
logDebug, logWarning, logError, logFatal...
Logs Are Where?
- Native console:
- Safari, FireBug, Opera
- Logging listener(s):
- functions called with log message objects
Bookmarklet Debugging
Pop-up MochiKit.LoggingPane:
javascript:logger.debuggingBookmarklet()
MochiKit.LoggingPane
- MochiKit.Logging listener
- Can be used in-line or as a pop-up window
Manually creating a LoggingPane
Pop-up:
createLoggingPane()
Inline:
createLoggingPane(true)
MochiKit.DOM
- W3C bindings are painful
- Easily find, create, mangle DOM nodes
- Don't need $(s), automatic
- Works on responseXML too!
createDOM
- Any object into a DOM node
- Strings to text, flattens iterators
- Extensible
createDOM Example
createDOM(tagName, attributes, contents...)
A simple list:
var node = createDOM('ul', null,
createDOM('li', null, 'first'),
createDOM('li', null, 'second'));
Renders as:
<ul><li>first</li><li>second</li></ul>
Less Ugly
Use aliases instead, supports common tags:
var node = UL(null,
LI('first'),
LI('second'));
Flattening for the DOM
Functional style handy for DOM creation:
var items = ['first', 'second'];
var node = UL(null, map(LI, items));
Attributes
First parameter is either an object (attributes), or a string (text node):
var classes = repeat({'class': 'itemclass'});
var items = ['first', 'second'];
var node = UL({'class': 'listclass'},
map(LI, classes, items));
Alternating
MochiKit.Iter good for table rows:
var classes = cycle(
{'class': 'even'},
{'class': 'odd'});
var items = ['first', 'second'];
var node = UL(null,
map(LI, classes, items));
Interpreter DOM
Another in-line interpreter demo, this time showing off MochiKit's DOM
support.
Scraping Text
Scraping text is useful for progressive enhancement...
HTML:
<span id="scrape_me">text is <b>here</b></span>
JavaScript:
>>> scrapeText('scrape_me');
"text is here"
Manipulating DOM
- appendChildNodes(parent, children...):
- Add nodes via createDOM
- replaceChildNodes(parent, children...):
- Remove all, then append
- swapDOM(dest, src):
- Replace dest with src (or remove)
DOM Attributes
- setNodeAttribute(node, attr, value):
- node attribute attr=value
- updateNodeAttributes(node, attrs):
- node attributes from object attrs
DOM Gotchas
- DOM manipulation isn't as fast as innerHTML, but it's a LOT easier
- IE expects tables to have a TBODY
MochiKit.Color
- Full CSS3 color model with alpha
- NSColor-like API (from Cocoa)
- Works in RGB, HSV, HSL
- Normalized to [0, 1.0]
Components to Color
- Color.fromRGB(r, g, b, alpha=1.0)
- fromHSL, fromHSV
- Also {r: 1, g: 0, b: 0, a: 1}
String to Color
- Color.fromString(str):
- Color from any valid CSS color description
- 'rgb(...)'
- 'hsl(...)'
- '#RRGGBB'
- 'blue'
DOM to Color
- Color.fromBackground(node)
- fromComputedStyle, fromText, ...
NSColor Colors
Cocoa-based constructors for basic colors
- Color.whiteColor()
- blueColor, transparentColor, ...
Mixing Colors
- color.blendedColor(otherColor, fraction)
- color.colorWithHue, colorWithLevel, ...
Color Components
Objects:
- color.asRGB(), asHSL, asHSV
Strings:
- color.toHexString(), toRGBString, toHSLString
MochiKit.Async
- AJAX!
- Model based on Twisted
- XMLHttpRequest and timed events (setTimeout)
WTF is a Deferred?
- A "promise" for a result
- Can be chained
- Model on any asynchronous platform
- Not "ideal" API, but no coroutines or threads
Trivial Deferreds
- succeed(value):
- successful Deferred from value
- fail(error):
- failed Deferred from error
- maybeDeferred(func, arguments..):
- Deferred from func(args..) call
Timed Events
- wait(seconds, value):
- Deferred that waits
- callLater(seconds, func, arguments...):
- Deferred that waits, then calls
Network Events
- doSimpleXMLHttpRequest(url):
- Deferred from XMLHttpRequest GET
- loadJSONDoc(url):
- Deferred from XMLHttpRequest GET then eval
- sendXMLHttpRequest(req, data):
- Deferred from async XMLHttpRequest
Deferred Usage
Fetch a JSON document:
function gotDocument(json) {
// ...
}
var d = loadJSONDoc("example.json");
d.addCallback(gotDocument);
d.addErrback(logError);
Result Chaining
loadJSONDoc implementation:
var d = doSimpleXMLHttpRequest(url);
d.addCallback(evalJSONDoc);
return d;
Uses Deferred's chained results
Deferred Chaining
Returning a Deferred from a callback will "pause" the callback chain:
function gotDocument(json) { /* ... */ }
function delay(res) { return wait(2.0, res); }
var d = loadJSONDoc('example.json');
d.addCallback(delay);
d.addCallback(gotDocument);
MochiKit.Signal
- Crown jewel of MochiKit 1.3
- Sorry it took so long!
- Browsers totally suck at events
Browser Events Suck
- IE is totally different
- IE's garbage "collector"
- Safari needs help
- They all disagree on pixel positions
- Key events seriously borked
connect to the DOM
Works everywhere:
function myClick(e) {
var mouse = e.mouse();
log('page coordinates: ' + mouse.page);
log('client coordinates: ' + mouse.client);
}
connect('element_id', 'onclick', myClick);
Custom Event Object
- Consistent event object!!
- e.type() is the event type
- e.target() is the event target
- e.mouse() has mouse coordinates
- e.key() for keyboard state
- e.modifiers() for keyboard modifiers
- e.stop() to stop
Mouse Events
The slide's logo will be dragged during this slide, while displaying live
pixel coordinates.
Keyboard Events
Event |
Key Code |
Key String |
onkeydown |
- |
- |
onkeyup |
- |
- |
onkeypress |
- |
- |
Modifiers
An in-line version of the key_events demo will be shown during this slide,
showing capture of onkeypress, onkeydown, onkeyup events. Also shows
the consistent naming of keys.
Signal Anything
- Broadcast your own events!
- connect() and disconnect() are the same
- signal(src, signal, args...) to fire
MochiKit Support
- Check the documentation
- Ask on the mailing list
- Check the wiki/bug tracker
- #mochikit on irc.freenode.net