使用原型的无痛JavaScript

原型是一个使JavaScript有趣的面向对象的JavaScript库(由Sam Stephenson和朋友编写)。 不管怎么说,它在网站上说。 那些熟悉开源社区最新和最出色的应用程序框架Rails的人可能会认识到Prototype,因为它实际上构成了Rails JavaScript帮助器的基础。 但是,Prototype可以独立于Rails使用,以帮助编码许多JavaScript脚本和Web 2.0插件。

就我个人而言,我认为陪审团对整个“有趣的” JavaScript事情都持否定态度,但是Prototype是一个执行得很好的JavaScript库,尽管情况最近有所改善,但众所周知它的文档很少。 本文提供了整个图书馆的旋风之旅。 它旨在为您提供足够的示例和资源,以开始在DOM脚本项目中使用Prototype。

首先,我们将研究Prototype的基本构建块:其$函数,其对String,Number,Array和Function对象的添加,其表单处理功能及其DOM函数。 然后,我们继续研究Prototype著名的AJAX帮助器。 最后,我们将简要讨论基于此的其他有趣项目。

我想指出,在撰写本文时,Prototype的当前稳定版本是1.4.0。 我觉得该库将随着Rails迅捷的开发周期而迅速改变,因此情况将会改变。 最后的无聊之处是,此时Prototype仅支持较新的浏览器-您可能希望基于DOM和XMLHttpRequest的库。 有关浏览器支持的详细信息,请参见Prototype网站

入门

可以从原型站点下载最新版本的Prototype。 只需下载prototype.js并使用<script>标记将其链接到您的页面即可:

<script type="text/javascript" src="path/to/prototype.js"></script>

如果您使用的是Rails,则无需下载Prototype:它已包含在发行版中。 您可以通过将其放入页面的<head>中来将其包括在视图中:

<%= javascript_include_tag 'prototype' %>

现在,让我们开始吧!

原型的小帮手

使用Prototype的真正好处之一是它提供了非常致命的简单辅助函数,它可以为非常常见的脚本任务提供帮助。 $函数已经引起了一些关注。 给它一个或多个元素ID,它将返回对它们的引用:

 
// reference to the element with the ID 'nav'
$("nav")
// an array of element references
$("img1", "img2", "img3")

它就像一个document.getElementById ,令人惊讶的是,使用它时,编码看起来更加方便。

另一个非常有用的功能是document.getElementsByClassName ,它按照其提示进行操作:它使用CSS类名并返回该类的所有元素的列表:

// all elements with class 'navlink' 
document.getElementsByClassName("navlink")
// all elements with class navlink and inside the element with ID 'nav'
document.getElementByClassName("navlink", $("nav"))

同样,在撰写本文时,Prototype版本1.5.0_rc0获得了强大的$$函数,该函数使您可以使用标准CSS选择器语法选择元素:

// an array of all input elements inside 'commentform' 
$$("#commentform input")
// an array of all links with the class 'external'
$$("a.external")

请注意,在撰写本文时,除非您从Subversion下载最新版本的Prototype ,否则此功能将不可用。

$F
<select name="country" id="country"> 
 <option selected="selected" value="UK">United Kingdom</option>
 <option value="FR">France</option>
 ...
</select>

$F('country') // 'UK'
Making JavaScript Suck Less

Oops, I've stolen another JavaScript library 's tag line. JavaScript library developers just can't seem to keep from trying to make JavaScript be like another language. The Mochikit guys want JavaScript to be Python, countless programmers have tried to make JavaScript like Java, and Prototype tries to make it like Ruby. Prototype makes extensions to the core of JavaScript that can (if you choose to use them) have a dramatic effect on your approach to coding JavaScript. Depending on your background and the way your brain works, this may or may not be of help to you.

OO the Ruby(ish) way: Class.create and Object.extend

The Class.create method allows you to define classes in a more Ruby-like way, although this is purely aesthetic as it essentially just calls the initialize method you define as the constructor, rather than the taking the traditional JavaScript approach of creating objects with constructor functions.

var DOMTable = Class.create(); 
DOMTable.prototype = {
 initialize : function(el) {
   this.el = el;
 },
 ...
}

However, much more powerful is the stupidly simple but effective Object.extend method . All it does is copy one object's properties and methods to another object, but its uses are many. Here's a quick taster:

// make a (shallow) copy of obj1 
var obj2 = Object.extend({}, obj1);

var options = {
 method : "post",
 args : ""
};

// merges in the given options object to the default options object
Object.extend(options, {
 args : "data=454",
 onComplete : function() { alert("done!"); }
});

