1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Rewrite JavaScript guide to use ES6

Co-Authored-By: guledali <guled.ali01@gmail.com>

Closes #37529
This commit is contained in:
Gannon McGibbon 2020-02-20 18:46:01 -05:00
parent 145299315a
commit 1e7f867a7a

View file

@ -37,16 +37,15 @@ powers, a JavaScript writer can make a web page that can update just parts of
itself, without needing to get the full page data from the server. This is a itself, without needing to get the full page data from the server. This is a
powerful technique that we call Ajax. powerful technique that we call Ajax.
Rails ships with CoffeeScript by default, and so the rest of the examples As an example, here's some JavaScript code that makes an Ajax request:
in this guide will be in CoffeeScript. All of these lessons, of course, apply
to vanilla JavaScript as well.
As an example, here's some CoffeeScript code that makes an Ajax request using ```js
the jQuery library: fetch("/test")
.then((data) => data.text())
```coffeescript .then((html) => {
$.ajax(url: "/test").done (html) -> const results = document.querySelector("#results");
$("#results").append html results.insertAdjacentHTML("beforeend", data);
});
``` ```
This code fetches data from "/test", and then appends the result to the `div` This code fetches data from "/test", and then appends the result to the `div`
@ -69,57 +68,68 @@ Here's the simplest way to write JavaScript. You may see it referred to as
'inline JavaScript': 'inline JavaScript':
```html ```html
<a href="#" onclick="this.style.backgroundColor='#990000'">Paint it red</a> <a href="#" onclick="this.style.backgroundColor='#990000';event.preventDefault();">Paint it red</a>
``` ```
When clicked, the link background will become red. Here's the problem: what When clicked, the link background will become red. Here's the problem: what
happens when we have lots of JavaScript we want to execute on a click? happens when we have lots of JavaScript we want to execute on a click?
```html ```html
<a href="#" onclick="this.style.backgroundColor='#009900';this.style.color='#FFFFFF';">Paint it green</a> <a href="#" onclick="this.style.backgroundColor='#009900';this.style.color='#FFFFFF';event.preventDefault();">Paint it green</a>
``` ```
Awkward, right? We could pull the function definition out of the click handler, Awkward, right? We could pull the function definition out of the click handler,
and turn it into CoffeeScript: and turn it a function:
```coffeescript ```js
@paintIt = (element, backgroundColor, textColor) -> window.paintIt = function(event, backgroundColor, textColor) {
element.style.backgroundColor = backgroundColor event.preventDefault();
if textColor? event.target.style.backgroundColor = backgroundColor;
element.style.color = textColor if (textColor) {
event.target.style.color = textColor;
}
}
``` ```
And then on our page: And then on our page:
```html ```html
<a href="#" onclick="paintIt(this, '#990000')">Paint it red</a> <a href="#" onclick="paintIt(event, '#990000')">Paint it red</a>
``` ```
That's a little bit better, but what about multiple links that have the same That's a little bit better, but what about multiple links that have the same
effect? effect?
```html ```html
<a href="#" onclick="paintIt(this, '#990000')">Paint it red</a> <a href="#" onclick="paintIt(event, '#990000')">Paint it red</a>
<a href="#" onclick="paintIt(this, '#009900', '#FFFFFF')">Paint it green</a> <a href="#" onclick="paintIt(event, '#009900', '#FFFFFF')">Paint it green</a>
<a href="#" onclick="paintIt(this, '#000099', '#FFFFFF')">Paint it blue</a> <a href="#" onclick="paintIt(event, '#000099', '#FFFFFF')">Paint it blue</a>
``` ```
Not very DRY, eh? We can fix this by using events instead. We'll add a `data-*` Not very DRY, eh? We can fix this by using events instead. We'll add a `data-*`
attribute to our link, and then bind a handler to the click event of every link attribute to our link, and then bind a handler to the click event of every link
that has that attribute: that has that attribute:
```coffeescript ```js
@paintIt = (element, backgroundColor, textColor) -> function paintIt(element, backgroundColor, textColor) {
element.style.backgroundColor = backgroundColor element.style.backgroundColor = backgroundColor;
if textColor? if (textColor) {
element.style.color = textColor element.style.color = textColor;
}
}
$ -> window.addEventListener("load", () => {
$("a[data-background-color]").click (e) -> const links = document.querySelectorAll(
e.preventDefault() "a[data-background-color]"
);
links.forEach((element) => {
element.addEventListener("click", (event) => {
event.preventDefault();
backgroundColor = $(this).data("background-color") const {backgroundColor, textColor} = element.dataset;
textColor = $(this).data("text-color") paintIt(element, backgroundColor, textColor);
paintIt(this, backgroundColor, textColor) });
});
});
``` ```
```html ```html
<a href="#" data-background-color="#990000">Paint it red</a> <a href="#" data-background-color="#990000">Paint it red</a>
@ -135,10 +145,6 @@ concatenator. We can serve our entire JavaScript bundle on every page, which
means that it'll get downloaded on the first page load and then be cached on means that it'll get downloaded on the first page load and then be cached on
every page after that. Lots of little benefits really add up. every page after that. Lots of little benefits really add up.
The Rails team strongly encourages you to write your CoffeeScript (and
JavaScript) in this style, and you can expect that many libraries will also
follow this pattern.
Built-in Helpers Built-in Helpers
---------------- ----------------
@ -164,10 +170,10 @@ remote elements inside your application.
[`form_with`](https://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_with) [`form_with`](https://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_with)
is a helper that assists with writing forms. By default, `form_with` assumes that is a helper that assists with writing forms. By default, `form_with` assumes that
your form will be using Ajax. You can opt out of this behavior by your form will be using Ajax. You can opt out of this behavior by
passing the `:local` option `form_with`. passing the `:local` option to `form_with`.
```erb ```erb
<%= form_with model: @article do |form| %> <%= form_with(model: @article, id: "new-article") do |form| %>
... ...
<% end %> <% end %>
``` ```
@ -175,7 +181,7 @@ passing the `:local` option `form_with`.
This will generate the following HTML: This will generate the following HTML:
```html ```html
<form action="/articles" accept-charset="UTF-8" method="post" data-remote="true"> <form id="new-article" action="/articles" accept-charset="UTF-8" method="post" data-remote="true">
... ...
</form> </form>
``` ```
@ -187,22 +193,22 @@ You probably don't want to just sit there with a filled out `<form>`, though.
You probably want to do something upon a successful submission. To do that, You probably want to do something upon a successful submission. To do that,
bind to the `ajax:success` event. On failure, use `ajax:error`. Check it out: bind to the `ajax:success` event. On failure, use `ajax:error`. Check it out:
```coffeescript ```js
$(document).ready -> window.addEventListener("load", () => {
$("#new_article").on("ajax:success", (event) -> const element = document.querySelector("#new-article");
[data, status, xhr] = event.detail element.addEventListener("ajax:success", (event) => {
$("#new_article").append xhr.responseText const [_data, _status, xhr] = event.detail;
).on "ajax:error", (event) -> element.insertAdjacentHTML("beforeend", xhr.responseText);
$("#new_article").append "<p>ERROR</p>" });
element.addEventListener("ajax:error", () => {
element.insertAdjacentHTML("beforeend", "<p>ERROR</p>");
});
});
``` ```
Obviously, you'll want to be a bit more sophisticated than that, but it's a Obviously, you'll want to be a bit more sophisticated than that, but it's a
start. start.
NOTE: As of Rails 5.1 and the new `rails-ujs`, the parameters `data, status, xhr`
have been bundled into `event.detail`. For information about the previously used
`jquery-ujs` in Rails 5 and earlier, read the [`jquery-ujs` wiki](https://github.com/rails/jquery-ujs/wiki/ajax).
#### link_to #### link_to
[`link_to`](https://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to) [`link_to`](https://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to)
@ -227,12 +233,17 @@ click. We would generate some HTML like this:
<%= link_to "Delete article", @article, remote: true, method: :delete %> <%= link_to "Delete article", @article, remote: true, method: :delete %>
``` ```
and write some CoffeeScript like this: and write some JavaScript like this:
```coffeescript ```js
$ -> window.addEventListener("load", () => {
$("a[data-remote]").on "ajax:success", (event) -> const links = document.querySelectorAll("a[data-remote]");
alert "The article was deleted." links.forEach((element) => {
element.addEventListener("ajax:success", () => {
alert("The article was deleted.");
});
});
});
``` ```
#### button_to #### button_to
@ -298,7 +309,7 @@ requests for `data-remote` elements, by way of the `data-type` attribute.
### Confirmations ### Confirmations
You can ask for an extra confirmation of the user by adding a `data-confirm` You can ask for an extra confirmation of the user by adding a `data-confirm`
attribute on links and forms. The user will be presented a JavaScript `confirm()` attribute on links and forms. The user will be presented with a JavaScript `confirm()`
dialog containing the attribute's text. If the user chooses to cancel, the action dialog containing the attribute's text. If the user chooses to cancel, the action
doesn't take place. doesn't take place.
@ -323,7 +334,7 @@ you should **not** have `data-confirm` on the form itself.
The default confirmation uses a JavaScript confirm dialog, but you can customize The default confirmation uses a JavaScript confirm dialog, but you can customize
this by listening to the `confirm` event, which is fired just before the confirmation this by listening to the `confirm` event, which is fired just before the confirmation
window appears to the user. To cancel this default confirmation, have the confirm window appears to the user. To cancel this default confirmation, have the confirm
handler to return `false`. handler return `false`.
### Automatic disabling ### Automatic disabling
@ -338,9 +349,9 @@ This also works for links with `data-method` attribute.
For example: For example:
```erb ```erb
<%= form_with model: @article.new do |form| %> <%= form_with(model: Article.new) do |form| %>
<%= form.submit data: { disable_with: "Saving..." } %> <%= form.submit data: { disable_with: "Saving..." } %>
<%= end %> <% end %>
``` ```
This generates a form with: This generates a form with:
@ -358,6 +369,7 @@ These introductions cause small changes to `custom events` fired during the requ
NOTE: Signature of calls to UJS's event handlers has changed. NOTE: Signature of calls to UJS's event handlers has changed.
Unlike the version with jQuery, all custom events return only one parameter: `event`. Unlike the version with jQuery, all custom events return only one parameter: `event`.
In this parameter, there is an additional attribute `detail` which contains an array of extra parameters. In this parameter, there is an additional attribute `detail` which contains an array of extra parameters.
For information about the previously used `jquery-ujs` in Rails 5 and earlier, read the [`jquery-ujs` wiki](https://github.com/rails/jquery-ujs/wiki/ajax).
| Event name | Extra parameters (event.detail) | Fired | | Event name | Extra parameters (event.detail) | Fired |
|---------------------|---------------------------------|-------------------------------------------------------------| |---------------------|---------------------------------|-------------------------------------------------------------|
@ -371,18 +383,14 @@ In this parameter, there is an additional attribute `detail` which contains an a
Example usage: Example usage:
```html ```js
document.body.addEventListener('ajax:success', function(event) { document.body.addEventListener("ajax:success", (event) => {
var detail = event.detail; const [data, status, xhr] = event.detail;
var data = detail[0], status = detail[1], xhr = detail[2]; });
})
``` ```
NOTE: As of Rails 5.1 and the new `rails-ujs`, the parameters `data, status, xhr`
have been bundled into `event.detail`. For information about the previously used
`jquery-ujs` in Rails 5 and earlier, read the [`jquery-ujs` wiki](https://github.com/rails/jquery-ujs/wiki/ajax).
### Stoppable events ### Stoppable events
You can stop execution of the Ajax request by running `event.preventDefault()` You can stop execution of the Ajax request by running `event.preventDefault()`
from the handlers methods `ajax:before` or `ajax:beforeSend`. from the handlers methods `ajax:before` or `ajax:beforeSend`.
The `ajax:before` event can manipulate form data before serialization and the The `ajax:before` event can manipulate form data before serialization and the
@ -393,8 +401,8 @@ browser to submit the form via normal means (i.e. non-Ajax submission) will be
canceled and the form will not be submitted at all. This is useful for canceled and the form will not be submitted at all. This is useful for
implementing your own Ajax file upload workaround. implementing your own Ajax file upload workaround.
Note, you should use `return false` to prevent event for `jquery-ujs` and Note, you should use `return false` to prevent an event for `jquery-ujs` and
`e.preventDefault()` for `rails-ujs` `event.preventDefault()` for `rails-ujs`.
Server-Side Concerns Server-Side Concerns
-------------------- --------------------
@ -475,10 +483,13 @@ respond to your Ajax request. You then have a corresponding
`app/views/users/create.js.erb` view file that generates the actual JavaScript `app/views/users/create.js.erb` view file that generates the actual JavaScript
code that will be sent and executed on the client side. code that will be sent and executed on the client side.
```erb ```js
$("<%= escape_javascript(render @user) %>").appendTo("#users"); var users = document.querySelector("#users");
users.insertAdjacentHTML("beforeend", "<%= j render(@user) %>");
``` ```
NOTE: JavaScript view rendering doesn't do any preprocessing, so you shouldn't use ES6 syntax here.
Turbolinks Turbolinks
---------- ----------
@ -504,21 +515,23 @@ attribute to the tag:
### Page Change Events ### Page Change Events
When writing CoffeeScript, you'll often want to do some sort of processing upon You'll often want to do some sort of processing upon
page load. With jQuery, you'd write something like this: page load. Using the DOM, you'd write something like this:
```coffeescript ```js
$(document).ready -> window.addEventListener("load", () => {
alert "page has loaded!" alert("page has loaded!");
});
``` ```
However, because Turbolinks overrides the normal page loading process, the However, because Turbolinks overrides the normal page loading process, the
event that this relies upon will not be fired. If you have code that looks like event that this relies upon will not be fired. If you have code that looks like
this, you must change your code to do this instead: this, you must change your code to do this instead:
```coffeescript ```js
$(document).on "turbolinks:load", -> document.addEventListener("turbolinks:load", () => {
alert "page has loaded!" alert("page has loaded!");
});
``` ```
For more details, including other events you can bind to, check out [the For more details, including other events you can bind to, check out [the
@ -532,23 +545,23 @@ When using another library to make Ajax calls, it is necessary to add
the security token as a default header for Ajax calls in your library. To get the security token as a default header for Ajax calls in your library. To get
the token: the token:
```javascript ```js
var token = document.getElementsByName('csrf-token')[0].content const token = document.getElementsByName(
"csrf-token"
)[0].content;
``` ```
You can then submit this token as a `X-CSRF-Token` header for your You can then submit this token as a `X-CSRF-Token` header for your
Ajax request. You do not need to add a CSRF token for GET requests, Ajax request. You do not need to add a CSRF token for GET requests,
only non-GET ones. only non-GET ones.
You can read more about about Cross-Site Request Forgery in [Security](https://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf) You can read more about about Cross-Site Request Forgery in the [Security guide](https://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf).
Other Resources Other Resources
--------------- ---------------
Here are some helpful links to help you learn even more: Here are some helpful links to help you learn even more:
* [jquery-ujs wiki](https://github.com/rails/jquery-ujs/wiki) * [rails-ujs wiki](https://github.com/rails/rails/tree/master/actionview/app/assets/javascripts)
* [jquery-ujs list of external articles](https://github.com/rails/jquery-ujs/wiki/External-articles)
* [Rails 3 Remote Links and Forms: A Definitive Guide](http://www.alfajango.com/blog/rails-3-remote-links-and-forms/)
* [Railscasts: Unobtrusive JavaScript](http://railscasts.com/episodes/205-unobtrusive-javascript) * [Railscasts: Unobtrusive JavaScript](http://railscasts.com/episodes/205-unobtrusive-javascript)
* [Railscasts: Turbolinks](http://railscasts.com/episodes/390-turbolinks) * [Railscasts: Turbolinks](http://railscasts.com/episodes/390-turbolinks)