What’s up with Prototype and script.aculo.us? (Shiny things in Spinoffsland)
Christophe PorteneuveCTO, Ciblo.net + Prototype Core member
Did you guys catch our Prototype Developer Day?
• That was on Monday morning
• Bringing the community together
• Discussing contributions (to code, to docs)
• Several Prototype Core members
• Large Q&A session
• Awesome stuff!
What we’re going to see
• Prototype 1.6 → 1.6.0.3
• Custom events
• Class system
• Other various improvements
• Where Prototype’s headed
• Prototype 1.6.1 then 2.0
• Sprockets, PDoc
• Where script.aculo.us is headed (Scripty2)
Prototype 1.6
Prototype 1.6
• “Hey, that was almost a year ago!”
• Yeah, but never discussed at TAE yet (plus, 1.6.0.3 now…)
• And a lot of good stuff in there!
• Custom events and dom:loaded• New class system, with inheritance
• New Function-related stuff
• Ajax.Response• Countless other improvements and fixes
Custom events: overview
• Events our own JS code triggers
• Attached to DOM nodes
• Bubble like regular DOM events
• Publish/subscribe model: fire/observe• Major benefits
• Encapsulation (internal behaviors are not exposed)
• Loose coupling (anyone can trigger/observe any event)
• Ability to bubble favors event delegation
Custom events: naming and availability
• How do we tell a custom event from a native one?
• The custom event’s name must have 1+ colon
• e.g. dom:loaded, content:updated, effect:finished
• You don’t need to “register” the event
• You just observe / fire it.
• Custom events available on…
• Any DOM node
• The document object
Custom events: “creating” them
• Just observe one somewhere in your code
initComputation: function() { var target = this.element.down('.computed'), that = this;
function recompute() { target.update(that.getComputedValue()); } document.observe('data:changed', recompute);}
Custom events: triggering them
• Simply call fire on the relevant DOM element
new Field.Observer('edtReqTaxRate', 0.5, function(field, value) { field.fire('data:changed');});
Custom events: the memo property
initComputation: function() { var target = this.element.down('.computed'), that = this;
function recompute(e) { target.update(e && e.memo ? e.memo : that.options.value); } recompute(); document.observe('data:changed', recompute);}
new Field.Observer('edtReqTaxRate', 0.5, function(field, value) { field.fire('data:changed', value);});
Custom events: already much in style
• Especially by UI-oriented third-party libraries
• Lets them create a full communication scheme between
their widgets with no coupling whatsoever
• Prototype UI
• http://prototype-ui.com
• LivePipe UI (f.k.a. Control.Suite)
• http://livepipe.net/
dom:loaded
• Our first “standard” custom event
• Triggers right after DOM load
• So just forget
• And use
Event.observe(window, 'load', initPage);
document.observe('dom:loaded', initPage);
A note about stopObserving
• You can make partial calls now
• Omit the handler
• All handlers for this event on this element
• Omit the event too
• All handlers on all events on this element
• Easier cleanup
myEditor.stopObserving('change');
myEditor.stopObserving();
More Event goodness
• Guaranteed even methods and properties
• relatedTarget, pageX, pageY
• stopPropagation(), preventDefault(), stopped
• Click detection
• isLeftClick(), isMiddleClick(), isRightClick()
• Pointer position
• pointer(), pointerX(), pointerY()
A note on event binding
• Pre-1.6, handlers were “unbound” by default
• Unless you had bound them explicitly, they’d end up using
the window scope
• Starting with 1.6, handlers are bound to the
subject of your observe call by default
• If you bound them explicitly, as always, that prior binding
will be retained.
New class system
• The idea: ease up traditional OOP constructs and
behaviors
• Inheritance
• Mix-ins
• Method overriding with access to inherited version
• Backward-compatible API
Ye Olde Class
• Key issues:
• initialize required,
even if empty
• Can’t give a parent
class
• Can’t override
methods easily
var MyClass = Class.create({ initialize: function(…) { // “Constructor” code here },
publicMethod1: function(…) { },
…});
The new class syntax
• We can provide a single parent class
• We then handle the prototype chaining for it
• We can provide 1+ “mixins” (modules)
• Bunches of methods that get blended in the prototype.
• initialize is optional (an empty one will fill in)
var MyClass = Class.create([parentClass, ][mixIns…, ]{ [initialize: function(…) { … },] publicMethod1: …, …})
Inheritance becomes a breeze
var XMLParser = Class.create({ initialize: function(source) { … }, …});
var XHTMLParser = Class.create(XMLParser, { …});
var AtomParser = Class.create(XMLParser, { …});
Free stuff you get
• Every instance has a constructor• Every class has a superclass and subclassesXMLParser.superclass // => nullXHTMLParser.superclass // => XMLParserAtomParser.superclass // => XMLParserXMLParser.subclasses // => [XHTMLParser, AtomParser]
var parser = new XHTMLParser(someXHTMLSource);
parser.constructor // => XHTMLParserparser.constructor.superclass // => XMLParser
Mixing in modules
• A module is just a bag of properties/methods
• You can mix as many of those as you want at class
creation time.var FeedAnalyzer = { // methods…};…var AtomParser = Class.create(XMLParser, FeedAnalyzer, MarkupFixer, StandardNamespaceHandler, { …});
Class methods
• These are just methods on the Class object itself,
so we can resort to good ol’ Object.extend.
var MyGenericClassMethods = { // methods…};…Object.extend(AtomParser, MyGenericClassMethods);Object.extend(AtomParser, { adHocMethod1: …, adHocMethod2: …, …});
Adding methods post-declaration
• Every class has a addMethods method that mixes
anything module-like in.
• It’s actually used on your modules (and custom
methods) at creation time.var UberCoolMethodsIGotJustNow = { leverageMicroFormats: …, turnIntoiPhoneUIs: …, turnIntoUniversalWidgets: …};
AtomParser.addMethods(UberCoolMethodsIGotJustNow);
Accessing overridden methods
• Insert a $super first argument. That’s it.
var XMLParser = Class.create({ initialize: function(source) { … }, …});
var AtomParser = Class.create({ initialize: function($super, source, options) { $super(source); // Handle options }, …});
Function-fu
• Prototype 1.6 introduced a lot of new methods on
function so you can reduce custom anonymous
wrappers and go wild (but reasonable) with AOP
• curry, rescuer of useless binds
• delay and defer, because patience is a virtue
• wrap, because AOP / Decorator can rock
• argumentNames (aka Hackish The First)
Spicying up your code with curry
• You know how you always use bind(null,…) just
because you need partial application?
• Don’t do it.
• That’s what curry is for:
$('preview').observe('mousewheel:up', shiftZoom.bind(null, 125));$('preview').observe('mousewheel:down', shiftZoom.bind(null, 80));
$('preview').observe('mousewheel:up', shiftZoom.curry(125));$('preview').observe('mousewheel:down', shiftZoom.curry(80));
curry vs. bind
• You should use bind when…
• You actually need to bind (set the semantics of this)
• It doesn’t matter whether you want to pre-fill arguments!
• You should use curry when…
• You need to pre-fill arguments
• And you don’t care about the binding, or more specifically
don’t want to change it.
Deferring execution: delay and defer
• Schedule execution of a snippet of JS for later
• Either a specific time later
• Or ASAP—typically right after the DOM got a moment to
breathe in your recent updates
• function.delay(seconds)• defer is the “ASAP” case, and is just…
• delay.curry(0.1) :-)
• Essentially works because of single-threadedness
Classical defer use-case
• You just added to the DOM
• You need to manipulate the added fragment now
• Attach event listener, manipulate its styling, whatever
• Most of the time you’ll need to let the browser
“catch its breath”
• So your DOM addition is actually processed and available
through the scripting interfaces.
Classical defer use-case
function addAndBindForm(formMarkup) { $('formsContainer').insert(formMarkup); $('formsContainer').down('form:last-of-type'). observe('submit', genericFormValidate);}
function addAndBindForm(formMarkup) { $('formsContainer').insert(formMarkup); (function() { $('formsContainer').down('form:last-of-type'). observe('submit', genericFormValidate); }).defer();
Ouch! Likely won’t return your form!
Going AOP / Decorator with wrap
• Replacing a method with an augmented version of
it, which means you get a reference to the former
version.if (Prototype.Browser.IE) { // Strip handlers on newly-removed elements to prevent memory leaks Element.Methods.update = Element.Methods.update.wrap( function(proceed, element, contents) { Element.select(element, '*').each(Event.stopObserving); return proceed(element, contents); } );}
argumentNames
• Array of argument names for a given function
• Hackish: relies on functions’ toString() capability
• Won’t work once packed in a name-changing way (e.g.
ShrinkSafe)
• Won’t work on lightened-up JS runtimes (e.g. versions of
Opera Mobile)
• We’re using it for our $super trick
• But we should stop, and will find another way
Ajax.Response
• Encapsulates the whole response you get
• Headers + body
• Extracts relevant-type contents
• responseText, responseXML, responseJSON
• “Shields” header retrieval
• Exceptions on native fetching turn into nulls
• You get that in callbacks instead of your requester
• API-compatible, but adds stuff
Before/after 1.6 for response analysis
Before 1.6 Since 1.6
callback(requester, headerJSON) callback(response, headerJSON)
Watch when you’re grabbing properties
Properties defined only once they make sense
No JSON supportheaderJSON, responseJSON,
sanitizeJSON/evalJSON options
Automatic JS evaluation evalJS = false|true|force
Property fetching can raise exceptions
Fetch wrappers return '' or null when necessary.
Content insertion
• Element.insert/wrap• insert now takes either a single element (bottom insertion)
or a hash of insertions (positional keys)
• wrap puts your element within another one (bottom
insertion) and returns, exceptionally, the wrapper (not the
element you called wrap on).
Positioning moved into Element
• Positioning methods in Element• absolutize, relativize
• getOffsetParent
• cumulativeScrollOffset, cumulativeOffset,
positionedOffset, viewportOffset
• clonePosition
• This will likely all move, with dimensioning
methods, into a separate object in 1.6.1.
Viewport inspection
• document.viewport• getDimensions, getWidth, getHeight
• Also remember Element.viewportOffset• If you cache it, you should probably listen for
window resize events and update your cache
accordingly.
JSON support, basic math methods
• JSON support
• Object.toJSON
• toJSON for Date, Array, Number, Hash, String
• String has evalJSON, isJSON and unfilterJSON
• As we saw, Ajax.Response had dedicated stuff
• Methodized usual math on Number
• abs, round, ceil, floor
• e.g. myNumber.abs(), (5.42).floor()
Safer Hash and improved serialization
• New Hash• Essentially we wanted you to be able to store anything
• Functions/methods, stuff whose names clash against builtins…
• So we stopped storing on the Hash itself and use internal
storage
• get/set/unset/index
• Improved form serialization
• W3C specs by the book (buttons, null/disabled/readonly…)
More versatile grep
• Extended Enumerable#grep semantics
• Used to toString() items and apply a regex on these
• Now uses its argument’s match method on items
• Backwards-compatible (RegExp.match is RegExp.test)
• Much more versatile. Consider Selector#match…
Improved Template
• Allows deep access to interpolated object using []
and . operatorsvar speaker = { name: 'Christophe', age: 30, sessions: [ { title: 'PDD', time: 'Monday at 8:00am' }, { title: 'Shiny…', time: 'Wednesday at 11:05am' } ]};
var tpl = new Template('#{name}, #{age}, will speak #{sessions.length} time(s), \starting on #{sessions[0].time} about #{sessions[0].title}.');
tpl.evaluate(speaker)// => "Christophe, 30, will speak 2 time(s), starting on Monday at 8:00am about PDD"
Improved Template
• Only for properties.
• If you need method calls, equip your topmost object with a
toTemplateReplacements() method.var speaker = { … toTemplateReplacements: function() { var scount = this.sessions.length; return Object.extend({ sessionCount: scount + ' time' + (scount > 1 ? 's' : '') }, this); }};
var tpl = new Template('#{name}, #{age}, will speak #{sessionCount}, starting on \#{sessions[0].time} about #{sessions[0].title}.');
And so much more…
• String#interpolate: one-shot templating
• Object.isXxx
• isElement, isArray, isHash, isFunction, isString,
isNumber, isUndefined
• Node constants
• Guaranteed Node namespace and node type constants, from
ELEMENT_NODE to NOTATION_NODE
'#{name} is #{age} years old'.interpolate(speaker)
And so much more…
• Array#intersect
• Element.identify• Ensure you’ve got an ID in the end
• Element.setStyle(cssString)• If you’re hardcoding it, can be nicer than a hash
• Is even usually faster!
[1, 2, 3, 4, 5, 6].intersect([3, 5, 7]) // => [3, 5]
The future of Prototype
Some serious flux recently…
• 2008 has been an odd year for us
• GitHub + LightHouse + dayjob overloads = ?!?
• 1.6.0.2 was released on January 25
• 1.6.0.3 was released on… September 29!
• We’re resuming active work now…
• On a more solid ground (back to our strong policies)
• With more community involvement (docs/tests/code/etc.)
Prototype 1.6.0.4
• Significant bugfix / perffix release
• Backward-compatible, obviously
• General directions
• Massive code restructuration and pattern-based cleanup
• Most notably rewrite of DOM and Event modules
• Dozens of pending patches scheduled for upgraded commit
• Ideally, should release before mid-November 2008
Prototype 1.6.1
• The next 3-digit release will be awesome
• General directions*
• Massive code restructuration and pattern-based cleanup
• Stronger, better-quality versions of 80+ pending patches
• More builtin custom events (e.g. content changes)
• Ajax timeouts
• Full-spectrum positioning/dimensioning (aka “layout”)
lookup and modification
* As always, subject to change…
Prototype 1.6.1
• When? When, when, when, when, when?
• Er…
• When it’s ready?
• We’d love to ship that before 2008 is over
• But as always, we’ll ship when it’s done
• Still, we’re going to work our asses off. Honest.
* As always, subject to change…
Prototype 2
• “Holy XHR, they’ve changed everything!”
• Yes, but we won’t b0rk your scripts!
• General directions:
• Way more modular
• As everyone won’t have to grab full payload, hitherto discarded feature
ideas could become “official modules.”
• Better class system (no “$super”, truer encapsulation, etc.)
• Perhaps stop extending DOM prototypes and find an API-
compatible way to get the same code feel
The future of script.aculo.us
Scripty2
• Currently it’s “just” effects
• 100% new effects engine, custom events-rich
• Debug facility lets us slow down, step through, etc.
• Jaw-droppin’ awesome, but not quite finalized yet
• Current state on GitHub
• Coming soon:
• 100% new drag-and-drop module
• Behaviors module (might end up shared with Prototype 2)
Scripty2 • A few demos
• Twistory
• http://twistori.com
• Creative Scrape
• http://creativescrape.com
• Coming-up “Progress” for the Sunlight Foundation
• Local demo
Shameless plug
• Fully up-to-date on Prototype 1.6
and script.aculo.us 1.8
• Full API reference
• Tons of examples / use cases
• More content and “Neuron
Workout” solutions at
http://thebungeebook.net/
http://books.pragprog.com/titles/cppsu
Fair-trade plug
• Andrew Dupont’s recent book
• An ideal complement to mine
• Tackles the APIs in a different,
very goal-oriented way
• Just like TBB, available as
PDF and in print
http://apress.com/book/view/1590599195
Audience Response
Questions, guys?
?
On to some tech demos and lunch! Enjoy, everyone.
Top Related