options.method // "post"
options.args // "ata=454"
options.onComplete // function() { alert("done!"); }

It's most commonly used to "mix in" methods from one object with another. For instance, you could create a set of functions that make certain DOM elements sortable:

var Sortable = { 
 sortBy : function(func) {
   ...
 },
 sortByReversed : function(func) {
   ...
 },
 reset : function() {
   ...
 }
};

Then, if we wanted to make our DOMTable from above sortable, we could mix in these methods to the DOMTable object:

var myTable = new DOMTable("table-id"); 
Object.extend(myTable, Sortable);

Now we can call those methods on the table:

// sort the table using the given function 
myTable.sortBy(function (itemA, itemB) { ... });

Function Binding

Prototype also adds to the Function object two really useful methods: bind and bindAsEventListener . These are used mainly to bind a function to a particular object so that the this keyword points to that object. This is incredibly useful when you're setting event handler functions. Imagine you try something like this:

var myObject = new Object();  
myObject.message = "Hello!";  
myObject.eventHandler = function() {  
 alert(this.message);  
}  
 
$("mydiv").onmouseover = myObject.eventHandler;

Traditionally, you'd get an error because, when the event triggers the handler function, this refers to the mydiv element, not myObject , so this.message is undefined. You can solve this problem using the bind method like so:

$("mydiv").onmouseover = myObject.eventHandler.bind(myObject);

Now it all works fine, because the this keyword is bound to myObject . Further to that, bindAsEventListener does that same thing, though it passes the event object through to your function in a cross-browser compatible way, so you no longer need to worry about window.event in IE. Try this:

myObject.eventHandler = function(event) {  
 alert(event.srcElement.nodeName);  
}  
 
$("mydiv").onmouseover = myObject.eventHandler.bindAsEventListener(myObject);

Now our eventHandler function has access to the event object. Much more detail on these two methods is available at their creator's site .

New String and Number Methods

Prototype has added an enormous number of useful methods to the built in String object. Let's have a quick look at some of the best.

// "backgroundColor"  
"background-color".camelize()
camelize
// "I am a piece of HTML"  
"I am a piece of <strong>HTML</strong>".striptTags()  
 
// {a : 10, b: "thing"}  
"a=10&b=thing".toQueryParams()

Prototype adds a great method to Number , too. Say goodbye to your for loops!

// alerts "1", "2", "3" ... "50"  
50.times(function(n) {  
 alert(n);  
}};

Here, the times method takes a function that will be called the given number of times, and passes in the current iteration number as an argument. This use of an iterator function is common when using Enumerable, which we'll discuss next.

Iterating the Ruby way: Enumerable and Hash

One of the hidden gems of Prototype is the Enumerable mix-in and the Hash object, which have been poached straight out of Ruby. If you're not familiar with Ruby, don't worry. I'll explain it all here.

We'll start with Enumerable . In short, when we add Enumerable to an object using Object.extend , it gives the object in question lots of really useful functions for working with its properties. Enumerable has been added to Array 's prototype, so any array has these new methods. Here are a few examples of what you can do with the new "enumerated" arrays:

// alerts "a is at 0" then "b is at 1" then "c is at 2"  
["a", "b", "c"].each(function(item, index) {  
 alert(item + " is at " + index);  
});  
 
// [80,50]  
[1, 80, 3, 50].select(function(item) {  
 return (item > 20);  
});
select
// ["A", "B", "C"]  
["a", "b", "c"].invoke("toUpperCase");
invoke
// ["cat", "rat"]  
["cat", "dog", "rat", "mouse",].grep(/at/);
grep
 Enumerable offers a large number of incredibly powerful functions that can make many tedious DOM scripting tasks a breeze. I strongly suggest you have a good look at the Enumerable methods in Sergio Pereira's extremely useful developer notes .

There's a small problem here, though. In JavaScript, you can come across many types of objects that, to all intents and purposes, act like arrays but aren't Array objects. Objects such as DOM NodeLists and function arguments won't have Enumerable available to them automatically. This is easy to rectify, though; to add the Enumerable functions to any array-like object, use $A :

// add Enumerable to childNodes  
var children = $A($("mydiv").childNodes);  
 
// sets class="highlighted" for all child nodes of "mydiv"  
children.each(function(child) {  
 child.setAttribute("class", "highlighted");  
});

To create a hash, call the magic function $H on any object. This turns all the properties of the object into a set of key-value pairs with Enumerable mixed in. Let's take hashes for a spin:

// create a hash by feeding an object to $H  
var contact = $H({  
 name : "Dan Webb",  
 email : "dan@danwebb.net",  
 address : "None of your Business, London",  
 postcode : "a111111"  
});  
 
