Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Advanced Prototype.js

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

Advanced Prototype.js

Avatar for Christophe Porteneuve

Christophe Porteneuve

July 25, 2007
Tweet

More Decks by Christophe Porteneuve

Other Decks in Technology

Transcript

  1. Did you attend Intro? • This was earlier this morning.

    We saw... ‣ A few noteworthy JavaScript aspects
  2. Did you attend Intro? • This was earlier this morning.

    We saw... ‣ A few noteworthy JavaScript aspects ‣ Extensions to native types (arrays, strings...)
  3. 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
  4. 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
  5. 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
  6. “So you’re qualified cuz...?” • Prototype Core member • Rails

    & script.aculo.us contributor • Prototype doc writer (prototypejs.org)
  7. “So you’re qualified cuz...?” • Prototype Core member • Rails

    & script.aculo.us contributor • Prototype doc writer (prototypejs.org) • Prototype book author
  8. “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
  9. “Advanced,” like, how? • Function binding, wrapping & methodizing •

    Enumerable best practices • Event best practices
  10. “Advanced,” like, how? • Function binding, wrapping & methodizing •

    Enumerable best practices • Event best practices • JSON love
  11. “Advanced,” like, how? • Function binding, wrapping & methodizing •

    Enumerable best practices • Event best practices • JSON love • Custom DOM extensions, custom classes
  12. “Advanced,” like, how? • Function binding, wrapping & methodizing •

    Enumerable best practices • Event best practices • JSON love • Custom DOM extensions, custom classes • Sneak peek at upcoming 1.6!
  13. Should I know Prototype a bit already? • Well... yeah.

    It would help. • We won’t go over each syntax and basic class here.
  14. 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.
  15. Method scopes (and other horror stories) var name = 'a

    JavaScript n00b'; var obj = { name: 'Scruffy', greet: function() { return "Hi, I’m " + this.name; } }; function sayHi(greetFx) { alert(greetFx()); }
  16. Method scopes (and other horror stories) var name = 'a

    JavaScript n00b'; var obj = { name: 'Scruffy', greet: function() { return "Hi, I’m " + this.name; } }; function sayHi(greetFx) { alert(greetFx()); } sayHi(obj.greet);
  17. Method scopes (and other horror stories) var name = 'a

    JavaScript n00b'; var obj = { name: 'Scruffy', greet: function() { return "Hi, I’m " + this.name; } }; function sayHi(greetFx) { alert(greetFx()); } sayHi(obj.greet); // Alerts "Hi, I’m a JavaScript n00b"
  18. What the hell just happened? • We passed a reference

    to a method ‣ It lost its binding: the scope to which “this” refers.
  19. 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?
  20. 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!
  21. 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?
  22. 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!
  23. One call to bind them all • bind “attaches” a

    scope object to a method ‣ Actually returns an ad hoc anonymous function
  24. 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)
  25. 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.
  26. 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))
  27. 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!"
  28. 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);
  29. 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...
  30. But wait! There’s more! • Most people don’t realize this,

    but bind lets you pre- fill arguments, too!
  31. But wait! There’s more! • Most people don’t realize this,

    but bind lets you pre- fill arguments, too! var obj = { base: [1, 2, 3], listAll: function() { return this.base.concat($A(arguments)).join(','); } }
  32. But wait! There’s more! • Most people don’t realize this,

    but bind lets you pre- fill arguments, too! var obj = { base: [1, 2, 3], listAll: function() { return this.base.concat($A(arguments)).join(','); } } var call = obj.listAll.bind(obj, 4, 5, 6);
  33. But wait! There’s more! • Most people don’t realize this,

    but bind lets you pre- fill arguments, too! var obj = { base: [1, 2, 3], listAll: function() { return this.base.concat($A(arguments)).join(','); } } var call = obj.listAll.bind(obj, 4, 5, 6); call();
  34. But wait! There’s more! • Most people don’t realize this,

    but bind lets you pre- fill arguments, too! var obj = { base: [1, 2, 3], listAll: function() { return this.base.concat($A(arguments)).join(','); } } var call = obj.listAll.bind(obj, 4, 5, 6); call(); // '1,2,3,4,5,6'
  35. 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...
  36. 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
  37. 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);
  38. Binding or closure? • Think twice before binding inner functions

    ‣ Wrapping anonymous function: calls × 2
  39. Binding or closure? • Think twice before binding inner functions

    ‣ Wrapping anonymous function: calls × 2 ‣ In a fat loop: feel the pain?
  40. 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:
  41. 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!
  42. Composing with wrap • Composing a function into another: f(g(x))

    • Your outer function must expect its inner function as its first argument.
  43. 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!
  44. 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');
  45. Methodizing • Yuck, sounds like a Bubble 1.0 slide! •

    Encodes the following pattern: ‣ A function takes its subject as first argument
  46. 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
  47. 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
  48. 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);
  49. To each his own • Are you one of the

    many who over-use each?
  50. To each his own • Are you one of the

    many who over-use each? ‣ each is the generic, all-purpose iterator
  51. 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
  52. 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!
  53. 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!
  54. 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!
  55. 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
  56. map / collect • The generic transformation function ‣ Each

    element is turned into a derived value thanks to a custom transform you provide
  57. 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
  58. 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); })
  59. 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); }) // ['*', '**', '***', '****', '*****']
  60. 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(' ');
  61. 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'
  62. faster map: pluck • Common map use-case • Simple transform:

    fetching the same property for each element
  63. faster map: pluck • Common map use-case • Simple transform:

    fetching the same property for each element • Much faster than a map
  64. 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')
  65. 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]
  66. 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('')
  67. 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' <-- 'The'[0] == 'T' ;-)
  68. faster map: invoke • Common map use-case • Simple transform:

    calling the same method, with the same arguments
  69. faster map: invoke • Common map use-case • Simple transform:

    calling the same method, with the same arguments • Much faster than a map
  70. 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
  71. 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
  72. 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('')
  73. 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'
  74. 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')
  75. 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] <-- 'N vwl whtsvr'
  76. A rundown of the rest... • eachSlice, inGroupsOf: cutting in

    slots • select, reject, partition, grep, all, any: global lookups, checks & filters
  77. 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
  78. 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
  79. 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...
  80. Save handlers: use bubbling! • The Ugly: multiple bind[AsEventListener] on

    the same handler elements.each(function(e) { e.observe('click', this.handleClick.bind(this)); });
  81. 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));
  82. 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);
  83. 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...
  84. 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.
  85. 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')
  86. 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')
  87. The art of stopping • Common pitfall: not caching handlers

    ‣ You just bind on the fly... ‣ ...and deregistering doesn’t work!
  88. 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!
  89. 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!
  90. 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!
  91. 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);
  92. Converting stuff to JSON • Custom toJSON method ‣ Array,

    Number, String, Date • Generic Object.toJSON otherwise
  93. 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
  94. 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"]}’
  95. 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
  96. 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()
  97. AJAX & JSON • The X-JSON header: just for tiny

    bits! ‣ Passed as last argument to callbacks
  98. AJAX & JSON • The X-JSON header: just for tiny

    bits! ‣ Passed as last argument to callbacks ‣ Obtained internally by calling Ajax.Request’s evalJSON()
  99. 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
  100. 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
  101. 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
  102. 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!
  103. Element.extend a closer look • Continually optimized • Any element

    is extended at most once • Done internally by $, which is used throughout the library
  104. 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
  105. 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)
  106. 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)
  107. 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.
  108. 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.
  109. Those few “static” methods • Noticed a few methods are

    not in X.Methods, but right in X? ‣ Form.reset
  110. Those few “static” methods • Noticed a few methods are

    not in X.Methods, but right in X? ‣ Form.reset ‣ Field.focus, Field.select
  111. 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
  112. 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
  113. 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!)
  114. 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)
  115. 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
  116. 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?
  117. 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
  118. Custom DOM extension MyLib.Methods = { catchEye: function(element) { Element.addClassName('eyeCatcher');

    Element.visualEffect(element, 'pulsate', { duration: 2 }); return element; } }; MyLib.FieldMethods = { toggleError: function(element, cleared) { var meth = (cleared ? 'remove' : 'add') + 'ClassName'; Element[meth](element, 'erroneous'); return element; } };
  119. Custom DOM extension MyLib.Methods = { catchEye: function(element) { Element.addClassName('eyeCatcher');

    Element.visualEffect(element, 'pulsate', { duration: 2 }); return element; } }; MyLib.FieldMethods = { toggleError: function(element, cleared) { var meth = (cleared ? 'remove' : 'add') + 'ClassName'; Element[meth](element, 'erroneous'); return element; } }; Object.extend(Element.Methods, MyLib.Methods);
  120. Custom DOM extension MyLib.Methods = { catchEye: function(element) { Element.addClassName('eyeCatcher');

    Element.visualEffect(element, 'pulsate', { duration: 2 }); return element; } }; MyLib.FieldMethods = { toggleError: function(element, cleared) { var meth = (cleared ? 'remove' : 'add') + 'ClassName'; Element[meth](element, 'erroneous'); return element; } }; Object.extend(Element.Methods, MyLib.Methods); Object.extend(Form.Element.Methods, MyLib.FieldMethods);
  121. Custom DOM extension MyLib.Methods = { catchEye: function(element) { Element.addClassName('eyeCatcher');

    Element.visualEffect(element, 'pulsate', { duration: 2 }); return element; } }; MyLib.FieldMethods = { toggleError: function(element, cleared) { var meth = (cleared ? 'remove' : 'add') + 'ClassName'; Element[meth](element, 'erroneous'); return element; } }; Object.extend(Element.Methods, MyLib.Methods); Object.extend(Form.Element.Methods, MyLib.FieldMethods); Element.addMethods();
  122. 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...
  123. 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).
  124. 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.
  125. Creating a class AsyncLoader = Class.create(); AsyncLoader.prototype = { resources:

    {}, initialize: function() { var args = $A(arguments), callback; if ('string' != typeof args.last()) callback = args.pop().bind(this); args.each(function(uri) { new Ajax.Request(uri, { method: 'get', onSuccess: function(xhr) { this.resources[uri] = xhr.responseText; (callback || Prototype.emptyFunction)(uri); }}); }); } }; function onURILoaded(uri) { ... } new AsyncLoader('foo.html', 'bar.php', onURILoaded);
  126. What’s Object.extend for? • It simply copies over all properties

    of a source object into a destination object.
  127. 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...
  128. 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
  129. 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
  130. 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 || {});
  131. 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 || {});
  132. 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. });
  133. Static Object methods • We don’t augment Object.prototype ‣ We

    used to. Plain rude, pollutes everyone. • We do provide utility methods as static
  134. 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)
  135. 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!
  136. 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.
  137. 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
  138. Event overhaul • Event object wrapping • Unified DOMContentLoaded •

    Scope correction (this = element) • Custom events
  139. Miscellanea • Relying on native JS methods for Array whenever

    possible (e.g. forEach) • Extended grep semantics • Dynamic content beef-up (toHTML, toElement, replace/update, isElement...) • Bug fixes and performance boosts across the board
  140. Online resources • The excellent documentation site ‣ http://prototypejs.org ‣

    API reference, tutorials, downloads, blog... ‣ Soon: 3rd-party libs roster
  141. Online resources • The excellent documentation site ‣ http://prototypejs.org ‣

    API reference, tutorials, downloads, blog... ‣ Soon: 3rd-party libs roster • The official lists ‣ http://groups.google.com/rubyonrails-spinoffs ‣ http://groups.google.com/prototype-core
  142. Online resources • The excellent documentation site ‣ http://prototypejs.org ‣

    API reference, tutorials, downloads, blog... ‣ Soon: 3rd-party libs roster • The official lists ‣ http://groups.google.com/rubyonrails-spinoffs ‣ http://groups.google.com/prototype-core • IRC ‣ #prototype on Freenode
  143. Shameless plug • “The Bungee Book” • Available already (as

    beta) from the Pragmatic Bookshelf ‣ http://books.pragprog.com/titles/cppsu/
  144. Shameless plug • “The Bungee Book” • Available already (as

    beta) from the Pragmatic Bookshelf ‣ http://books.pragprog.com/titles/cppsu/ • 95% content-complete already
  145. 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
  146. 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!
  147. Q&A