We saw... ‣ A few noteworthy JavaScript aspects ‣ Extensions to native types (arrays, strings...) ‣ Extensions to DOM elements ‣ Event unified handling ‣ AJAX support
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?
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!
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)
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.
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))
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!"
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);
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...
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);
‣ 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!
• 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');
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
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);
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!
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!
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 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
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); })
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); }) // ['*', '**', '***', '****', '*****']
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(' ');
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'
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('')
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' <-- 'The'[0] == 'T' ;-)
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('')
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'
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')
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] <-- 'N vwl whtsvr'
slots • select, reject, partition, grep, all, any: global lookups, checks & filters • find/detect, include: single lookup • inject: computing one value out of it all
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...
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);
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...
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.
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')
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')
‣ 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!
‣ 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!
‣ 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!
‣ 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);
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"]}’
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()
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
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
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!
is extended at most once • Done internally by $, which is used throughout the library ‣ Returned elements / element sets: extended • Adds Element.Methods.* (~60 methods)
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)
on MSIE, if Element.Methods grows too large HTMLElement. prototype HTML*Element. prototype Linear Linear Const. Linear Const. Const. Const. Linear Const. Const.
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.
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
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!)
repositories ‣ Updates HTML element prototypes ‣ Deals with “simulated” methods (those that compensate for missing W3C DOM ones) ‣ Deals with quirks across browsers
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?
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
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...
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).
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.
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 || {});
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 || {});
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. });
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.
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
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!