// ["name", "email", "address", "postcode"]  
contact.keys()  
// ["Dan Webb", "dan@danwebb.net","None of your Business, London", "a111111"]  
contact.values()  
// "name=Dan Webb&email=..."  
contact.toQueryString()
 Hash extends Enumerable as well, so all those useful methods are also available...

// alerts "name contains Dan Webb" and so on  
contact.each(function(item) {  
 alert(item.key + " contains " + item.value);  
});

At first, if you're not a Rubyist, Enumerable and Hash may seem a bit of a hassle but I can assure you, once you start using them, you'll wonder why you ever bothered getting RSI writing all those for loops! When you use one or more of them together, you'll realise the massive power of these new methods. You can read about Enumerable and Hash in more detail at Encyte Media.

The Event object helps to provide what, to many, is the holy grail of JavaScript: simple, cross-browser event handling:

function eventHandlerFunction(e) {  
 // the element that triggered the event  
 var element = Event.element(e);  
 // gets the mouse position  
 var mouseX = Event.pointerX(e),  
     mouseY = Event.pointerY(e);  
 // stop default behaviour and event propagation  
 Event.stop(e);  
}  
 
// register eventHandlerFunction to the onclick of myObject  
Event.observe(myObject, "click", eventHandlerFunction, false);  
 
// removes the event handler  
Event.stopObserving(myObject, "click", eventHandlerFunction, false);

In a rather pleasant way, Prototype tries to avoid those pesky memory leaks in IE by automatically removing every observer when the page unloads.

In my opinion, though, this is a rather under-developed event handling solution at the moment, so it might be worth considering using something a bit richer like Dean Edwards's addEvent for the time being.

Handling Forms

The Form and Field objects provide a number of simple but convenient functions for working with forms and input fields, as well as code that supports Prototype's AJAX implementation.

The Form Object

Generally, methods of the Form object take either an ID or an object reference to an element:

// disables the form making all elements read only   
Form.disable(form)  
// enables a form again  
Form.enable(form)  
// clears values from all form elements    
Form.reset(form)  
// returns an array of all form fields in the form  
Form.getElements(form)  
// focuses on the first form field  
Form.focusFirstElement(form)

The Field Object

The Field object deals with individual form elements, and its methods typically take an ID or an object reference to the element in a similar way to the Form object:

// clears the field, will accept any number of arguments   
Field.clear(field)  
// returns true if all given fields have a value  
Field.clear(field, anotherField)  
// gives focus to the field  
Field.focus(field)  
// selects any text in the field  
Field.select(field)

Form Serialisation

In Prototype terms, serializing a form means reading all the form's elements and turning them into a URL-encoded string (nearly) identical to the one that would be sent if you submitted the form. For example, consider this form:

<form id="search" action="search.php" method="post">   
 <input type="text" name="query" value="thing" />  
 <select name="field">  
   <option value="artistname">Artist Name</option>  
   <option value="title" selected="selected">Title</option>  
 </select>  
 <input type="submit" name="submit" value="Search" />  
</form>  
 
// query=thing&field=title&submit=Search  
Form.serialize($("search"))

Notice that Form.serialize cleverly smoothes over the differences between the ways in which different form elements are accessed, so that inputs, selects, checkboxes and radio buttons are handled properly. Form.serialize is useful for several tasks, but comes into its own when we're working with AJAX, as we'll see shortly.

 Form.serialize exhibits some strange behaviour that's worth mentioning here. You'll remember that I said the URL-encoded string that Form.serialize produces is nearly identical to the one that would be sent if you submitted the form. Well, it's "nearly identical" because Form.serialize doesn't deal with submit button or image inputs properly. It includes all submit buttons in the string, regardless of whether or not they've been pressed, and completely ignores image and button inputs. As long as you're aware of this, you can code around it.

Form Observers

 Form.Observer and Form.Element.Observer allow you to watch a form (or, in the latter case, a single form element) and trigger callbacks when the data changes. There are actually two flavours of each observer that check for value changes. The first is a periodic observer, which works like this:

new Form.Observer($("myform"), 1, myCallBackFunction);   
new Form.Element.Observer($("myfield"), 1, myCallBackFunction);

These observers check every second whether or not the data has changed and, if it has, will call myCallBackFunction .

The second type of observer is event-based and will only perform the check when change or click events are produced for the elements. You can use it like this:

