Prototype

Stuart Halloway stu@relevancellc.com

Prototype’s Role

What is Prototype?
provides core support for Ajax hides browser oddities powers other libraries (Scriptaculous, Rico) keeps things simple works with any server stack integrates with Rails

Prototype’s Class: OO for JavaScript

Ajax.Request = Class.create(); Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; Ajax.Request.prototype = Object.extend(new Ajax.Base(), { initialize: function(url, options) { this.transport = Ajax.getTransport(); this.setOptions(options); this.request(url); }, // ...snip ... }

create a class

Ajax.Request = Class.create(); Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; Ajax.Request.prototype = Object.extend(new Ajax.Base(), { initialize: function(url, options) { this.transport = Ajax.getTransport(); this.setOptions(options); this.request(url); }, // ...snip ... }

class-level properties

Ajax.Request = Class.create(); Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; Ajax.Request.prototype = Object.extend(new Ajax.Base(), { initialize: function(url, options) { this.transport = Ajax.getTransport(); this.setOptions(options); this.request(url); }, // ...snip ... }

extend another class

Ajax.Request = Class.create(); Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; Ajax.Request.prototype = Object.extend(new Ajax.Base(), { initialize: function(url, options) { this.transport = Ajax.getTransport(); this.setOptions(options); this.request(url); }, // ...snip ... }

constructor

Ajax.Request = Class.create(); Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; Ajax.Request.prototype = Object.extend(new Ajax.Base(), { initialize: function(url, options) { this.transport = Ajax.getTransport(); this.setOptions(options); this.request(url); }, // ...snip ... }

instance-level properties

Ajax.Request = Class.create(); Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; Ajax.Request.prototype = Object.extend(new Ajax.Base(), { initialize: function(url, options) { this.transport = Ajax.getTransport(); this.setOptions(options); this.request(url); }, // ...snip ... }

beware the comma

var Class = { create: function() { return function() { this.initialize.apply(this, arguments); } } }

Class.create

Object.extend = function(destination, source) { for (var property in source) { destination[property] = source[property]; } return destination; }

Object.extend

OO in Prototype
• Classes are language library features • Inheritance is a language library feature • Classes and inheritance are flexible ideas

Augmenting Built-in Classes

Object.extend(String.prototype, { stripTags: function() { return this.replace(/<\/?[^>]+>/gi, ''); }, stripScripts: function() { return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); }, // ... snip ... }

augmenting String

$A(element.childNodes).each(function(node){ element.removeChild(node) });

enumeration: each

toQueryString: function() { return this.map(function(pair) { return pair.map(encodeURIComponent).join('='); }).join('&'); },

enumeration: map

compact: function() { return this.select(function(value) { return value != undefined || value != null; }); },

enumeration: select

Augmenting Classes

• functions have sensible homes • collection traversal is trivial

xhr Support

new Ajax.Request(this.validateAction, { method: 'get', parameters: e.name + "=" + e.value, onSuccess: (function(req) { this.reportValidation(e, req.responseText); }).bind(this), onFailure: function(req) {alert(req.responseText)}, onException: function(t,e) {alert(e);} });

URL to invoke

new Ajax.Request(this.validateAction, { method: 'get', parameters: e.name + "=" + e.value, onSuccess: (function(req) { this.reportValidation(e, req.responseText); }).bind(this), onFailure: function(req) {alert(req.responseText)}, onException: function(t,e) {alert(e);} });

HTTP verb

new Ajax.Request(this.validateAction, { method: 'get', parameters: e.name + "=" + e.value, onSuccess: (function(req) { this.reportValidation(e, req.responseText); }).bind(this), onFailure: function(req) {alert(req.responseText)}, onException: function(t,e) {alert(e);} });

query string

new Ajax.Request(this.validateAction, { method: 'get', parameters: e.name + "=" + e.value, onSuccess: (function(req) { this.reportValidation(e, req.responseText); }).bind(this), onFailure: function(req) {alert(req.responseText)}, onException: function(t,e) {alert(e);} });

success callback

new Ajax.Request(this.validateAction, { method: 'get', parameters: e.name + "=" + e.value, onSuccess: (function(req) { this.reportValidation(e, req.responseText); }).bind(this), onFailure: function(req) {alert(req.responseText)}, onException: function(t,e) {alert(e);} });

bind if you need this

new Ajax.Request(this.validateAction, { method: 'get', parameters: e.name + "=" + e.value, onSuccess: (function(req) { this.reportValidation(e, req.responseText); }).bind(this), onFailure: function(req) {alert(req.responseText)}, onException: function(t,e) {alert(e);} });

failure callback

new Ajax.Request(this.validateAction, { method: 'get', parameters: e.name + "=" + e.value, onSuccess: (function(req) { this.reportValidation(e, req.responseText); }).bind(this), onFailure: function(req) {alert(req.responseText)}, onException: function(t,e) {alert(e);} });

exception callback

Ajax.Request
• hides browser oddities • takes a slew of options • performs callbacks asynchronously

xhr Example: Polling and Updating

new PeriodicalExecuter(function() { new Ajax.Updater('items', 'http://localhost:3000/retrieve_chats', {evalScripts:true}); }, 10);

call periodically

new PeriodicalExecuter(function() { new Ajax.Updater('items', 'http://localhost:3000/retrieve_chats', {evalScripts:true}); }, 10);

update a page element

new PeriodicalExecuter(function() { new Ajax.Updater('items', 'http://localhost:3000/retrieve_chats', {evalScripts:true}); }, 10);

URL to invoke

new PeriodicalExecuter(function() { new Ajax.Updater('items', 'http://localhost:3000/retrieve_chats', {evalScripts:true}); }, 10);

evaluate response scripts

new PeriodicalExecuter(function() { new Ajax.Updater('items', 'http://localhost:3000/retrieve_chats', {evalScripts:true}); }, 10);

interval (seconds)

xhr
• a method for every season • Ajax.Request, Ajax.Updater • methods share common options

DOM Support

clear: function() { for (var i = 0; i < arguments.length; i++) $(arguments[i]).value = ''; },

$

function $() { var results = [], element; for (var i = 0; i < arguments.length; i++) { element = arguments[i]; if (typeof element == 'string') element = document.getElementById(element); results.push(Element.extend(element)); } return results.length < 2 ? results[0] : results; }

$ implementation

$$("h1").each(function(e) { Element.addClassName(e, "blueborder"); }); $$("li .showme").each(function(e) { Effect.Pulsate(e); });

$$

insertions

log: function(type, message) { new Insertion.Bottom(this.element, '<tr class="' + type + '"><td>' + message.escapeHTML() + '</td></tr>'); Element.scrollTo(this.form); },

insertion example

//from pragforms sample app new Form.Element.Observer( 'search', 0.5, function(element, value) { Element.show('spinner'); new Ajax.Updater( 'ajaxWrapper', 'http://localhost:3000/user/search', { asynchronous:true, evalScripts:true, onComplete:function(request) { Element.hide('spinner') }, parameters:'search=' + encodeURIComponent(value) } ) } )

observe an element

//from pragforms sample app new Form.Element.Observer( 'search',

0.5,
function(element, value) { Element.show('spinner'); new Ajax.Updater( 'ajaxWrapper', 'http://localhost:3000/user/search', { asynchronous:true, evalScripts:true, onComplete:function(request) { Element.hide('spinner') }, parameters:'search=' + encodeURIComponent(value) } ) } )

interval (seconds)

//from pragforms sample app new Form.Element.Observer( 'search', 0.5,

function(element, value) { Element.show('spinner'); new Ajax.Updater( 'ajaxWrapper', 'http://localhost:3000/user/search', { asynchronous:true, evalScripts:true, onComplete:function(request) { Element.hide('spinner') }, parameters:'search=' + encodeURIComponent(value) } ) }
)

handler

<%= observe_field :search, :frequency => 0.5, :update => 'ajaxWrapper', :complete=>"Element.hide('spinner')", :before=>"Element.show('spinner')", :with=>"'search=' + encodeURIComponent(value)", :url=>{:action=>'search'} %>

let Rails write the code

DOM Support
• find elements by id with $ • find by class with $$ • update elements with insertions • grab user input with observers

Resources and Samples
Prototype: prototype.conio.net About Relevance, LLC: www.relevancellc.com/about Pragforms App: www.relevancellc.com/code

Rails Samples: www.pragmaticprogrammer.com/titles/fr_rails4java/