Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 119 lines (82 sloc) 7.3 kb
0bde6d6 @mislav documentation
mislav authored
1 Unobtrusive scripting support for prototype.js
2 ==============================================
3
4 This unobtrusive scripting support file is developed for the Ruby on Rails framework, but is not strictly tied to any specific backend. You can drop this into any application to:
5
6 - force confirmation dialogs for various actions;
7 - make non-GET requests from hyperlinks;
8 - make forms or hyperlinks submit data asynchronously with Ajax;
9 - have submit buttons become automatically disabled on form submit to prevent double-clicking.
10
11 These features are achieved by adding certain ["data" attributes][data] to your HTML markup. Full documentation of recognized attributes is below.
12
13 Requirements
14 ------------
15
16 - [prototype.js 1.7 RC3][proto] or later;
17 - for Ruby on Rails only: `<%= csrf_meta_tag %>` in the HEAD of your main layout;
18 - HTML5 doctype (optional).
19
20 If you don't use HTML5, adding "data" attributes to your HTML4 or XHTML pages might make them fail [W3C markup validation][validator]. However, this shouldn't create any issues for web browsers or other user agents.
21
22 In Ruby on Rails 3, the `csrf_meta_tag` helper generates two meta tags containing values necessary for [cross-site request forgery protection][csrf] built into Rails. If you're using Rails 2, here is how to implement that helper:
23
24 # app/helpers/application_helper.rb
25 def csrf_meta_tag
26 if protect_against_forgery?
27 out = %(<meta name="csrf-param" content="%s"/>\n)
28 out << %(<meta name="csrf-token" content="%s"/>)
29 out % [ Rack::Utils.escape_html(request_forgery_protection_token),
30 Rack::Utils.escape_html(form_authenticity_token) ]
31 end
32 end
33
34 Documentation
35 -------------
36
37 ### "data-confirm": Confirmation dialogs for links and forms
38
39 <form data-confirm="Are you sure you want to submit?">...</form>
40
41 The presence of this attribute indicates that activating a link or submitting a form should be intercepted so the user can be presented a JavaScript `confirm()` dialog containing the text that is the value of the attribute. If the user chooses to cancel, the action doesn't take place.
42
43 ### "data-disable-with": Automatic disabling of submit buttons in forms
44
45 <input type="submit" value="Save" data-disable-with="Saving...">
46
47 This attribute indicates that a submit button should get disabled while the form is submitting. This is to prevent accidental double-clicks from the user, which could result in duplicate HTTP requests that the backend may not detect as such. The value of the attribute is text that will become the new value of the button in its disabled state.
48
49 ### "data-method": Links that result in POST, PUT, or DELETE requests
50
51 <a href="..." data-method="delete">Delete this entry</a>
52
53 Activating hyperlinks (usually by clicking or tapping on them) always results in an HTTP GET request. However, if your application is [RESTful][], some links are in fact actions that change data on the server and must be performed with non-GET requests. This attribute allows marking up such links with an explicit method such as "post", "put" or "delete".
54
55 The way it works is that, when the link is activated, it constructs a hidden form in the document with the "action" attribute corresponding to "href" value of the link and the method corresponding to "data-method" value, and submits that form.
56
57 Note for non-Rails backends: because submitting forms with HTTP methods other than GET and POST isn't widely supported across browsers, all other HTTP methods are actually sent over POST with the intended method indicated in the "_method" parameter. Rails framework automatically detects and compensates for this.
58
59 ### "data-remote": Make links and forms submit asynchronously with Ajax
60
61 <form data-remote="true">...</form>
62
63 This attribute indicates that the link or form is to be submitted asynchronously; that is, without the page refreshing.
64
65 If the backend is configured to return snippets of JavaScript for these requests, those snippets will get executed on the page once requests are completed. This is regular prototype.js behavior, and can be used for modifying the document after an action has taken place.
66
67 Alternatively, you can handle the following custom events to hook into the lifecycle of the Ajax request.
68
69 #### Custom events fired during "data-remote" requests
70
71 - `ajax:before` (no memo, stoppable) — fires before the Ajax request is initiated. If you stop this event with `event.stop()` method, the Ajax request will never take place.
72 - `ajax:create` — right after the Ajax request object has been initialized, but before it is sent. Useful for adding custom request headers.
73 - `ajax:success` — after completion, if the HTTP response was success;
74 - `ajax:failure` — after completion, if the server returned an error;
75 - `ajax:complete` — after the request has been completed, no matter what outcome.
76
77 The [`Ajax.Response`][response] instance of the current request is accessible on each of these events except `ajax:before` through the `memo` property. The [`Ajax.Request`][ajax] instance is then accessible through the `request` property on the response object. To illustrate, an example event handler would look like:
78
79 function(event) {
80 var response = event.memo
81 response.request //=> Ajax.Request instance
82 response.responseText //=> text body of the response
83 response.responseJSON //=> data object in case of JSON responses
84 }
85
86 #### Examples
87
88 When processing a request failed on the server, it might return the error message as HTML:
89
90 document.on('ajax:failure', '#account_settings', function(event, container) {
91 // insert the failure message inside the "#account_settings" element
92 container.insert(event.memo.responseText)
93 })
94
95 Set custom HTTP headers just for a specific type of forms:
96
97 document.on('ajax:create', 'form.new_conversation', function(event) {
98 var request = event.memo.request
99 request.options.requestHeaders = {'Accept': 'text/html'}
100 })
101
102 If the form has file uploads, they can't be serialized and sent with Ajax. This detects file uploads in forms marked with "data-remote", prevents the Ajax request and, instead, submits the form normally (synchronously). The same form *without* file uploads would still be sent asynchronously, with Ajax:
103
104 document.on('ajax:before', 'form', function(e, form) {
105 // detects if there are files for upload
106 var hasFiles = form.select('input[type=file]').any(function(i){ return i.getValue() })
107 if (hasFiles) {
108 e.stop() // prevent Ajax request, it won't work for files
109 form.submit() // submit the form as usual
110 }
111 })
112
113 [data]: http://dev.w3.org/html5/spec/elements.html#embedding-custom-non-visible-data-with-the-data-attributes "Embedding custom non-visible data with the data-* attributes"
114 [proto]: http://prototypejs.org/2010/10/12/prototype-1-7-rc3-support-for-ie9
115 [validator]: http://validator.w3.org/
116 [csrf]: http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html
117 [RESTful]: http://en.wikipedia.org/wiki/Representational_State_Transfer "Representational State Transfer"
118 [ajax]: http://prototypejs.github.com/doc/edge/ajax/Ajax/Request/
119 [response]: http://prototypejs.github.com/doc/edge/ajax/Ajax/Response/
Something went wrong with that request. Please try again.