new Form.EventObserver($("myform"), myCallBackFunction);   
new Form.Element.EventObserver($("myfield", myCallbackFunction);

If all the fields in the form you're observing support an event handler, this is a much more efficient way to observe the form. However, if you want to watch for changes in elements that don't support these events, use the periodic observers.

Working the DOM

Prototype has 4 objects ( Element , Insertion , Observer , and Position ) that allow various forms of DOM manipulation and smooth over many of the browser differences that make dealing with the DOM so screen-smashingly infuriating. Instead of throwing your computer out the window, have a look through this section.

The Element Object

The Element object works in the way you've probably come to expect by this point: most of Element's methods simply take an ID or an object reference to the element you want to manipulate. Here's a peek at some of the most useful methods:

// Hides an element   
Element.hide(element)  
// Shows an element  
Element.show(element)  
// Adds a CSS class to the element  
Element.addClassName(element, "cssClassName")  
// Removes a CSS class from the element  
Element.removeClassName(element, "cssClassName")  
// Returns true if element has the CSS class  
Element.hasClassName(element, "cssClassName")  
// {width: 394, height: 20}  
Element.getDimensions(element)  
// replaces the innerHTML of element with newHtml  
Element.update(element, newHtml)

See the full list at Sergio Pereira's site .

The Insertion Object

I know what you're thinking: this sounds a bit weird, right? Well, the Insertion object adds chunks of HTML in and around an element. There are 4 types of insertion: Before , After , Top and Bottom . Here's how you'd add some HTML before an element with the ID "myelement":

new Insertion.Before("myelement", "<p>I'm before!</p>");

This diagram shows where each type of Insertion will drop your HTML content in relation to the given element.

1514_insertions

The Position Object

The Position object offers a load of methods that can tell you about a given location on the screen, and provide information about that location relative to other elements, in a cross-browser compatible way. This should take much of the fiddliness out of writing animations, effects and drag-and-drop code. Have a look at the Position reference for more details.

Get your Web 2.0 On

"Finally!" you're thinking, "He's got on to what we really want to know about." Yes, I've left it to the end to get into Prototype's AJAX helpers, because they're built on top of all the other stuff we've been going through, and it helps to understand Prototype's form serialization, observers and insertions when we talk about AJAX.

AJAX, in case you've been buried in a very deep hole for the past couple of years, refers to using the browser's XMLHttpRequest object (or equivalent) to communicate with the server without reloading the page. Prototype smoothes over most of the detail, but it's still good to get a bit of background on XMLHttpRequest , which you'll find in this article by Cameron Adams .

So, now you're all pumped to get some Web 2.0 action, let's look in to a really simple AJAX request:

new Ajax.Request("hello.php", {    
 onSuccess : function(resp) {    
   alert("The response from the server is: " + resp.responseText);    
 },    
 onFailure : function(resp) {    
   alert("Oops, there's been an error.");    
 },    
 parameters : "name=Fred"    
});

The Ajax.Request constructor takes a URL and an options object. In this case, we're sending a parameter (name) to hello.php , and alerting its response (or alerting an error if it doesn't work). It's worth taking the time to get familiar with what options are available; here's an overview of the options, along with their defaults:

1514_options

Prototype adds a custom HTTP header to all its AJAX requests so that your server application can detect that it's an AJAX call, rather than a normal call. The header is:

X-Requested-With: XMLHttpRequest

Here's an example PHP function used to detect an AJAX call:

function isAjax() {    
 return isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&    
     $_SERVER ['HTTP_X_REQUESTED_WITH']  == 'XMLHttpRequest';    
}

Using this approach, you can write AJAX applications that work even if the user is using an old browser or has JavaScript disabled, but that's a whole other article...

Using Form.serialize to Pass Data to Ajax.Request

As we've seen above, the parameters option is used to pass a URL-encoded string of variables. If the data you need to send is set by a form, as it is with most AJAX applications, you can simply use Form.serialize to generate a URL-encoded string from all of your form fields and pass that into the parameters option like so:

function addComment(e) {    
 // submit the form using Ajax    
 new Ajax.Request("comment.php", {    
   parameters : Form.serialize(this),    
   onSuccess : updateComment    
 });    
 Event.stop(e);    
}    
   
Event.observe($("commentform"), "submit", addComment, false);

Writing AJAX Event Handlers

In the example above, onSuccess and onFailure are two examples of AJAX event handlers. Event handler functions given in the options object of an Ajax.Request call are given one argument, which is the XMLHttpRequest object for that AJAX call. I normally call this argument response or resp . You can use this argument to get the response from the server like so:

function successHandler(resp, jsonObj) {    
 // returns the response from the server as raw text    
 resp.responseText    
 // returns the response as an XML document that you can navigate with the DOM    
 resp.responseXML    
 // alert some property of the returned JSON    
 alert(jsonObj.name);    
}

