Did you attend Intro? • This was earlier this morning. We saw... ‣ A few noteworthy JavaScript aspects ‣ Extensions to native types (arrays, strings...)
Did you attend Intro? • This was earlier this morning. We saw... ‣ A few noteworthy JavaScript aspects ‣ Extensions to native types (arrays, strings...) ‣ Extensions to DOM elements
Did you attend Intro? • This was earlier this morning. We saw... ‣ A few noteworthy JavaScript aspects ‣ Extensions to native types (arrays, strings...) ‣ Extensions to DOM elements ‣ Event unified handling
Did you attend Intro? • This was earlier this morning. We saw... ‣ A few noteworthy JavaScript aspects ‣ Extensions to native types (arrays, strings...) ‣ Extensions to DOM elements ‣ Event unified handling ‣ AJAX support
“So you’re qualified cuz...?” • Prototype Core member • Rails & script.aculo.us contributor • Prototype doc writer (prototypejs.org) • Prototype book author • Top question batter on the Google Group
Should I know Prototype a bit already? • Well... yeah. It would help. • We won’t go over each syntax and basic class here. • If you’re proficient at JavaScript, that may compensate.
What the hell just happened? • We passed a reference to a method ‣ It lost its binding: the scope to which “this” refers. • How come it usually works then?
What the hell just happened? • We passed a reference to a method ‣ It lost its binding: the scope to which “this” refers. • How come it usually works then? ‣ You call methods directly on objects!
What the hell just happened? • We passed a reference to a method ‣ It lost its binding: the scope to which “this” refers. • How come it usually works then? ‣ You call methods directly on objects! • OK, so how can I fix it?
What the hell just happened? • We passed a reference to a method ‣ It lost its binding: the scope to which “this” refers. • How come it usually works then? ‣ You call methods directly on objects! • OK, so how can I fix it? ‣ By using bind, of course!
One call to bind them all • bind “attaches” a scope object to a method ‣ Actually returns an ad hoc anonymous function ‣ So you may want to cache it if you plan on re-using it (say, register/deregister in a repository)
One call to bind them all • bind “attaches” a scope object to a method ‣ Actually returns an ad hoc anonymous function ‣ So you may want to cache it if you plan on re-using it (say, register/deregister in a repository) • Relies on JavaScript’s native apply method.
One call to bind them all • bind “attaches” a scope object to a method ‣ Actually returns an ad hoc anonymous function ‣ So you may want to cache it if you plan on re-using it (say, register/deregister in a repository) • Relies on JavaScript’s native apply method. sayHi(obj.greet.bind(obj))
One call to bind them all • bind “attaches” a scope object to a method ‣ Actually returns an ad hoc anonymous function ‣ So you may want to cache it if you plan on re-using it (say, register/deregister in a repository) • Relies on JavaScript’s native apply method. sayHi(obj.greet.bind(obj)) // Alerts "Hi, I’m Scruffy!"
One call to bind them all • bind “attaches” a scope object to a method ‣ Actually returns an ad hoc anonymous function ‣ So you may want to cache it if you plan on re-using it (say, register/deregister in a repository) • Relies on JavaScript’s native apply method. sayHi(obj.greet.bind(obj)) // Alerts "Hi, I’m Scruffy!" var fx = obj.greet.bind(obj); sayHi(fx);
One call to bind them all • bind “attaches” a scope object to a method ‣ Actually returns an ad hoc anonymous function ‣ So you may want to cache it if you plan on re-using it (say, register/deregister in a repository) • Relies on JavaScript’s native apply method. sayHi(obj.greet.bind(obj)) // Alerts "Hi, I’m Scruffy!" var fx = obj.greet.bind(obj); sayHi(fx); // Same...
Binding event listeners • Event handlers expect the event object as their first argument ‣ If you use bind with pre-filled arguments, you’ll push this event object aside...
Binding event listeners • Event handlers expect the event object as their first argument ‣ If you use bind with pre-filled arguments, you’ll push this event object aside... • The one use case for bindAsEventListener
Binding event listeners • Event handlers expect the event object as their first argument ‣ If you use bind with pre-filled arguments, you’ll push this event object aside... • The one use case for bindAsEventListener var obj = { // fields, other methods... function clickHandler(e, mode) { ... } }; var ch = obj.clickHandler.bindAsEventListener(obj, 'auto'); $(element).observe('click', ch);
Binding or closure? • Think twice before binding inner functions ‣ Wrapping anonymous function: calls × 2 ‣ In a fat loop: feel the pain? ‣ Just save “this” and leverage the closure:
Binding or closure? • Think twice before binding inner functions ‣ Wrapping anonymous function: calls × 2 ‣ In a fat loop: feel the pain? ‣ Just save “this” and leverage the closure: someMethod: function() { var obj = this; this.children.each(function(child) { obj.markChildProcessed(child); }); } No binding needed!
Composing with wrap • Composing a function into another: f(g(x)) • Your outer function must expect its inner function as its first argument. • Prominent use-case: AOP!
Composing with wrap • Composing a function into another: f(g(x)) • Your outer function must expect its inner function as its first argument. • Prominent use-case: AOP! function log() { var args = $A(arguments), proceed = args.shift(); console.log('>>> (' + args.invoke('inspect').join(', ') + ')'); proceed.call(this, args); console.log('<<< '); } lfx = fx.wrap(log); lfx('hello', 'world');
Methodizing • Yuck, sounds like a Bubble 1.0 slide! • Encodes the following pattern: ‣ A function takes its subject as first argument ‣ We wish to equip subjects with it, as a direct method
Methodizing • Yuck, sounds like a Bubble 1.0 slide! • Encodes the following pattern: ‣ A function takes its subject as first argument ‣ We wish to equip subjects with it, as a direct method • Prime example: Element.Methods (DOM
Methodizing • Yuck, sounds like a Bubble 1.0 slide! • Encodes the following pattern: ‣ A function takes its subject as first argument ‣ We wish to equip subjects with it, as a direct method • Prime example: Element.Methods (DOM MyLib = { b0rk: function(element, mode, count) { ... } } MyLib.b0rk(elt, 'auto', 3); elt.b0rk = MyLib.b0rk.methodize(); elt.b0rk('auto', 3);
To each his own • Are you one of the many who over-use each? ‣ each is the generic, all-purpose iterator ‣ It has a significant cost: function call + closure
To each his own • Are you one of the many who over-use each? ‣ each is the generic, all-purpose iterator ‣ It has a significant cost: function call + closure • Looking to produce derived values? Use map!
To each his own • Are you one of the many who over-use each? ‣ each is the generic, all-purpose iterator ‣ It has a significant cost: function call + closure • Looking to produce derived values? Use map! ‣ Getting the same property on all? Use pluck!
To each his own • Are you one of the many who over-use each? ‣ each is the generic, all-purpose iterator ‣ It has a significant cost: function call + closure • Looking to produce derived values? Use map! ‣ Getting the same property on all? Use pluck! ‣ Calling the same method on all? Use invoke!
To each his own • Are you one of the many who over-use each? ‣ each is the generic, all-purpose iterator ‣ It has a significant cost: function call + closure • Looking to produce derived values? Use map! ‣ Getting the same property on all? Use pluck! ‣ Calling the same method on all? Use invoke! • Many, many more common use-cases are optimized
map / collect • The generic transformation function ‣ Each element is turned into a derived value thanks to a custom transform you provide • Returns an array of the results
map / collect • The generic transformation function ‣ Each element is turned into a derived value thanks to a custom transform you provide • Returns an array of the results $R(1, 5).map(function(count) { return '*'.times(count); })
map / collect • The generic transformation function ‣ Each element is turned into a derived value thanks to a custom transform you provide • Returns an array of the results $R(1, 5).map(function(count) { return '*'.times(count); }) // ['*', '**', '***', '****', '*****']
map / collect • The generic transformation function ‣ Each element is turned into a derived value thanks to a custom transform you provide • Returns an array of the results $R(1, 5).map(function(count) { return '*'.times(count); }) // ['*', '**', '***', '****', '*****'] $w('Sometimes you gotta code your map').map(function(word) { return word.toArray().reverse().join(''); }).join(' ');
map / collect • The generic transformation function ‣ Each element is turned into a derived value thanks to a custom transform you provide • Returns an array of the results $R(1, 5).map(function(count) { return '*'.times(count); }) // ['*', '**', '***', '****', '*****'] $w('Sometimes you gotta code your map').map(function(word) { return word.toArray().reverse().join(''); }).join(' '); // 'semitemoS uoy attog edoc ruoy pam'
faster map: pluck • Common map use-case • Simple transform: fetching the same property for each element • Much faster than a map $w('The Ajax Experience').pluck('length')
faster map: pluck • Common map use-case • Simple transform: fetching the same property for each element • Much faster than a map $w('The Ajax Experience').pluck('length') // [3, 4, 10]
faster map: pluck • Common map use-case • Simple transform: fetching the same property for each element • Much faster than a map $w('The Ajax Experience').pluck('length') // [3, 4, 10] $w('The Ajax Experience').pluck(0).join('')
faster map: pluck • Common map use-case • Simple transform: fetching the same property for each element • Much faster than a map $w('The Ajax Experience').pluck('length') // [3, 4, 10] $w('The Ajax Experience').pluck(0).join('') // 'TAE'
faster map: invoke • Common map use-case • Simple transform: calling the same method, with the same arguments • Much faster than a map • Still, chaining multiple calls can be costlier
faster map: invoke • Common map use-case • Simple transform: calling the same method, with the same arguments • Much faster than a map • Still, chaining multiple calls can be costlier
faster map: invoke • Common map use-case • Simple transform: calling the same method, with the same arguments • Much faster than a map • Still, chaining multiple calls can be costlier $w('Thou Shalt Not Be Longer Than 4').invoke('truncate', 4, '…').join('')
faster map: invoke • Common map use-case • Simple transform: calling the same method, with the same arguments • Much faster than a map • Still, chaining multiple calls can be costlier $w('Thou Shalt Not Be Longer Than 4').invoke('truncate', 4, '…').join('') // 'Thou Sha… Not Be Lon… Than 4'
faster map: invoke • Common map use-case • Simple transform: calling the same method, with the same arguments • Much faster than a map • Still, chaining multiple calls can be costlier $w('Thou Shalt Not Be Longer Than 4').invoke('truncate', 4, '…').join('') // 'Thou Sha… Not Be Lon… Than 4' $w('No vowel whatsoever').invoke('gsub', /[aeiouy]+/i, '').pluck('length')
faster map: invoke • Common map use-case • Simple transform: calling the same method, with the same arguments • Much faster than a map • Still, chaining multiple calls can be costlier $w('Thou Shalt Not Be Longer Than 4').invoke('truncate', 4, '…').join('') // 'Thou Sha… Not Be Lon… Than 4' $w('No vowel whatsoever').invoke('gsub', /[aeiouy]+/i, '').pluck('length') // [1, 3, 6]
A rundown of the rest... • eachSlice, inGroupsOf: cutting in slots • select, reject, partition, grep, all, any: global lookups, checks & filters • find/detect, include: single lookup
A rundown of the rest... • eachSlice, inGroupsOf: cutting in slots • select, reject, partition, grep, all, any: global lookups, checks & filters • find/detect, include: single lookup • inject: computing one value out of it all
A rundown of the rest... • eachSlice, inGroupsOf: cutting in slots • select, reject, partition, grep, all, any: global lookups, checks & filters • find/detect, include: single lookup • inject: computing one value out of it all • zip, toArray, size, inspect, min, max...
Save handlers: use bubbling! • The Ugly: multiple bind[AsEventListener] on the same handler elements.each(function(e) { e.observe('click', this.handleClick.bind(this)); });
Save handlers: use bubbling! • The Ugly: multiple bind[AsEventListener] on the same handler • The Bad: multiple registrations of the same handler elements.invoke('observe', 'click', this.handleClick.bind(this));
Save handlers: use bubbling! • The Ugly: multiple bind[AsEventListener] on the same handler • The Bad: multiple registrations of the same handler • The Good: single registration on container, using the bubbling and Event.element var handler = this.handleClick.bind(this); container.observe('click', handler);
element or findElement? • Event.element returns the element the event fired on (IE: source. W3: target) ‣ Sometimes you’re not interested in it: perhaps bubbling means your registered ‘click’ on an a, and this is a span inside it...
element or findElement? • Event.element returns the element the event fired on (IE: source. W3: target) ‣ Sometimes you’re not interested in it: perhaps bubbling means your registered ‘click’ on an a, and this is a span inside it... • Event.findElement lets you specify a CSS3 selector used to walk up the element’s ancestry.
element or findElement? • Event.element returns the element the event fired on (IE: source. W3: target) ‣ Sometimes you’re not interested in it: perhaps bubbling means your registered ‘click’ on an a, and this is a span inside it... • Event.findElement lets you specify a CSS3 selector used to walk up the element’s ancestry. ‣ Event.findElement(e, 'a')
element or findElement? • Event.element returns the element the event fired on (IE: source. W3: target) ‣ Sometimes you’re not interested in it: perhaps bubbling means your registered ‘click’ on an a, and this is a span inside it... • Event.findElement lets you specify a CSS3 selector used to walk up the element’s ancestry. ‣ Event.findElement(e, 'a') ‣ Event.findElement(e, 'p.container')
The art of stopping • Common pitfall: not caching handlers ‣ You just bind on the fly... ‣ ...and deregistering doesn’t work! Event.observe(this.element, 'click', this.clickHandler.bind(this)); ... Event.stopObserving(this.element, 'click', this.clickHandler.bind(this)); // But the listener still works!
The art of stopping • Common pitfall: not caching handlers ‣ You just bind on the fly... ‣ ...and deregistering doesn’t work! • That’s because bind returns a new function Event.observe(this.element, 'click', this.clickHandler.bind(this)); ... Event.stopObserving(this.element, 'click', this.clickHandler.bind(this)); // But the listener still works!
The art of stopping • Common pitfall: not caching handlers ‣ You just bind on the fly... ‣ ...and deregistering doesn’t work! • That’s because bind returns a new function ‣ Cache the bound listener Event.observe(this.element, 'click', this.clickHandler.bind(this)); ... Event.stopObserving(this.element, 'click', this.clickHandler.bind(this)); // But the listener still works!
The art of stopping • Common pitfall: not caching handlers ‣ You just bind on the fly... ‣ ...and deregistering doesn’t work! • That’s because bind returns a new function ‣ Cache the bound listener Event.observe(this.element, 'click', this.clickHandler.bind(this)); ... Event.stopObserving(this.element, 'click', this.clickHandler.bind(this)); // But the listener still works! this._boundClickHandler = this.clickHandler.bind(this); Element.observe(this.element, 'click', this._boundClickHandler);
Converting stuff to JSON • Custom toJSON method ‣ Array, Number, String, Date • Generic Object.toJSON otherwise • So you’re good to go on any kind of data
Converting stuff to JSON • Custom toJSON method ‣ Array, Number, String, Date • Generic Object.toJSON otherwise • So you’re good to go on any kind of data var obj = { name: 'Christophe', age: 29, talks: ['IP', 'AP', 'Sc'] }; Object.toJSON(obj) // ‘{"name": "Christophe", "age": 29, "talks": ["IP", "AP", "Sc"]}’
Parsing JSON • ‘yourJSONString’.evalJSON() ‣ Wraps it in parentheses, just in case • Security! ‣ Text can be surrounded by a security wrapper text (Prototype.JSONFilter) to prevent accidental evaluation
Parsing JSON • ‘yourJSONString’.evalJSON() ‣ Wraps it in parentheses, just in case • Security! ‣ Text can be surrounded by a security wrapper text (Prototype.JSONFilter) to prevent accidental evaluation ‣ evalJSON(true) does a sanity check on the text using isJSON()
AJAX & JSON • The X-JSON header: just for tiny bits! ‣ Passed as last argument to callbacks ‣ Obtained internally by calling Ajax.Request’s evalJSON() • JSON response body: 1.5.1.1 not there yet
AJAX & JSON • The X-JSON header: just for tiny bits! ‣ Passed as last argument to callbacks ‣ Obtained internally by calling Ajax.Request’s evalJSON() • JSON response body: 1.5.1.1 not there yet ‣ Filters out the security wrapper, if any
AJAX & JSON • The X-JSON header: just for tiny bits! ‣ Passed as last argument to callbacks ‣ Obtained internally by calling Ajax.Request’s evalJSON() • JSON response body: 1.5.1.1 not there yet ‣ Filters out the security wrapper, if any ‣ Requires a JavaScript MIME type, as usual
AJAX & JSON • The X-JSON header: just for tiny bits! ‣ Passed as last argument to callbacks ‣ Obtained internally by calling Ajax.Request’s evalJSON() • JSON response body: 1.5.1.1 not there yet ‣ Filters out the security wrapper, if any ‣ Requires a JavaScript MIME type, as usual ‣ But an unstored JSON structure is useless!
Element.extend a closer look • Continually optimized • Any element is extended at most once • Done internally by $, which is used throughout the library
Element.extend a closer look • Continually optimized • Any element is extended at most once • Done internally by $, which is used throughout the library ‣ Returned elements / element sets: extended
Element.extend a closer look • Continually optimized • Any element is extended at most once • Done internally by $, which is used throughout the library ‣ Returned elements / element sets: extended • Adds Element.Methods.* (~60 methods)
Element.extend a closer look • Continually optimized • Any element is extended at most once • Done internally by $, which is used throughout the library ‣ Returned elements / element sets: extended • Adds Element.Methods.* (~60 methods) ‣ And tag-specific (Element.Methods.ByTag)
Actual extension cost ‣ The danger of slowness looms only on MSIE, if Element.Methods grows too large HTMLElement. prototype HTML*Element. prototype Linear Linear Const. Linear Const. Const. Const. Linear Const. Const.
Actual extension cost ‣ The danger of slowness looms only on MSIE, if Element.Methods grows too large ‣ Even on MSIE, elements are extended lazily only the first time: after that, they’re good HTMLElement. prototype HTML*Element. prototype Linear Linear Const. Linear Const. Const. Const. Linear Const. Const.
Those few “static” methods • Noticed a few methods are not in X.Methods, but right in X? ‣ Form.reset ‣ Field.focus, Field.select • That’s because native methods exist
Those few “static” methods • Noticed a few methods are not in X.Methods, but right in X? ‣ Form.reset ‣ Field.focus, Field.select • That’s because native methods exist ‣ Ours just return their argument afterwards
Those few “static” methods • Noticed a few methods are not in X.Methods, but right in X? ‣ Form.reset ‣ Field.focus, Field.select • That’s because native methods exist ‣ Ours just return their argument afterwards ‣ Consistent with custom methods (chaining!)
Element.addMethods • Essentially updates the extension mechanism from the method repositories ‣ Updates HTML element prototypes ‣ Deals with “simulated” methods (those that compensate for missing W3C DOM ones)
Element.addMethods • Essentially updates the extension mechanism from the method repositories ‣ Updates HTML element prototypes ‣ Deals with “simulated” methods (those that compensate for missing W3C DOM ones) ‣ Deals with quirks across browsers
Element.addMethods • Essentially updates the extension mechanism from the method repositories ‣ Updates HTML element prototypes ‣ Deals with “simulated” methods (those that compensate for missing W3C DOM ones) ‣ Deals with quirks across browsers • Adding your custom extensions?
Element.addMethods • Essentially updates the extension mechanism from the method repositories ‣ Updates HTML element prototypes ‣ Deals with “simulated” methods (those that compensate for missing W3C DOM ones) ‣ Deals with quirks across browsers • Adding your custom extensions? ‣ Call it once you’re done
Classes in JavaScript • In JavaScript, a type is defined by it constructor, which is just a function. • “Static” properties / methods are defined directly on the constructor object • Instance properties / methods are defined on the constructor’s prototype • Let’s leave private/privileged/public alone...
Classes in Prototype • Warning: will evolve big time in 2.0 • Prototype just defines a Class.create function that provides a constructor which automatically calls the new instance’s initialize method (forwarding arg’s).
Classes in Prototype • Warning: will evolve big time in 2.0 • Prototype just defines a Class.create function that provides a constructor which automatically calls the new instance’s initialize method (forwarding arg’s). • Lets you put initialization code right beside instance methods / properties.
What’s Object.extend for? • It simply copies over all properties of a source object into a destination object. • Prototype uses it all over the place to...
What’s Object.extend for? • It simply copies over all properties of a source object into a destination object. • Prototype uses it all over the place to... ‣ Build option sets out of defaults + actual
What’s Object.extend for? • It simply copies over all properties of a source object into a destination object. • Prototype uses it all over the place to... ‣ Build option sets out of defaults + actual
What’s Object.extend for? • It simply copies over all properties of a source object into a destination object. • Prototype uses it all over the place to... ‣ Build option sets out of defaults + actual this.options = Object.clone(MyClass.DefaultOptions); Object.extend(this.options, options || {});
What’s Object.extend for? • It simply copies over all properties of a source object into a destination object. • Prototype uses it all over the place to... ‣ Build option sets out of defaults + actual ‣ Mix in modules with classes this.options = Object.clone(MyClass.DefaultOptions); Object.extend(this.options, options || {});
What’s Object.extend for? • It simply copies over all properties of a source object into a destination object. • Prototype uses it all over the place to... ‣ Build option sets out of defaults + actual ‣ Mix in modules with classes this.options = Object.clone(MyClass.DefaultOptions); Object.extend(this.options, options || {}); MyContainer = Class.create(); Object.extend(MyContainer.prototype, Enumerable); Object.extend(MyContainer.prototype, { // initialize, _each, etc. });
Static Object methods • We don’t augment Object.prototype ‣ We used to. Plain rude, pollutes everyone. • We do provide utility methods as static ‣ Object.method(obj)
Static Object methods • We don’t augment Object.prototype ‣ We used to. Plain rude, pollutes everyone. • We do provide utility methods as static ‣ Object.method(obj) ‣ clone, extend, keys, values: simple hashes!
Static Object methods • We don’t augment Object.prototype ‣ We used to. Plain rude, pollutes everyone. • We do provide utility methods as static ‣ Object.method(obj) ‣ clone, extend, keys, values: simple hashes! ‣ toJSON: we saw it already.
Static Object methods • We don’t augment Object.prototype ‣ We used to. Plain rude, pollutes everyone. • We do provide utility methods as static ‣ Object.method(obj) ‣ clone, extend, keys, values: simple hashes! ‣ toJSON: we saw it already. ‣ inspect: debug-oriented string representation
Shameless plug • “The Bungee Book” • Available already (as beta) from the Pragmatic Bookshelf ‣ http://books.pragprog.com/titles/cppsu/ • 95% content-complete already • Up-to-date on the latest stuff
Shameless plug • “The Bungee Book” • Available already (as beta) from the Pragmatic Bookshelf ‣ http://books.pragprog.com/titles/cppsu/ • 95% content-complete already • Up-to-date on the latest stuff • Pre-order the paper book too!