Remember, though, that resp is just the XMLHttpRequest object, so all of those properties are available.

You can send data as JSON from your server by adding the JSON data to the X-JSON response header. This will then automatically be evaluated by Prototype, and sent as the second argument.

The Ajax.Updater and Ajax.PeriodicalUpdater

Many AJAX operations simply involve updating some HTML on your page with HTML returned from the server. The Ajax.Updater object wraps Ajax.Request and simplifies this common use case for us. Here's a simple example:

new Ajax.Updater("mydiv", "hello.php", {    
 parameters : "name=Fred",    
 onFailure : function(resp) {    
   alert("Oops, there's been an error.");    
 }    
});

The above snippet would simply replace the contents of the element whose ID was "mydiv" with whatever content was returned from the server. Ajax.PeriodicalUpdater is similar, but makes the Ajax call repeatedly at an interval that you set:

new Ajax.PeriodicalUpdater("mydiv", "hello.php", {    
 // initial number of seconds interval between calls    
 frequency : 1,    
 decay : 2    
});

The decay option allows you to give your server a bit of a break if it's returning a lot of identical responses. Essentially, every time PeriodicalUpdater makes a request, it compares the results with what the server returned last time. If the values are the same, it multiplies the interval by the decay value. So, for the above example, it would make the next request two seconds later, then four seconds later, and so on, until it received a different result from the server. At that point, the interval would be reset to one second.

AJAX with Responders

AJAX responders allow you to register global event handlers that are triggered for each and every AJAX request that happens on the page. They're very useful for managing applications with large amounts of AJAX activity. For instance, you can use them to show a standard loading animation whenever an AJAX request is happening:

Ajax.Responders.register({    
 onCreate : showLoader,    
 onComplete : hideLoader    
});

If you are looking for some working examples of AJAX with Prototype, try this article .

Where to Next?

As we've seen through this article, Prototype not only is useful on its own, but provides an excellent starting point for writing other, more specialized libraries. That's exactly what a growing number of people have been doing.

Script.aculo.us and Moo.fx

Thomas Fuchs' script.aculo.us is getting a lot of attention at the moment for its whiz-bang effects and clever UI widgets. It was originally part of the core Prototype library, but soon grew out of control and broke free of its parent.

Using Prototype as a basis, script.aculo.us specialises in providing a rich user experience through animated effects, simple to use drag and drop functionality, and powerful UI components. There's a nice Wiki on the site, with a rapidly growing store of quality documentation to help you get started, and examples pages to get your creative juices flowing. As script.aculo.us is getting rather large in file size, it's been split into several files, so your users won't have to download the whole library just so you can use a few slide effects. However, even the individual files are pretty bulky.

If you're after some simple effects, I'd really recommend Moo.fx . It's only 3k in size, and gives you some toggling slide and fade effects that, often, are all that's required in a simple AJAX application. It's also a great starting point if you want to write your own effects. Have a look at the code to see a great example of programming using Prototype's Object.extend to provide simple inheritance. Valerio is obviously very focused on keeping his script file sizes down, so he even has a 'lite' version of Prototype (chopped to around 10k), and a lean version of Ajax.Request , which I find myself using more often than the full Prototype library. It's definitely worth a look.

Behaviour

Behaviour is a great addition to your DOM scripting toolkit that allows you to use CSS selectors to add behaviour to your documents. Here's a sample of what it allows you to do:

Behaviour.register({    
 "#comment_form form" : function(el) {    
   // catch form submission and complete with XHR if possible    
   el.onsubmit = function() {    
     Form.disable(this);    
     new Ajax.Request(this.action, {    
       method: this.method,    
       postBody: Form.serialize(this),    
       onComplete: updateComments});    
     return false;    
   };    
 }    
});

Read more about this over at the Behaviour site . It's now possible to achieve a similar type of thing using the brand new $$ function discussed earlier, so this may eventually become redundant.

jQuery

jQuery is a compact little library that plays well with Prototype and creates a superhero version of the $ function that can take XPath and CSS 3 selectors. It couples that capability with some extremely clever method chaining approach that makes for very concise code. Watch out for this one.

Wrap up

Prototype is a powerful piece of kit when it comes to adding some DOM scripted spice to your web applications. Unfortunately, its capabilities and tools have grown at a much faster rate than its documentation! We've managed to cover every corner of Prototype in this article, but not every method. I hope, though, that you now know where to go to get the extra information you need.

If you want to know more try Ronnie Roller's prototypedoc.com , a resource that keeps up with the growing body of Prototype documentation. Have fun!

From: https://www.sitepoint.com/painless-javascript-prototype/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值